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; } // -- --