1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-15 07:54:55 +00:00

Hook up sdk generator engines to metadata

This commit is contained in:
adudek-bw
2025-05-06 13:08:27 -04:00
parent 382528de63
commit 360de4640c
6 changed files with 317 additions and 1 deletions

View File

@@ -5,4 +5,5 @@ export * from "./settings";
export { EmailRandomizer } from "./email-randomizer";
export { EmailCalculator } from "./email-calculator";
export { PasswordRandomizer } from "./password-randomizer";
export { SdkPasswordRandomizer } from "./sdk-password-randomizer";
export { UsernameRandomizer } from "./username-randomizer";

View File

@@ -0,0 +1,98 @@
import {
BitwardenClient,
PassphraseGeneratorRequest,
PasswordGeneratorRequest,
} from "@bitwarden/sdk-internal";
import {
CredentialGenerator,
GenerateRequest,
GeneratedCredential,
PassphraseGenerationOptions,
PasswordGenerationOptions,
} from "../types";
/** Generation algorithms that produce randomized secrets */
export class SdkPasswordRandomizer
implements
CredentialGenerator<PassphraseGenerationOptions>,
CredentialGenerator<PasswordGenerationOptions>
{
/** Instantiates the password randomizer
* @param randomizer data source for random data
*/
constructor(private client: BitwardenClient) {}
generate(
request: GenerateRequest,
settings: PasswordGenerationOptions,
): Promise<GeneratedCredential>;
generate(
request: GenerateRequest,
settings: PassphraseGenerationOptions,
): Promise<GeneratedCredential>;
async generate(
request: GenerateRequest,
settings: PasswordGenerationOptions | PassphraseGenerationOptions,
) {
if (isPasswordGenerationOptions(settings)) {
const password = await this.client.generator().password(convertPasswordRequest(settings));
return new GeneratedCredential(
password,
"password",
Date.now(),
request.source,
request.website,
);
} else if (isPassphraseGenerationOptions(settings)) {
const passphrase = await this.client
.generator()
.passphrase(convertPassphraseRequest(settings));
return new GeneratedCredential(
passphrase,
"password",
Date.now(),
request.source,
request.website,
);
}
throw new Error("Invalid settings received by generator.");
}
}
function convertPasswordRequest(settings: PasswordGenerationOptions): PasswordGeneratorRequest {
return {
lowercase: settings.lowercase,
uppercase: settings.uppercase,
numbers: settings.number,
special: settings.special,
length: settings.length,
avoidAmbiguous: settings.ambiguous,
minLowercase: settings.minLowercase,
minUppercase: settings.minUppercase,
minNumber: settings.minNumber,
minSpecial: settings.minSpecial,
};
}
function convertPassphraseRequest(
settings: PassphraseGenerationOptions,
): PassphraseGeneratorRequest {
return {
numWords: settings.numWords,
wordSeparator: settings.wordSeparator,
capitalize: settings.capitalize,
includeNumber: settings.includeNumber,
};
}
function isPasswordGenerationOptions(settings: any): settings is PasswordGenerationOptions {
return "length" in (settings ?? {});
}
function isPassphraseGenerationOptions(settings: any): settings is PassphraseGenerationOptions {
return "numWords" in (settings ?? {});
}

View File

