1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 22:13:32 +00:00

flesh out metadata organization

This commit is contained in:
✨ Audrey ✨
2024-12-16 15:29:01 -05:00
parent cfd5dcd830
commit 45a24a2309
14 changed files with 590 additions and 161 deletions

View File

@@ -0,0 +1,49 @@
import { CredentialAlgorithm, CredentialType } from "./type";
/** Credential generator metadata common across credential generators */
export type AlgorithmMetadata = {
/** Uniquely identifies the credential configuration
* @example
* // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)`
* // to pattern test whether the credential describes a forwarder algorithm
* const meta : AlgorithmMetadata = // ...
* const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {};
*/
id: CredentialAlgorithm;
/** The kind of credential generated by this configuration */
category: CredentialType;
/** Localization keys */
i18nKeys: {
/** descriptive name of the algorithm */
name: string;
/** explanatory text for the algorithm */
description?: string;
/** labels the generate action */
generateCredential: string;
/** labels the generated output */
credentialGenerated: string;
/** labels the copy output action */
copyCredential: string;
};
/** fine-tunings for generator user experiences */
capabilities: {
/** `true` when the generator supports autogeneration
* @remarks this property is useful when credential generation
* carries side effects, such as configuring a service external
* to Bitwarden.
*/
autogenerate: boolean;
/** Well-known fields to display on the options panel or collect from the environment.
* @remarks: at present, this is only used by forwarders
*/
fields: string[];
};
};

View File

