summaryrefslogtreecommitdiff
path: root/worker/secret.ts
diff options
context:
space:
mode:
Diffstat (limited to 'worker/secret.ts')
-rw-r--r--worker/secret.ts193
1 files changed, 193 insertions, 0 deletions
diff --git a/worker/secret.ts b/worker/secret.ts
new file mode 100644
index 0000000..e0a4c5d
--- /dev/null
+++ b/worker/secret.ts
@@ -0,0 +1,193 @@
+import {
+ Either,
+ getRequiredEnvVars,
+ getStdout,
+ type IEither,
+ type ITraceable,
+ type LogMetricTraceSupplier,
+ Metric,
+ TraceUtil,
+} from "@emprespresso/pengueno";
+
+// -- <ISecret> --
+export interface LoginItem {
+ login: {
+ username: string;
+ password: string;
+ };
+}
+
+export interface SecureNote {
+ notes: string;
+}
+
+export type SecretItem = LoginItem | SecureNote;
+export interface IVault<TClient, TKey, TItemId> {
+ unlock: (client: TClient) => Promise<IEither<Error, TKey>>;
+ lock: (client: TClient, key: TKey) => Promise<IEither<Error, TKey>>;
+
+ fetchSecret: <T extends SecretItem>(
+ client: TClient,
+ key: TKey,
+ item: TItemId,
+ ) => Promise<IEither<Error, T>>;
+}
+// -- </ISecret> --
+
+// -- <IVault> --
+type TClient = ITraceable<unknown, LogMetricTraceSupplier>;
+type TKey = string;
+type TItemId = string;
+export class Bitwarden implements IVault<TClient, TKey, TItemId> {
+ 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(
+ (err, _val) => Bitwarden.loginMetric[err ? "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(
+ (err, _val) =>
+ Bitwarden.unlockVaultMetric[err ? "failure" : "success"],
+ ),
+ );
+ })
+ .get();
+ }
+
+ public fetchSecret<T extends SecretItem>(
+ client: TClient,
+ key: string,
+ item: string,
+ ): Promise<IEither<Error, T>> {
+ 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<Error, Array<T & { name: string }>> =>
+ Either.fromFailable(() => JSON.parse(itemsJson)),
+ )
+ .flatMap((itemsList): IEither<Error, T> => {
+ 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(
+ (err, _val) =>
+ Bitwarden.fetchSecretMetric[err ? "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((err, _val) => {
+ tEitherWithLocked.trace.trace(
+ Bitwarden.lockVaultMetric[err ? "failure" : "success"],
+ );
+ if (err) return;
+ tEitherWithLocked.trace.trace(
+ "all locked up and secure now~ (。•̀ᴗ-)✧",
+ );
+ });
+ })
+ .get();
+ }
+
+ public static getConfigFromEnvironment(): IEither<Error, BitwardenConfig> {
+ 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;
+}
+// -- </IVault> --