import {
Either,
getRequiredEnvVars,
getStdout,
type IEither,
type ITraceable,
type LogMetricTraceSupplier,
Metric,
TraceUtil,
} from '@emprespresso/pengueno';
// -- --
export interface LoginItem {
login: {
username: string;
password: string;
};
}
export interface SecureNote {
notes: string;
}
export type SecretItem = LoginItem | SecureNote;
export interface IVault {
unlock: (client: TClient) => Promise>;
lock: (client: TClient, key: TKey) => Promise>;
fetchSecret: (client: TClient, key: TKey, item: TItemId) => Promise>;
}
// -- --
// -- --
type TClient = ITraceable;
type TKey = string;
type TItemId = string;
export class Bitwarden implements IVault {
constructor(private readonly config: BitwardenConfig) {}
public unlock(client: TClient) {
return client
.move(this.config)
.bimap(TraceUtil.withMetricTrace(Bitwarden.loginMetric))
.flatMap((tConfig) => tConfig.move(`bw config server ${tConfig.get().server}`).map(getStdout))
.map(async (tEitherWithConfig) => {
const eitherWithConfig = await tEitherWithConfig.get();
tEitherWithConfig.trace.trace('logging in~ ^.^');
return eitherWithConfig.flatMapAsync((_) =>
tEitherWithConfig.move('bw login --apikey --quiet').map(getStdout).get(),
);
})
.peek(async (tEitherWithAuthd) => {
const eitherWithAuthd = await tEitherWithAuthd.get();
return tEitherWithAuthd.trace.trace(
eitherWithAuthd.fold(({ isLeft }) => Bitwarden.loginMetric[isLeft ? 'failure' : 'success']),
);
})
.map(async (tEitherWithAuthd) => {
const eitherWithAuthd = await tEitherWithAuthd.get();
tEitherWithAuthd.trace.trace('unlocking the secret vault~ (◕ᴗ◕✿)');
return eitherWithAuthd.flatMapAsync((_) =>
tEitherWithAuthd.move('bw unlock --passwordenv BW_PASSWORD --raw').map(getStdout).get(),
);
})
.peek(async (tEitherWithSession) => {
const eitherWithAuthd = await tEitherWithSession.get();
return tEitherWithSession.trace.trace(
eitherWithAuthd.fold(({ isLeft }) => Bitwarden.unlockVaultMetric[isLeft ? 'failure' : 'success']),
);
})
.get();
}
public fetchSecret(client: TClient, key: string, item: string): Promise> {
return client
.move(key)
.bimap(TraceUtil.withMetricTrace(Bitwarden.fetchSecretMetric))
.peek((tSession) => tSession.trace.trace(`looking for your secret ${item} (⑅˘꒳˘)`))
.flatMap((tSession) =>
tSession.move('bw list items').map((listCmd) =>
getStdout(listCmd, {
env: { BW_SESSION: tSession.get() },
}),
),
)
.map(
TraceUtil.promiseify((tEitherItemsJson) =>
tEitherItemsJson
.get()
.flatMap(
(itemsJson): IEither> =>
Either.fromFailable(() => JSON.parse(itemsJson)),
)
.flatMap((itemsList): IEither => {
const secret = itemsList.find(({ name }) => name === item);
if (!secret) {
return Either.left(new Error(`couldn't find the item ${item} (。•́︿•̀。)`));
}
return Either.right(secret);
}),
),
)
.peek(async (tEitherWithSecret) => {
const eitherWithSecret = await tEitherWithSecret.get();
return tEitherWithSecret.trace.trace(
eitherWithSecret.fold(({ isLeft }) => Bitwarden.fetchSecretMetric[isLeft ? 'failure' : 'success']),
);
})
.get();
}
public lock(client: TClient, key: TKey) {
return client
.move(key)
.bimap(TraceUtil.withMetricTrace(Bitwarden.lockVaultMetric))
.peek((tSession) => tSession.trace.trace(`taking care of locking the vault :3`))
.flatMap((tSession) =>
tSession.move('bw lock').map((lockCmd) =>
getStdout(lockCmd, {
env: { BW_SESSION: tSession.get() },
}),
),
)
.peek(async (tEitherWithLocked) => {
const eitherWithLocked = await tEitherWithLocked.get();
return eitherWithLocked.fold(({ isLeft }) => {
tEitherWithLocked.trace.trace(Bitwarden.lockVaultMetric[isLeft ? 'failure' : 'success']);
if (isLeft) return;
tEitherWithLocked.trace.trace('all locked up and secure now~ (。•̀ᴗ-)✧');
});
})
.get();
}
public static getConfigFromEnvironment(): IEither {
return getRequiredEnvVars(['BW_SERVER', 'BW_CLIENTSECRET', 'BW_CLIENTID', 'BW_PASSWORD']).mapRight(
({ BW_SERVER, BW_CLIENTSECRET, BW_CLIENTID }) => ({
clientId: BW_CLIENTID,
secret: BW_CLIENTSECRET,
server: BW_SERVER,
}),
);
}
private static loginMetric = Metric.fromName('Bitwarden.login');
private static unlockVaultMetric = Metric.fromName('Bitwarden.unlockVault');
private static fetchSecretMetric = Metric.fromName('Bitwarden.fetchSecret');
private static lockVaultMetric = Metric.fromName('Bitwarden.lock');
}
export interface BitwardenConfig {
server: string;
secret: string;
clientId: string;
}
// -- --