@@ -1,5 +1,6 @@
import { deepFreeze } from "../util";
/** algorithms for generating credentials */
export const Algorithm = Object.freeze({
/** A password composed of random characters */
password: "password",
@@ -14,26 +15,37 @@ export const Algorithm = Object.freeze({
catchall: "catchall",
/** An email username composed of words from the EFF word list */
plusAddress: "plus-address",
plusAddress: "subaddress",
/** An integrated email forwarding service */
forwarder: "forwarder",
} as const);
export const Category = Object.freeze({
/** categorizes credentials according to their use-case outside of Bitwarden */
export const Type = Object.freeze({
password: "password",
username: "username",
email: "email",
} as const);
/** Credential generation algorithm identifiers grouped by category. */
export const CategorizedAlgorithm = deepFreeze({
/** Lists algorithms in the "password" credential category */
[Category.password]: [Algorithm.password, Algorithm.passphrase] as const,
/** categorizes settings according to their expected use-case within Bitwarden */
export const Purpose = Object.freeze({
/** account-level generator options. This is the default.
* @remarks these are the options displayed on the generator tab
*/
account: "account",
/** Lists algorithms in the "username" credential category */
[Category.username]: [Algorithm.username] as const,
// FIXME: consider adding a purpose for bitwarden's master password
});
/** Lists algorithms in the "email" credential category */
[Category.email]: [Algorithm.catchall, Algorithm.plusAddress, Algorithm.forwarder] as const,
/** Credential generation algorithms grouped by purpose. */
export const AlgorithmsByType = deepFreeze({
/** Algorithms that produce passwords */
[Type.password]: [Algorithm.password, Algorithm.passphrase] as const,
/** Algorithms that produce usernames */
[Type.username]: [Algorithm.username] as const,
/** Algorithms that produce email addresses */
[Type.email]: [Algorithm.catchall, Algorithm.plusAddress, Algorithm.forwarder] as const,
} as const);

View File

@@ -0,0 +1,76 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { EmailRandomizer } from "../../engine";
import { CatchallConstraints } from "../../policies/catchall-constraints";
import {
CatchallGenerationOptions,
CredentialGenerator,
GeneratorDependencyProvider,
NoPolicy,
} from "../../types";
import { deepFreeze } from "../../util";
import { Algorithm, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const catchall: GeneratorMetadata<CatchallGenerationOptions, NoPolicy> = deepFreeze({
id: Algorithm.catchall,
category: Type.email,
i18nKeys: {
name: "catchallEmail",
description: "catchallEmailDesc",
generateCredential: "generateEmail",
credentialGenerated: "email",
copyCredential: "copyEmail",
},
capabilities: {
autogenerate: true,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
): CredentialGenerator<CatchallGenerationOptions> {
return new EmailRandomizer(dependencies.randomizer);
},
},
options: {
constraints: { catchallDomain: { minLength: 1 } },
account: {
storage: {
key: "catchallGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<CatchallGenerationOptions>([
"catchallType",
"catchallDomain",
]),
state: GENERATOR_DISK,
initial: {
catchallType: "random",
catchallDomain: "",
},
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {},
},
},
},
policy: {
combine(_acc: NoPolicy, _policy: Policy) {
return {};
},
toConstraints(_policy: NoPolicy, email: string) {
return new CatchallConstraints(email);
},
},
});
export default catchall;

View File

@@ -0,0 +1,59 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
import { ForwarderConfiguration } from "../../engine";
import { Forwarder } from "../../engine/forwarder";
import { GeneratorDependencyProvider, NoPolicy } from "../../types";
import { deepFreeze } from "../../util";
import { Purpose, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
import { toForwarderIntegration } from "../util";
export function toGeneratorMetadata<Settings extends ApiSettings = ApiSettings>(
configuration: ForwarderConfiguration<Settings>,
): GeneratorMetadata<Settings, NoPolicy> {
const forwarder = deepFreeze({
id: toForwarderIntegration(configuration),
category: Type.email,
i18nKeys: {
name: configuration.name,
description: "forwardedEmailDesc",
generateCredential: "generateEmail",
credentialGenerated: "email",
copyCredential: "copyEmail",
},
capabilities: {
autogenerate: false,
fields: configuration.forwarder.request as string[],
},
engine: {
create(dependencies: GeneratorDependencyProvider) {
// FIXME: figure out why `configuration` fails to typecheck
const config: any = configuration;
return new Forwarder(config, dependencies.client, dependencies.i18nService);
},
},
options: {
constraints: configuration.forwarder.settingsConstraints,
[Purpose.account]: {
storage: configuration.forwarder.local.settings,
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {},
},
},
},
policy: {
combine(_acc: NoPolicy, _policy: Policy) {
return {};
},
toConstraints(_policy: NoPolicy) {
return new IdentityConstraint<Settings>();
},
},
} satisfies GeneratorMetadata<Settings, NoPolicy>);
return forwarder;
}

View File

@@ -0,0 +1,79 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { EmailRandomizer } from "../../engine";
import { SubaddressConstraints } from "../../policies/subaddress-constraints";
import {
CredentialGenerator,
GeneratorDependencyProvider,
NoPolicy,
SubaddressGenerationOptions,
} from "../../types";
import { deepFreeze } from "../../util";
import { Algorithm, Purpose, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const plusAddress: GeneratorMetadata<SubaddressGenerationOptions, NoPolicy> = deepFreeze({
id: Algorithm.plusAddress,
category: Type.email,
i18nKeys: {
name: "plusAddressedEmail",
description: "plusAddressedEmailDesc",
generateCredential: "generateEmail",
credentialGenerated: "email",
copyCredential: "copyEmail",
},
capabilities: {
autogenerate: true,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
): CredentialGenerator<SubaddressGenerationOptions> {
return new EmailRandomizer(dependencies.randomizer);
},
},
options: {
constraints: {},
[Purpose.account]: {
storage: {
key: "subaddressGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<SubaddressGenerationOptions>([
"subaddressType",
"subaddressEmail",
]),
state: GENERATOR_DISK,
initial: {
subaddressType: "random",
subaddressEmail: "",
},
options: {
deserializer(value) {
return value;
},
clearOn: ["logout"],
},
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {},
},
},
},
policy: {
combine(_acc: NoPolicy, _policy: Policy) {
return {};
},
toConstraints(_policy: NoPolicy, email: string) {
return new SubaddressConstraints(email);
},
},
});
export default plusAddress;

View File

@@ -0,0 +1,67 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy as AdminPolicy } from "@bitwarden/common/admin-console/models/domain/policy";
import { ObjectKey } from "@bitwarden/common/tools/state/object-key";
import { Constraints } from "@bitwarden/common/tools/types";
import { CredentialGenerator, GeneratorConstraints, GeneratorDependencyProvider } from "../types";
import { AlgorithmMetadata } from "./algorithm-metadata";
import { Purpose } from "./data";
/** Extends the algorithm metadata with storage and engine configurations.
* @example
* // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)`
* // to pattern test whether the credential describes a forwarder algorithm
* const meta : CredentialGeneratorInfo = // ...
* const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {};
*/
export type GeneratorMetadata<Options, Policy> = AlgorithmMetadata & {
/** An algorithm that generates credentials when ran. */
engine: {
/** Factory for the generator
*/
create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator<Options>;
};
/** Defines parameters for credential generation */
options: {
/** global constraints; these apply to *all* generators */
constraints: Constraints<Options>;
/** account-local generator options */
[Purpose.account]: {
/** plaintext import buffer */
import?: ObjectKey<Options, Record<string, never>, Options> & { format: "plain" };
/** persistent storage location */
storage: ObjectKey<Options>;
/** policy enforced when saving the options */
policy: {
/** policy administration storage location for the policy */
type: PolicyType;
/** The value of the policy when it is not in effect. */
disabledValue: Policy;
};
};
};
policy: {
/** Combines multiple policies set by the administrative console into
* a single policy.
*/
combine: (acc: Policy, policy: AdminPolicy) => Policy;
/** Converts policy service data into actionable policy constraints.
*
* @param policy - the policy to map into policy constraints.
* @param email - the default email to extend.
*
* @remarks this version includes constraints needed for the reactive forms;
* it was introduced so that the constraints can be incrementally introduced
* as the new UI is built.
*/
toConstraints: (policy: Policy, email: string) => GeneratorConstraints<Options>;
};
};

View File

@@ -4,32 +4,29 @@ import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { ObjectKey } from "@bitwarden/common/tools/state/object-key";
import { PasswordRandomizer } from "../../engine";
import {
PassphraseGeneratorOptionsEvaluator,
passphraseLeastPrivilege,
PassphrasePolicyConstraints,
} from "../../policies";
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies";
import {
CredentialGenerator,
CredentialGeneratorConfiguration,
GeneratorDependencyProvider,
PassphraseGenerationOptions,
PassphraseGeneratorPolicy,
} from "../../types";
import { Algorithm, Category } from "../data";
import { Algorithm, Purpose, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const passphrase: CredentialGeneratorConfiguration<
PassphraseGenerationOptions,
PassphraseGeneratorPolicy
> = {
const passphrase: GeneratorMetadata<PassphraseGenerationOptions, PassphraseGeneratorPolicy> = {
id: Algorithm.passphrase,
category: Category.password,
nameKey: "passphrase",
generateKey: "generatePassphrase",
generatedValueKey: "passphrase",
copyKey: "copyPassphrase",
onlyOnRequest: false,
request: [],
category: Type.password,
i18nKeys: {
name: "passphrase",
generateCredential: "generatePassphrase",
credentialGenerated: "passphrase",
copyCredential: "copyPassphrase",
},
capabilities: {
autogenerate: false,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
@@ -37,13 +34,7 @@ const passphrase: CredentialGeneratorConfiguration<
return new PasswordRandomizer(dependencies.randomizer);
},
},
settings: {
initial: {
numWords: 6,
wordSeparator: "-",
capitalize: false,
includeNumber: false,
},
options: {
constraints: {
numWords: {
min: 3,
@@ -52,44 +43,45 @@ const passphrase: CredentialGeneratorConfiguration<
},
wordSeparator: { maxLength: 1 },
},
account: {
key: "passphraseGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PassphraseGenerationOptions>([
"numWords",
"wordSeparator",
"capitalize",
"includeNumber",
]),
state: GENERATOR_DISK,
initial: {
numWords: 6,
wordSeparator: "-",
capitalize: false,
includeNumber: false,
},
options: {
deserializer(value) {
return value;
[Purpose.account]: {
storage: {
key: "passphraseGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PassphraseGenerationOptions>([
"numWords",
"wordSeparator",
"capitalize",
"includeNumber",
]),
state: GENERATOR_DISK,
initial: {
numWords: 6,
wordSeparator: "-",
capitalize: false,
includeNumber: false,
},
options: {
deserializer(value) {
return value;
},
clearOn: ["logout"],
},
} satisfies ObjectKey<PassphraseGenerationOptions>,
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {
minNumberWords: 0,
capitalize: false,
includeNumber: false,
},
clearOn: ["logout"],
},
} satisfies ObjectKey<PassphraseGenerationOptions>,
},
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {
minNumberWords: 0,
capitalize: false,
includeNumber: false,
},
combine: passphraseLeastPrivilege,
createEvaluator(policy) {
return new PassphraseGeneratorOptionsEvaluator(policy);
},
toConstraints(policy) {
return new PassphrasePolicyConstraints(policy, passphrase.settings.constraints);
return new PassphrasePolicyConstraints(policy, passphrase.options.constraints);
},
},
};

