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