1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 05:30:01 +00:00

Add new metadata, feature flag, and engine for sdk generator

This commit is contained in:
adudek-bw
2025-03-19 13:57:01 -04:00
parent 30057fea33
commit 1d2fc11669
6 changed files with 310 additions and 1 deletions

View File

@@ -27,6 +27,7 @@ export enum FeatureFlag {
EnableRiskInsightsNotifications = "enable-risk-insights-notifications",
DesktopSendUIRefresh = "desktop-send-ui-refresh",
ExportAttachments = "export-attachments",
SDKGenerators = "sdk-generators",
/* Vault */
PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form",
@@ -83,6 +84,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.EnableRiskInsightsNotifications]: FALSE,
[FeatureFlag.DesktopSendUIRefresh]: FALSE,
[FeatureFlag.ExportAttachments]: FALSE,
[FeatureFlag.SDKGenerators]: FALSE,
/* Vault */
[FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE,

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,87 @@
import {
CredentialGenerator,
GenerateRequest,
GeneratedCredential,
PassphraseGenerationOptions,
PasswordGenerationOptions,
} from "../types";
import { optionsToEffWordListRequest, optionsToRandomAsciiRequest } from "../util";
import { EffWordListRequest, RandomAsciiRequest } from "./types";
/** Generation algorithms that produce randomized secrets */
export class SDKPasswordRandomizer
implements
CredentialGenerator<PassphraseGenerationOptions>,
CredentialGenerator<PasswordGenerationOptions>
{
/** Instantiates the password randomizer
*/
constructor() {}
/** create a password from ASCII codepoints
* @param request refines the generated password
* @returns a promise that completes with the generated password
*/
async randomSDKAscii(request: RandomAsciiRequest) {
// TODO
return "";
}
/** create a passphrase from the EFF's "5 dice" word list
* @param request refines the generated passphrase
* @returns a promise that completes with the generated passphrase
*/
async randomSDKEffLongWords(request: EffWordListRequest) {
// TODO
return "";
}
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 req = optionsToRandomAsciiRequest(settings);
const password = await this.randomSDKAscii(req);
return new GeneratedCredential(
password,
"password",
Date.now(),
request.source,
request.website,
);
} else if (isPassphraseGenerationOptions(settings)) {
const req = optionsToEffWordListRequest(settings);
const passphrase = await this.randomSDKEffLongWords(req);
return new GeneratedCredential(
passphrase,
"passphrase",
Date.now(),
request.source,
request.website,
);
}
throw new Error("Invalid settings received by generator.");
}
}
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 generated from the SDK */
sdkPassword: "sdkpassword",
/** A password composed of random words from the EFF word list generated from the 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,91 @@
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 { SDKPasswordRandomizer } from "../../engine";
import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies";
import {
CredentialGenerator,
GeneratorDependencyProvider,
PassphraseGenerationOptions,
} from "../../types";
import { Algorithm, Profile, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const sdkPassphrase: GeneratorMetadata<PassphraseGenerationOptions> = {
id: Algorithm.sdkPassphrase,
category: 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();
},
},
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,117 @@
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 { SDKPasswordRandomizer } from "../../engine";
import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies";
import {
CredentialGenerator,
GeneratorDependencyProvider,
PasswordGeneratorSettings,
} from "../../types";
import { Algorithm, Profile, Type } from "../data";
import { GeneratorMetadata } from "../generator-metadata";
const sdkPassword: GeneratorMetadata<PasswordGeneratorSettings> = deepFreeze({
id: Algorithm.sdkPassphrase,
category: 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();
},
},
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;