View File

@@ -1,36 +1,32 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { ObjectKey } from "@bitwarden/common/tools/state/object-key";
import { PasswordRandomizer } from "../../engine";
import {
DynamicPasswordPolicyConstraints,
PasswordGeneratorOptionsEvaluator,
passwordLeastPrivilege,
} from "../../policies";
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
import {
CredentialGenerator,
CredentialGeneratorConfiguration,
GeneratorDependencyProvider,
PasswordGenerationOptions,
PasswordGeneratorPolicy,
} from "../../types";
import { deepFreeze } from "../../util";
import { Algorithm, Category } from "../data";
import { Algorithm, Purpose, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const password: CredentialGeneratorConfiguration<
PasswordGenerationOptions,
PasswordGeneratorPolicy
> = deepFreeze({
const password: GeneratorMetadata<PasswordGenerationOptions, PasswordGeneratorPolicy> = deepFreeze({
id: Algorithm.password,
category: Category.password,
nameKey: "password",
generateKey: "generatePassword",
generatedValueKey: "password",
copyKey: "copyPassword",
onlyOnRequest: false,
request: [],
category: Type.password,
i18nKeys: {
name: "password",
generateCredential: "generatePassword",
credentialGenerated: "password",
copyCredential: "copyPassword",
},
capabilities: {
autogenerate: true,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
@@ -38,19 +34,7 @@ const password: CredentialGeneratorConfiguration<
return new PasswordRandomizer(dependencies.randomizer);
},
},
settings: {
initial: {
length: 14,
ambiguous: true,
uppercase: true,
minUppercase: 1,
lowercase: true,
minLowercase: 1,
number: true,
minNumber: 1,
special: false,
minSpecial: 0,
},
options: {
constraints: {
length: {
min: 5,
@@ -66,60 +50,61 @@ const password: CredentialGeneratorConfiguration<
max: 9,
},
},
account: {
key: "passwordGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PasswordGenerationOptions>([
"length",
"ambiguous",
"uppercase",
"minUppercase",
"lowercase",
"minLowercase",
"number",
"minNumber",
"special",
"minSpecial",
]),
state: GENERATOR_DISK,
initial: {
length: 14,
ambiguous: true,
uppercase: true,
minUppercase: 1,
lowercase: true,
minLowercase: 1,
number: true,
minNumber: 1,
special: false,
minSpecial: 0,
},
options: {
deserializer(value) {
return value;
[Purpose.account]: {
storage: {
key: "passwordGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PasswordGenerationOptions>([
"length",
"ambiguous",
"uppercase",
"minUppercase",
"lowercase",
"minLowercase",
"number",
"minNumber",
"special",
"minSpecial",
]),
state: GENERATOR_DISK,
initial: {
length: 14,
ambiguous: true,
uppercase: true,
minUppercase: 1,
lowercase: true,
minLowercase: 1,
number: true,
minNumber: 1,
special: false,
minSpecial: 0,
},
options: {
deserializer(value) {
return value;
},
clearOn: ["logout"],
},
clearOn: ["logout"],
},
} satisfies ObjectKey<PasswordGenerationOptions>,
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {
minLength: 0,
useUppercase: false,
useLowercase: false,
useNumbers: false,
numberCount: 0,
useSpecial: false,
specialCount: 0,
},
},
},
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {
minLength: 0,
useUppercase: false,
useLowercase: false,
useNumbers: false,
numberCount: 0,
useSpecial: false,
specialCount: 0,
},
combine: passwordLeastPrivilege,
createEvaluator(policy) {
return new PasswordGeneratorOptionsEvaluator(policy);
},
toConstraints(policy) {
return new DynamicPasswordPolicyConstraints(policy, password.settings.constraints);
return new DynamicPasswordPolicyConstraints(policy, password.options.constraints);
},
},
});