@@ -8,6 +8,12 @@ export const Algorithm = Object.freeze({
/** A password composed of random words from the EFF word list */
passphrase: "passphrase",
/** A password composed of random characters, retrieved from SDK */
sdkPassword: "sdkpassword",
/** A password composed of random words from the EFF word list, retrieved from SDK */
sdkPassphrase: "sdkpassphrase",
/** A username composed of words from the EFF word list */
username: "username",
@@ -38,7 +44,12 @@ export const Profile = Object.freeze({
/** Credential generation algorithms grouped by purpose. */
export const AlgorithmsByType = deepFreeze({
/** Algorithms that produce passwords */
[Type.password]: [Algorithm.password, Algorithm.passphrase] as const,
[Type.password]: [
Algorithm.password,
Algorithm.passphrase,
Algorithm.sdkPassword,
Algorithm.sdkPassphrase,
] as const,
/** Algorithms that produce usernames */
[Type.username]: [Algorithm.username] as const,

View File

@@ -0,0 +1,89 @@
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 { BitwardenClient } from "@bitwarden/sdk-internal";
import { SdkPasswordRandomizer } from "../../engine";
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies";
import { GeneratorDependencyProvider } from "../../providers";
import { CredentialGenerator, PassphraseGenerationOptions } from "../../types";
import { Algorithm, Profile, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const sdkPassphrase: GeneratorMetadata<PassphraseGenerationOptions> = {
id: Algorithm.sdkPassphrase,
type: Type.password,
weight: 130,
i18nKeys: {
name: "passphrase",
credentialType: "passphrase",
generateCredential: "generatePassphrase",
credentialGenerated: "passphraseGenerated",
copyCredential: "copyPassphrase",
useCredential: "useThisPassphrase",
},
capabilities: {
autogenerate: false,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
): CredentialGenerator<PassphraseGenerationOptions> {
return new SdkPasswordRandomizer(new BitwardenClient()); // @TODO hook up a real SDK client
},
},
profiles: {
[Profile.account]: {
type: "core",
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>,
constraints: {
type: PolicyType.PasswordGenerator,
default: {
wordSeparator: { maxLength: 1 },
numWords: {
min: 3,
max: 20,
recommendation: 6,
},
},
create(policies, context) {
const initial = {
minNumberWords: 0,
capitalize: false,
includeNumber: false,
};
const policy = policies.reduce(passphraseLeastPrivilege, initial);
const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints);
return constraints;
},
},
},
},
};
export default sdkPassphrase;

View File

@@ -0,0 +1,115 @@
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 { deepFreeze } from "@bitwarden/common/tools/util";
import { BitwardenClient } from "@bitwarden/sdk-internal";
import { SdkPasswordRandomizer } from "../../engine";
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
import { GeneratorDependencyProvider } from "../../providers";
import { CredentialGenerator, PasswordGeneratorSettings } from "../../types";
import { Algorithm, Profile, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const sdkPassword: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({
id: Algorithm.sdkPassword,
type: Type.password,
weight: 120,
i18nKeys: {
name: "password",
generateCredential: "generatePassword",
credentialGenerated: "passwordGenerated",
credentialType: "password",
copyCredential: "copyPassword",
useCredential: "useThisPassword",
},
capabilities: {
autogenerate: true,
fields: [],
},
engine: {
create(
dependencies: GeneratorDependencyProvider,
): CredentialGenerator<PasswordGeneratorSettings> {
return new SdkPasswordRandomizer(new BitwardenClient()); // @TODO hook up a real SDK client
},
},
profiles: {
[Profile.account]: {
type: "core",
storage: {
key: "passwordGeneratorSettings",
target: "object",
format: "plain",
classifier: new PublicClassifier<PasswordGeneratorSettings>([
"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"],
},
},
constraints: {
type: PolicyType.PasswordGenerator,
default: {
length: {
min: 5,
max: 128,
recommendation: 14,
},
minNumber: {
min: 0,
max: 9,
},
minSpecial: {
min: 0,
max: 9,
},
},
create(policies, context) {
const initial = {
minLength: 0,
useUppercase: false,
useLowercase: false,
useNumbers: false,
numberCount: 0,
useSpecial: false,
specialCount: 0,
};
const policy = policies.reduce(passwordLeastPrivilege, initial);
const constraints = new DynamicPasswordPolicyConstraints(
policy,
context.defaultConstraints,
);
return constraints;
},
},
},
},
});
export default sdkPassword;

View File

@@ -1,4 +1,5 @@
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SdkService } from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
import { Randomizer } from "../abstractions";
@@ -9,4 +10,5 @@ export type GeneratorDependencyProvider = {
// FIXME: introduce `I18nKeyOrLiteral` into forwarder
// structures and remove this dependency
i18nService: I18nService;
sdkService: SdkService;
};