mirror of
https://github.com/bitwarden/browser.git
synced 2024-10-22 07:50:04 +02:00
rough example
This commit is contained in:
parent
dc1f014ad8
commit
f26ab56a5d
@ -1,5 +1,7 @@
|
||||
import { Simplify } from "type-fest";
|
||||
|
||||
import { IntegrationId } from "./integration";
|
||||
|
||||
/** Constraints that are shared by all primitive field types */
|
||||
type PrimitiveConstraint = {
|
||||
/** `true` indicates the field is required; otherwise the field is optional */
|
||||
@ -144,4 +146,6 @@ export type VaultItemRequest = {
|
||||
/** Options that provide contextual information about the application state
|
||||
* when a generator is invoked.
|
||||
*/
|
||||
export type GenerationRequest = Partial<VaultItemRequest>;
|
||||
export type GenerationRequest = Partial<VaultItemRequest> & Partial<{
|
||||
integration: IntegrationId | null
|
||||
}>;
|
||||
|
@ -5,7 +5,7 @@ export const PasswordAlgorithms = Object.freeze(["password", "passphrase"] as co
|
||||
export const UsernameAlgorithms = Object.freeze(["username"] as const);
|
||||
|
||||
/** Types of email addresses that may be generated by the credential generator */
|
||||
export const EmailAlgorithms = Object.freeze(["catchall", "forwarder", "subaddress"] as const);
|
||||
export const EmailAlgorithms = Object.freeze(["catchall", "forwarder", "subaddress", "addyio"] as const);
|
||||
|
||||
/** All types of credentials that may be generated by the credential generator */
|
||||
export const CredentialAlgorithms = Object.freeze([
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
|
||||
import { GENERATOR_DISK, UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
|
||||
|
||||
import { Randomizer } from "../abstractions";
|
||||
import { EmailRandomizer, PasswordRandomizer, UsernameRandomizer } from "../engine";
|
||||
import { Forwarder } from "../engine/forwarder";
|
||||
import { AddyIoSettings } from "../integration";
|
||||
import {
|
||||
DefaultPolicyEvaluator,
|
||||
DynamicPasswordPolicyConstraints,
|
||||
@ -25,6 +27,7 @@ import {
|
||||
CredentialGenerator,
|
||||
CredentialGeneratorConfiguration,
|
||||
EffUsernameGenerationOptions,
|
||||
GeneratorDependencyProvider,
|
||||
NoPolicy,
|
||||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy,
|
||||
@ -33,6 +36,7 @@ import {
|
||||
SubaddressGenerationOptions,
|
||||
} from "../types";
|
||||
|
||||
import { DefaultAddyIoOptions } from "./default-addy-io-options";
|
||||
import { DefaultCatchallOptions } from "./default-catchall-options";
|
||||
import { DefaultEffUsernameOptions } from "./default-eff-username-options";
|
||||
import { DefaultPassphraseBoundaries } from "./default-passphrase-boundaries";
|
||||
@ -47,8 +51,8 @@ const PASSPHRASE = Object.freeze({
|
||||
nameKey: "passphrase",
|
||||
onlyOnRequest: false,
|
||||
engine: {
|
||||
create(randomizer: Randomizer): CredentialGenerator<PassphraseGenerationOptions> {
|
||||
return new PasswordRandomizer(randomizer);
|
||||
create(dependencies: GeneratorDependencyProvider): CredentialGenerator<PassphraseGenerationOptions> {
|
||||
return new PasswordRandomizer(dependencies.randomizer);
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@ -84,8 +88,8 @@ const PASSWORD = Object.freeze({
|
||||
nameKey: "password",
|
||||
onlyOnRequest: false,
|
||||
engine: {
|
||||
create(randomizer: Randomizer): CredentialGenerator<PasswordGenerationOptions> {
|
||||
return new PasswordRandomizer(randomizer);
|
||||
create(dependencies: GeneratorDependencyProvider): CredentialGenerator<PasswordGenerationOptions> {
|
||||
return new PasswordRandomizer(dependencies.randomizer);
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@ -129,8 +133,8 @@ const USERNAME = Object.freeze({
|
||||
nameKey: "randomWord",
|
||||
onlyOnRequest: false,
|
||||
engine: {
|
||||
create(randomizer: Randomizer): CredentialGenerator<EffUsernameGenerationOptions> {
|
||||
return new UsernameRandomizer(randomizer);
|
||||
create(dependencies: GeneratorDependencyProvider): CredentialGenerator<EffUsernameGenerationOptions> {
|
||||
return new UsernameRandomizer(dependencies.randomizer);
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@ -160,8 +164,8 @@ const CATCHALL = Object.freeze({
|
||||
descriptionKey: "catchallEmailDesc",
|
||||
onlyOnRequest: false,
|
||||
engine: {
|
||||
create(randomizer: Randomizer): CredentialGenerator<CatchallGenerationOptions> {
|
||||
return new EmailRandomizer(randomizer);
|
||||
create(dependencies: GeneratorDependencyProvider): CredentialGenerator<CatchallGenerationOptions> {
|
||||
return new EmailRandomizer(dependencies.randomizer);
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@ -191,8 +195,8 @@ const SUBADDRESS = Object.freeze({
|
||||
descriptionKey: "plusAddressedEmailDesc",
|
||||
onlyOnRequest: false,
|
||||
engine: {
|
||||
create(randomizer: Randomizer): CredentialGenerator<SubaddressGenerationOptions> {
|
||||
return new EmailRandomizer(randomizer);
|
||||
create(dependencies: GeneratorDependencyProvider): CredentialGenerator<SubaddressGenerationOptions> {
|
||||
return new EmailRandomizer(dependencies.randomizer);
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
@ -215,6 +219,42 @@ const SUBADDRESS = Object.freeze({
|
||||
},
|
||||
} satisfies CredentialGeneratorConfiguration<SubaddressGenerationOptions, NoPolicy>);
|
||||
|
||||
// FIXME: forwarders should dynamically extend generators; they shouldn't
|
||||
// have their own entries. They're included here solely in order to quickly
|
||||
// create generator configurations during UI modernization
|
||||
const ADDYIO = Object.freeze({
|
||||
id: "addyio",
|
||||
category: "email",
|
||||
nameKey: "addyIoKey",
|
||||
onlyOnRequest: true,
|
||||
engine: {
|
||||
create(dependencies: GeneratorDependencyProvider) {
|
||||
return new Forwarder(dependencies.client, dependencies.i18nService);
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
initial: DefaultAddyIoOptions,
|
||||
constraints: {},
|
||||
account: new UserKeyDefinition<AddyIoSettings>(GENERATOR_DISK, "addyIoGenerator", {
|
||||
deserializer: (value) => value,
|
||||
clearOn: [],
|
||||
})
|
||||
},
|
||||
policy: {
|
||||
type: PolicyType.PasswordGenerator,
|
||||
disabledValue: {},
|
||||
combine(_acc: NoPolicy, _policy: Policy) {
|
||||
return {};
|
||||
},
|
||||
createEvaluator(_policy: NoPolicy) {
|
||||
return new DefaultPolicyEvaluator<AddyIoSettings>();
|
||||
},
|
||||
toConstraints(_policy: NoPolicy) {
|
||||
return new IdentityConstraint<AddyIoSettings>();
|
||||
},
|
||||
}
|
||||
} satisfies CredentialGeneratorConfiguration<AddyIoSettings, NoPolicy>);
|
||||
|
||||
/** Generator configurations */
|
||||
export const Generators = Object.freeze({
|
||||
/** Passphrase generator configuration */
|
||||
@ -231,4 +271,6 @@ export const Generators = Object.freeze({
|
||||
|
||||
/** Email subaddress generator configuration */
|
||||
subaddress: SUBADDRESS,
|
||||
|
||||
addyio: ADDYIO,
|
||||
});
|
||||
|
@ -1,3 +1,7 @@
|
||||
import { IntegrationId } from "@bitwarden/common/tools/integration";
|
||||
import { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
|
||||
|
||||
import { ForwarderConfiguration } from "../engine";
|
||||
import { AddyIo } from "../integration/addy-io";
|
||||
import { DuckDuckGo } from "../integration/duck-duck-go";
|
||||
import { Fastmail } from "../integration/fastmail";
|
||||
@ -13,3 +17,9 @@ export const Integrations = Object.freeze({
|
||||
ForwardEmail,
|
||||
SimpleLogin,
|
||||
} as const);
|
||||
|
||||
const integrations = Object.fromEntries(Object.values(Integrations).map((i) => [i.id, i as ForwarderConfiguration<object>]));
|
||||
|
||||
export function getIntegration(id: IntegrationId) : ForwarderConfiguration<ApiSettings> {
|
||||
return integrations[id as string]
|
||||
}
|
||||
|
80
libs/tools/generator/core/src/engine/forwarder.ts
Normal file
80
libs/tools/generator/core/src/engine/forwarder.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { ApiSettings, IntegrationRequest, RestClient } from "@bitwarden/common/tools/integration/rpc";
|
||||
import { GenerationRequest } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { getIntegration } from "../data";
|
||||
import {
|
||||
CredentialGenerator,
|
||||
GeneratedCredential,
|
||||
} from "../types";
|
||||
|
||||
import { AccountRequest, ForwarderConfiguration } from "./forwarder-configuration";
|
||||
import { ForwarderContext } from "./forwarder-context";
|
||||
import { CreateForwardingAddressRpc, GetAccountIdRpc } from "./rpc";
|
||||
|
||||
/** Generation algorithms that produce randomized email addresses */
|
||||
export class Forwarder
|
||||
implements
|
||||
CredentialGenerator<ApiSettings>
|
||||
{
|
||||
/** Instantiates the email randomizer
|
||||
* @param random data source for random data
|
||||
*/
|
||||
constructor(private client: RestClient, private i18nService: I18nService) {}
|
||||
|
||||
async generate(
|
||||
request: GenerationRequest,
|
||||
settings: ApiSettings,
|
||||
) {
|
||||
if(!request.integration) {
|
||||
throw new Error("Invalid integration request received by generator.");
|
||||
}
|
||||
|
||||
const integration = getIntegration(request.integration);
|
||||
if(!integration) {
|
||||
throw new Error("Invalid integration request received by generator.");
|
||||
}
|
||||
|
||||
const requestOptions: IntegrationRequest & AccountRequest = { website: request.website };
|
||||
|
||||
const getAccount = await this.getAccountId(integration, settings);
|
||||
if (getAccount) {
|
||||
requestOptions.accountId = await this.client.fetchJson(getAccount, requestOptions);
|
||||
}
|
||||
|
||||
const create = this.createForwardingAddress(integration, settings);
|
||||
const result = await this.client.fetchJson(create, requestOptions);
|
||||
|
||||
return new GeneratedCredential(result, "forwarder", Date.now());
|
||||
}
|
||||
|
||||
private createContext<Settings>(
|
||||
configuration: ForwarderConfiguration<Settings>,
|
||||
settings: Settings,
|
||||
) {
|
||||
return new ForwarderContext(configuration, settings, this.i18nService);
|
||||
}
|
||||
|
||||
private createForwardingAddress<Settings extends ApiSettings>(
|
||||
configuration: ForwarderConfiguration<Settings>,
|
||||
settings: Settings,
|
||||
) {
|
||||
const context = this.createContext(configuration, settings);
|
||||
const rpc = new CreateForwardingAddressRpc<Settings>(configuration, context);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
private getAccountId<Settings extends ApiSettings>(
|
||||
configuration: ForwarderConfiguration<Settings>,
|
||||
settings: Settings,
|
||||
) {
|
||||
if (!configuration.forwarder.getAccountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const context = this.createContext(configuration, settings);
|
||||
const rpc = new GetAccountIdRpc<Settings>(configuration, context);
|
||||
|
||||
return rpc;
|
||||
}
|
||||
}
|
@ -22,12 +22,15 @@ import { Simplify } from "type-fest";
|
||||
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import {
|
||||
OnDependency,
|
||||
SingleUserDependency,
|
||||
UserDependency,
|
||||
} from "@bitwarden/common/tools/dependencies";
|
||||
import { IntegrationId } from "@bitwarden/common/tools/integration";
|
||||
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
|
||||
import { isDynamic } from "@bitwarden/common/tools/state/state-constraints-dependency";
|
||||
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
|
||||
|
||||
@ -42,7 +45,7 @@ import {
|
||||
CredentialGeneratorInfo,
|
||||
CredentialPreference,
|
||||
} from "../types";
|
||||
import { CredentialGeneratorConfiguration as Configuration } from "../types/credential-generator-configuration";
|
||||
import { CredentialGeneratorConfiguration as Configuration, GeneratorDependencyProvider } from "../types/credential-generator-configuration";
|
||||
import { GeneratorConstraints } from "../types/generator-constraints";
|
||||
|
||||
import { PREFERENCES } from "./credential-preferences";
|
||||
@ -59,6 +62,8 @@ type Generate$Dependencies = Simplify<Partial<OnDependency> & Partial<UserDepend
|
||||
* When `website$` errors, the generator forwards the error.
|
||||
*/
|
||||
website$?: Observable<string>;
|
||||
|
||||
integration$?: Observable<IntegrationId>
|
||||
};
|
||||
|
||||
type Algorithms$Dependencies = Partial<UserDependency>;
|
||||
@ -68,8 +73,18 @@ export class CredentialGeneratorService {
|
||||
private randomizer: Randomizer,
|
||||
private stateProvider: StateProvider,
|
||||
private policyService: PolicyService,
|
||||
private client: RestClient,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
private getDependencyProvider() : GeneratorDependencyProvider {
|
||||
return {
|
||||
client: this.client,
|
||||
i18nService: this.i18nService,
|
||||
randomizer: this.randomizer
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: the rxjs methods of this service can be a lot more resilient if
|
||||
// `Subjects` are introduced where sharing occurs
|
||||
|
||||
@ -84,11 +99,14 @@ export class CredentialGeneratorService {
|
||||
dependencies?: Generate$Dependencies,
|
||||
) {
|
||||
// instantiate the engine
|
||||
const engine = configuration.engine.create(this.randomizer);
|
||||
const engine = configuration.engine.create(this.getDependencyProvider());
|
||||
|
||||
// stream blocks until all of these values are received
|
||||
const website$ = dependencies?.website$ ?? new BehaviorSubject<string>(null);
|
||||
const request$ = website$.pipe(map((website) => ({ website })));
|
||||
const integration$ = dependencies?.integration$ ?? new BehaviorSubject<IntegrationId>(null);
|
||||
const request$ = combineLatest([website$, integration$]).pipe(
|
||||
map(([website, integration]) => ({ website, integration }))
|
||||
);
|
||||
const settings$ = this.settings$(configuration, dependencies);
|
||||
|
||||
// monitor completion
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { UserKeyDefinition } from "@bitwarden/common/platform/state";
|
||||
import { RestClient } from "@bitwarden/common/tools/integration/rpc";
|
||||
import { Constraints } from "@bitwarden/common/tools/types";
|
||||
|
||||
import { Randomizer } from "../abstractions";
|
||||
@ -6,6 +8,12 @@ import { CredentialAlgorithm, CredentialCategory, PolicyConfiguration } from "..
|
||||
|
||||
import { CredentialGenerator } from "./credential-generator";
|
||||
|
||||
export type GeneratorDependencyProvider = {
|
||||
randomizer: Randomizer,
|
||||
client: RestClient,
|
||||
i18nService: I18nService
|
||||
};
|
||||
|
||||
/** Credential generator metadata common across credential generators */
|
||||
export type CredentialGeneratorInfo = {
|
||||
/** Uniquely identifies the credential configuration
|
||||
@ -40,7 +48,7 @@ export type CredentialGeneratorConfiguration<Settings, Policy> = CredentialGener
|
||||
// the credential generator, but engine configurations should return
|
||||
// the underlying type. `create` may be able to do double-duty w/ an
|
||||
// engine definition if `CredentialGenerator` can be made covariant.
|
||||
create: (randomizer: Randomizer) => CredentialGenerator<Settings>;
|
||||
create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator<Settings>;
|
||||
};
|
||||
/** Defines the stored parameters for credential generation */
|
||||
settings: {
|
||||
|
Loading…
Reference in New Issue
Block a user