View File

@@ -1,25 +1,28 @@
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { CategorizedAlgorithm, Category } from "./data";
import { AlgorithmsByType, Purpose, Type } from "./data";
/** A collection of credentials that all fulfill a specific purpose. */
export type CredentialCategory = keyof typeof Category;
/** categorizes credentials according to their use-case outside of Bitwarden */
export type CredentialType = keyof typeof Type;
/** categorizes credentials according to their expected use-case within Bitwarden */
export type CredentialPurpose = keyof typeof Purpose;
/** A type of password that may be generated by the credential generator. */
export type PasswordAlgorithm = (typeof CategorizedAlgorithm.password)[number];
export type PasswordAlgorithm = (typeof AlgorithmsByType.password)[number];
/** A type of username that may be generated by the credential generator. */
export type UsernameAlgorithm = (typeof CategorizedAlgorithm.username)[number];
export type UsernameAlgorithm = (typeof AlgorithmsByType.username)[number];
/** A type of email address that may be generated by the credential generator. */
export type EmailAlgorithm = (typeof CategorizedAlgorithm.email)[number] | ForwarderIntegration;
export type EmailAlgorithm = (typeof AlgorithmsByType.email)[number] | ForwarderIntegration;
/** Identifies a forwarding service */
export type ForwarderIntegration = { forwarder: IntegrationId };
/** A type of credential that may be generated by the credential generator. */
// this is defined in terms of `CategorizedAlgorithm` to typecheck the keys of
// `CredentialCategory` against the keys of `Category`.
/** A type of credential that can be generated by the credential generator. */
// this is defined in terms of `AlgorithmsByType` to typecheck the keys of
// `AlgorithmsByType` against the keys of `CredentialType`.
export type CredentialAlgorithm =
| (typeof CategorizedAlgorithm)[CredentialCategory][number]
| (typeof AlgorithmsByType)[CredentialType][number]
| ForwarderIntegration;

