import { Either, getRequiredEnvVars, getStdout, type IEither, type ITraceable, type LogMetricTraceSupplier, Metric, TraceUtil, } from '@emprespresso/pengueno'; // -- -- export interface SecretItem { name: string; } export interface LoginItem extends SecretItem { login: { username: string; password: string; }; } export interface SecureNote extends SecretItem { notes: string; } 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) { const authed = client .move(this.config) .flatMap(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(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.loginMetric))); const unlocked = authed .flatMap(TraceUtil.withMetricTrace(Bitwarden.unlockVaultMetric)) .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(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.unlockVaultMetric))); return unlocked.get(); } public fetchSecret(client: TClient, key: string, item: string): Promise> { return client .move(key) .flatMap(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); }), ), ) .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.fetchSecretMetric))) .get(); } public lock(client: TClient, key: TKey) { return client .move(key) .flatMap(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(TraceUtil.promiseify(TraceUtil.traceResultingEither(Bitwarden.lockVaultMetric))) .peek( TraceUtil.promiseify((tEitherWithLocked) => tEitherWithLocked .get() .mapRight(() => 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').asResult(); private static unlockVaultMetric = Metric.fromName('Bitwarden.unlockVault').asResult(); private static fetchSecretMetric = Metric.fromName('Bitwarden.fetchSecret').asResult(); private static lockVaultMetric = Metric.fromName('Bitwarden.lock').asResult(); } export interface BitwardenConfig { server: string; secret: string; clientId: string; } // -- --