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