diff --git a/core/src/util/secrets.ts b/core/src/util/secrets.ts index 991a8b223c..5639a374e8 100644 --- a/core/src/util/secrets.ts +++ b/core/src/util/secrets.ts @@ -15,47 +15,18 @@ export function isSecret(s: unknown): s is Secret { return s !== null && typeof s === "object" && s["isSecretString"] === true } -export type UnwrapSecret = - T extends Record - ? Record> - : T extends Array - ? UnwrapSecret - : T extends MaybeSecret - ? string - : T - -type OptionalMaybeSecret = MaybeSecret | undefined -export type DeepMaybeSecret = OptionalMaybeSecret | OptionalMaybeSecret[] | { [key: string]: OptionalMaybeSecret } - -export function toClearText(s: T): UnwrapSecret { - if (isSecret(s)) { - return s.unwrapSecretValue() as UnwrapSecret - } - - // lodash isPlainObject implementation causes a type error - if (!!s && typeof s === "object" && s.constructor === Object) { - return Object.fromEntries(Object.entries(s).map(([k, v]) => [k, toClearText(v)])) as UnwrapSecret - } - - if (isArray(s)) { - return s.map(toClearText) as UnwrapSecret - } - - // it's a string or another type that doesn't need to be unwrapped - return s as UnwrapSecret -} - +/** + * Create an instance of Secret + * @example + * + * const secret = makeSecret("foo") + * console.log(secret) // => *** + * toClearText(secret) // => foo + */ export function makeSecret(s: string): Secret { return new SecretValue(s) } -export function transformSecret(s: T, transformFn: (s: string) => string): T { - if (isSecret(s)) { - return s.transformSecretValue(transformFn) as T - } - return transformFn(s) as T -} - export interface Secret { /** * Redacts secrets with three asterisks (***) @@ -64,7 +35,7 @@ export interface Secret { /** * Gives access to the clear text. - * Use toClearText if you are dealing with MaybeSecret values. + * Use {@link toClearText} if you are dealing with {@link MaybeSecret} values. */ unwrapSecretValue(): string @@ -77,13 +48,9 @@ export interface Secret { export type MaybeSecret = string | Secret /** - * To be used as tagged string. - * - * If none of the template expressions evaluate to a secret value, calling .toString() - * will redact secrets to prevent accidentally leaking them e.g. in logs. + * To be used as tagged string, to concatenate secret and non-secret strings, protecting the secrets from leaking them accidentally. * - * Otherwise it will return a special instance of Secret that only blanks out the secrets - * and leaves the rest as clear text; if we blank out too much then it will be harder to make sense of logs. + * Returns a {@link Secret} if any of the template expressions evaluate to a secret; Otherwise returns string. * * @example * @@ -93,7 +60,7 @@ export type MaybeSecret = string | Secret * console.log(secretBanana) // => MY_ENV_VAR=*** * console.log(regularBanana) // => MY_ENV_VAR=banana * - * console.log(secretBanana.unwrapSecretValue()) // => MY_ENV_VAR=banana + * console.log(toClearText(secretBanana)) // => MY_ENV_VAR=banana */ export function maybeSecret( nonSecrets: ReadonlyArray, @@ -125,6 +92,43 @@ export function joinSecrets(s: ReadonlyArray, separator: string): M return result || "" } +type UnwrapSecret = + T extends Record + ? Record> + : T extends Array + ? UnwrapSecret + : T extends MaybeSecret + ? string + : T + +type OptionalMaybeSecret = MaybeSecret | undefined +type DeepOptionalMaybeSecret = OptionalMaybeSecret | OptionalMaybeSecret[] | { [key: string]: OptionalMaybeSecret } + +export function toClearText(s: T): UnwrapSecret { + if (isSecret(s)) { + return s.unwrapSecretValue() as UnwrapSecret + } + + // lodash isPlainObject implementation causes a type error + if (!!s && typeof s === "object" && s.constructor === Object) { + return Object.fromEntries(Object.entries(s).map(([k, v]) => [k, toClearText(v)])) as UnwrapSecret + } + + if (isArray(s)) { + return s.map(toClearText) as UnwrapSecret + } + + // it's a string or another type that doesn't need to be unwrapped + return s as UnwrapSecret +} + +export function transformSecret(s: T, transformFn: (s: string) => string): T { + if (isSecret(s)) { + return s.transformSecretValue(transformFn) as T + } + return transformFn(s) as T +} + /////////// Private implementation details abstract class BaseSecret implements Secret {