1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01:00

[PM-5972] add subaddress generator strategy (#7897)

This commit is contained in:
✨ Audrey ✨ 2024-02-12 09:20:55 -05:00 committed by GitHub
parent 746bf0a474
commit c41dce8c63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 169 additions and 0 deletions

View File

@ -1,6 +1,7 @@
import { import {
ENCRYPTED_HISTORY, ENCRYPTED_HISTORY,
EFF_USERNAME_SETTINGS, EFF_USERNAME_SETTINGS,
SUBADDRESS_SETTINGS,
PASSPHRASE_SETTINGS, PASSPHRASE_SETTINGS,
PASSWORD_SETTINGS, PASSWORD_SETTINGS,
} from "./key-definitions"; } from "./key-definitions";
@ -30,6 +31,14 @@ describe("Key definitions", () => {
}); });
}); });
describe("SUBADDRESS_SETTINGS", () => {
it("should pass through deserialization", () => {
const value = {};
const result = SUBADDRESS_SETTINGS.deserializer(value);
expect(result).toBe(value);
});
});
describe("ENCRYPTED_HISTORY", () => { describe("ENCRYPTED_HISTORY", () => {
it("should pass through deserialization", () => { it("should pass through deserialization", () => {
const value = {}; const value = {};

View File

@ -4,6 +4,7 @@ import { PassphraseGenerationOptions } from "./passphrase/passphrase-generation-
import { GeneratedPasswordHistory } from "./password/generated-password-history"; import { GeneratedPasswordHistory } from "./password/generated-password-history";
import { PasswordGenerationOptions } from "./password/password-generation-options"; import { PasswordGenerationOptions } from "./password/password-generation-options";
import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options"; import { EffUsernameGenerationOptions } from "./username/eff-username-generator-options";
import { SubaddressGenerationOptions } from "./username/subaddress-generator-options";
/** plaintext password generation options */ /** plaintext password generation options */
export const PASSWORD_SETTINGS = new KeyDefinition<PasswordGenerationOptions>( export const PASSWORD_SETTINGS = new KeyDefinition<PasswordGenerationOptions>(
@ -32,6 +33,15 @@ export const EFF_USERNAME_SETTINGS = new KeyDefinition<EffUsernameGenerationOpti
}, },
); );
/** email subaddress generation options */
export const SUBADDRESS_SETTINGS = new KeyDefinition<SubaddressGenerationOptions>(
GENERATOR_DISK,
"subaddressGeneratorSettings",
{
deserializer: (value) => value,
},
);
/** encrypted password generation history */ /** encrypted password generation history */
export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>( export const ENCRYPTED_HISTORY = new KeyDefinition<GeneratedPasswordHistory>(
GENERATOR_DISK, GENERATOR_DISK,

View File

@ -1,4 +1,5 @@
export { EffUsernameGeneratorStrategy } from "./eff-username-generator-strategy"; export { EffUsernameGeneratorStrategy } from "./eff-username-generator-strategy";
export { SubaddressGeneratorStrategy } from "./subaddress-generator-strategy";
export { UsernameGeneratorOptions } from "./username-generation-options"; export { UsernameGeneratorOptions } from "./username-generation-options";
export { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction"; export { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
export { UsernameGenerationService } from "./username-generation.service"; export { UsernameGenerationService } from "./username-generation.service";

View File

@ -0,0 +1,10 @@
/** Settings supported when generating an email subaddress */
export type SubaddressGenerationOptions = {
type?: "random" | "website-name";
email?: string;
};
/** The default options for email subaddress generation. */
export const DefaultSubaddressOptions: Partial<SubaddressGenerationOptions> = Object.freeze({
type: "random",
});

View File

@ -0,0 +1,83 @@
import { mock } from "jest-mock-extended";
import { PolicyType } from "../../../admin-console/enums";
// FIXME: use index.ts imports once policy abstractions and models
// implement ADR-0002
import { Policy } from "../../../admin-console/models/domain/policy";
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
import { SUBADDRESS_SETTINGS } from "../key-definitions";
import { SubaddressGeneratorStrategy, UsernameGenerationServiceAbstraction } from ".";
describe("Email subaddress list generation strategy", () => {
describe("evaluator()", () => {
it("should throw if the policy type is incorrect", () => {
const strategy = new SubaddressGeneratorStrategy(null);
const policy = mock<Policy>({
type: PolicyType.DisableSend,
});
expect(() => strategy.evaluator(policy)).toThrow(new RegExp("Mismatched policy type\\. .+"));
});
it("should map to the policy evaluator", () => {
const strategy = new SubaddressGeneratorStrategy(null);
const policy = mock<Policy>({
type: PolicyType.PasswordGenerator,
data: {
minLength: 10,
},
});
const evaluator = strategy.evaluator(policy);
expect(evaluator).toBeInstanceOf(DefaultPolicyEvaluator);
expect(evaluator.policy).toMatchObject({});
});
});
describe("disk", () => {
it("should use password settings key", () => {
const legacy = mock<UsernameGenerationServiceAbstraction>();
const strategy = new SubaddressGeneratorStrategy(legacy);
expect(strategy.disk).toBe(SUBADDRESS_SETTINGS);
});
});
describe("cache_ms", () => {
it("should be a positive non-zero number", () => {
const legacy = mock<UsernameGenerationServiceAbstraction>();
const strategy = new SubaddressGeneratorStrategy(legacy);
expect(strategy.cache_ms).toBeGreaterThan(0);
});
});
describe("policy", () => {
it("should use password generator policy", () => {
const legacy = mock<UsernameGenerationServiceAbstraction>();
const strategy = new SubaddressGeneratorStrategy(legacy);
expect(strategy.policy).toBe(PolicyType.PasswordGenerator);
});
});
describe("generate()", () => {
it("should call the legacy service with the given options", async () => {
const legacy = mock<UsernameGenerationServiceAbstraction>();
const strategy = new SubaddressGeneratorStrategy(legacy);
const options = {
type: "website-name" as const,
email: "someone@example.com",
};
await strategy.generate(options);
expect(legacy.generateSubaddress).toHaveBeenCalledWith({
subaddressType: "website-name" as const,
subaddressEmail: "someone@example.com",
});
});
});
});

View File

@ -0,0 +1,56 @@
import { PolicyType } from "../../../admin-console/enums";
import { Policy } from "../../../admin-console/models/domain/policy";
import { GeneratorStrategy } from "../abstractions";
import { DefaultPolicyEvaluator } from "../default-policy-evaluator";
import { SUBADDRESS_SETTINGS } from "../key-definitions";
import { NoPolicy } from "../no-policy";
import { SubaddressGenerationOptions } from "./subaddress-generator-options";
import { UsernameGenerationServiceAbstraction } from "./username-generation.service.abstraction";
const ONE_MINUTE = 60 * 1000;
/** Strategy for creating an email subaddress */
export class SubaddressGeneratorStrategy
implements GeneratorStrategy<SubaddressGenerationOptions, NoPolicy>
{
/** Instantiates the generation strategy
* @param usernameService generates an email subaddress from an email address
*/
constructor(private usernameService: UsernameGenerationServiceAbstraction) {}
/** {@link GeneratorStrategy.disk} */
get disk() {
return SUBADDRESS_SETTINGS;
}
/** {@link GeneratorStrategy.policy} */
get policy() {
// Uses password generator since there aren't policies
// specific to usernames.
return PolicyType.PasswordGenerator;
}
/** {@link GeneratorStrategy.cache_ms} */
get cache_ms() {
return ONE_MINUTE;
}
/** {@link GeneratorStrategy.evaluator} */
evaluator(policy: Policy) {
if (policy.type !== this.policy) {
const details = `Expected: ${this.policy}. Received: ${policy.type}`;
throw Error("Mismatched policy type. " + details);
}
return new DefaultPolicyEvaluator<SubaddressGenerationOptions>();
}
/** {@link GeneratorStrategy.generate} */
generate(options: SubaddressGenerationOptions) {
return this.usernameService.generateSubaddress({
subaddressEmail: options.email,
subaddressType: options.type,
});
}
}