View File

@@ -0,0 +1,76 @@
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { GENERATOR_DISK } from "@bitwarden/common/platform/state";
import { PublicClassifier } from "@bitwarden/common/tools/public-classifier";
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
import { UsernameRandomizer } from "../../engine";
import {
CredentialGenerator,
EffUsernameGenerationOptions,
GeneratorDependencyProvider,
NoPolicy,
} from "../../types";
import { deepFreeze } from "../../util";
import { Algorithm, Purpose, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const effWordList: GeneratorMetadata<EffUsernameGenerationOptions, NoPolicy> = deepFreeze({
id: Algorithm.username,
category: Type.username,
i18nKeys: {
name: "randomWord",
generateCredential: "generateUsername",
credentialGenerated: "username",
copyCredential: "copyUsername",
},
capabilities: {
autogenerate: true,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
): CredentialGenerator<EffUsernameGenerationOptions> {
return new UsernameRandomizer(dependencies.randomizer);
},
},
options: {
constraints: {},
[Purpose.account]: {
storage: {
key: "effUsernameGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<EffUsernameGenerationOptions>([
"wordCapitalize",
"wordIncludeNumber",
]),
state: GENERATOR_DISK,
initial: {
wordCapitalize: false,
wordIncludeNumber: false,
website: null,
},
options: {
deserializer: (value) => value,
clearOn: ["logout"],
},
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {},
},
},
},
policy: {
combine(_acc: NoPolicy, _policy: Policy) {
return {};
},
toConstraints(_policy: NoPolicy) {
return new IdentityConstraint<EffUsernameGenerationOptions>();
},
},
});
export default effWordList;

View File

@@ -1,4 +1,10 @@
import { CategorizedAlgorithm } from "./data";
import {
IntegrationId,
IntegrationIds,
IntegrationMetadata,
} from "@bitwarden/common/tools/integration";
import { AlgorithmsByType } from "./data";
import {
CredentialAlgorithm,
EmailAlgorithm,
@@ -11,14 +17,14 @@ import {
export function isPasswordAlgorithm(
algorithm: CredentialAlgorithm,
): algorithm is PasswordAlgorithm {
return CategorizedAlgorithm.password.includes(algorithm as any);
return AlgorithmsByType.password.includes(algorithm as any);
}
/** Returns true when the input algorithm is a username algorithm. */
export function isUsernameAlgorithm(
algorithm: CredentialAlgorithm,
): algorithm is UsernameAlgorithm {
return CategorizedAlgorithm.username.includes(algorithm as any);
return AlgorithmsByType.username.includes(algorithm as any);
}
/** Returns true when the input algorithm is a forwarder integration. */
@@ -30,7 +36,7 @@ export function isForwarderIntegration(
/** Returns true when the input algorithm is an email algorithm. */
export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm {
return CategorizedAlgorithm.email.includes(algorithm as any) || isForwarderIntegration(algorithm);
return AlgorithmsByType.email.includes(algorithm as any) || isForwarderIntegration(algorithm);
}
export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) {
@@ -42,3 +48,28 @@ export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorit
return false;
}
}
export function toForwarderIntegration(value: IntegrationMetadata): ForwarderIntegration;
export function toForwarderIntegration(value: IntegrationId): ForwarderIntegration;
export function toForwarderIntegration(
value: IntegrationId | IntegrationMetadata,
): ForwarderIntegration {
if (value == null) {
throw new Error("`value` cannot be `null` or `undefined`");
}
let possibleId = undefined;
if (typeof value === "string") {
possibleId = value;
} else if (typeof value === "object" && "id" in value) {
possibleId = typeof value.id === "string" ? value.id : undefined;
} else {
throw new Error("Invalid `value` received.");
}
if (possibleId && IntegrationIds.includes(possibleId)) {
return { forwarder: possibleId } satisfies ForwarderIntegration;
} else {
throw new Error("Invalid `value` received.");
}
}