From 155d81c1135e03c518308a0c5d740de4e36f12cc Mon Sep 17 00:00:00 2001 From: Corentin Mors Date: Thu, 10 Aug 2023 10:54:11 +0200 Subject: [PATCH] Add secret field transformation for otp --- src/errors.ts | 6 +++++ src/modules/database/vaultSecrets.ts | 14 +++++++++- src/types.ts | 1 + src/utils/index.ts | 1 + src/utils/secretPath.ts | 38 ++++++++++++++++++++++++---- src/utils/secretTransformation.ts | 5 ++++ 6 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 src/utils/secretTransformation.ts diff --git a/src/errors.ts b/src/errors.ts index f73017a2..e5417957 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -3,3 +3,9 @@ export class CouldNotFindTeamCredentialsError extends Error { super('Could not find team credentials'); } } + +export class InvalidDashlanePathError extends Error { + constructor() { + super('Invalid Dashlane path'); + } +} diff --git a/src/modules/database/vaultSecrets.ts b/src/modules/database/vaultSecrets.ts index 7273f211..8d67b7a7 100644 --- a/src/modules/database/vaultSecrets.ts +++ b/src/modules/database/vaultSecrets.ts @@ -57,6 +57,13 @@ export const findVaultSecret = (vaultSecrets: VaultSecrets, parsedPath: ParsedPa vaultSecrets.notes = vaultSecrets.notes.filter((note) => note.title === parsedPath.title); } + if (parsedPath.secretId) { + vaultSecrets.credentials = vaultSecrets.credentials.filter( + (credential) => credential.id === parsedPath.secretId + ); + vaultSecrets.notes = vaultSecrets.notes.filter((note) => note.id === parsedPath.secretId); + } + if (vaultSecrets.credentials.length === 0 && vaultSecrets.notes.length === 0) { throw new Error(`No matching secret found for "${parsedPath.secretId ?? parsedPath.title ?? ''}"`); } @@ -72,7 +79,12 @@ export const findVaultSecret = (vaultSecrets: VaultSecrets, parsedPath: ParsedPa }"` ); } - return String(secretToRender[parsedPath.field]); + + const fieldContent = String(secretToRender[parsedPath.field]); + if (parsedPath.transformation) { + return parsedPath.transformation(fieldContent); + } + return fieldContent; } return JSON.stringify(secretToRender); diff --git a/src/types.ts b/src/types.ts index 6a916f69..ca4030e3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -220,4 +220,5 @@ export interface ParsedPath { secretId?: string; title?: string; field?: string; + transformation?: (field: string) => string; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 5ad60beb..5085ba29 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,5 +3,6 @@ export * from './dialogs'; export * from './gotImplementation'; export * from './secretParser'; export * from './secretPath'; +export * from './secretTransformation'; export * from './strings'; export * from './teamDeviceCredentials'; diff --git a/src/utils/secretPath.ts b/src/utils/secretPath.ts index f0f84a69..f7035810 100644 --- a/src/utils/secretPath.ts +++ b/src/utils/secretPath.ts @@ -1,25 +1,33 @@ import { isUuid } from './strings'; +import { transformOtp } from './secretTransformation'; import { ParsedPath } from '../types'; +import { InvalidDashlanePathError } from '../errors'; /** * Function to parse a custom Dashlane path and return the query parameters for the vault lookup * First we check if the path is a valid Dashlane path, should start with dl:// * Then we check if the path is a valid Dashlane vault id, should be a 32 character UUID string (ie: 11111111-1111-1111-1111-111111111111) * Otherwise, we assume the path is a valid Dashlane title - * Finally, we check if the next chunk of the path is a valid Dashlane field + * Next, we check if the next chunk of the path is a valid Dashlane field + * Finally, we check if there is a '?key' or '?key=value' query string at the end of the path * @param path */ export const parsePath = (path: string): ParsedPath => { if (!path.startsWith('dl://')) { - throw new Error('Invalid Dashlane path'); + throw new InvalidDashlanePathError(); } - const cleanPath = path.slice(5); + const queryParams = path.split('?'); + if (queryParams.length > 2) { + throw new InvalidDashlanePathError(); + } + + const cleanPath = path.slice(5, path.length - (queryParams.length === 2 ? queryParams[1].length + 1 : 0)); const pathChunks = cleanPath.split('/'); if (pathChunks.length > 2) { - throw new Error('Invalid Dashlane path'); + throw new InvalidDashlanePathError(); } let secretId = undefined; @@ -36,5 +44,25 @@ export const parsePath = (path: string): ParsedPath => { field = pathChunks[1]; } - return { secretId, title, field }; + let transformation = undefined; + + if (queryParams.length === 2) { + const queryParamChunks = queryParams[1].split('='); + if (queryParamChunks.length > 2) { + throw new InvalidDashlanePathError(); + } + + const queryParamKey = queryParamChunks[0]; + // const queryParamValue = queryParamChunks[1]; + + switch (queryParamKey) { + case 'otp': + transformation = transformOtp; + break; + default: + throw new InvalidDashlanePathError(); + } + } + + return { secretId, title, field, transformation }; }; diff --git a/src/utils/secretTransformation.ts b/src/utils/secretTransformation.ts new file mode 100644 index 00000000..bbd8b03c --- /dev/null +++ b/src/utils/secretTransformation.ts @@ -0,0 +1,5 @@ +import { authenticator } from 'otplib'; + +export const transformOtp = (secret: string) => { + return authenticator.generate(secret); +};