1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-10-22 07:50:04 +02:00

component rough draft

This commit is contained in:
✨ Audrey ✨ 2024-10-10 12:34:37 -04:00
parent 7b5403a5e8
commit 0791191ce0
No known key found for this signature in database
GPG Key ID: 0CF8B4C0D9088B97
11 changed files with 108 additions and 64 deletions

View File

@ -160,7 +160,14 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy {
if (root.nav === IDENTIFIER) {
return concat(of(this.username.value), this.username.valueChanges);
} else {
return of(root as { nav: PasswordAlgorithm });
return of(root as { nav: CredentialAlgorithm });
}
}),
switchMap((tier1) => {
if (tier1.nav === FORWARDER) {
return concat(of(this.forwarder.value), this.forwarder.valueChanges);
} else {
return of(tier1 as { nav: CredentialAlgorithm });
}
}),
filter(({ nav }) => !!nav),

View File

@ -31,6 +31,13 @@
credentialTypeHint$ | async
}}</bit-hint>
</bit-form-field>
<bit-form-field>
<bit-label>{{ "forwarder" | i18n }}</bit-label>
<bit-select [items]="forwarderOptions$ | async" formControlName="forwarder"> </bit-select>
<bit-hint *ngIf="!!(forwarderTypeHint$ | async)">{{
forwarderTypeHint$ | async
}}</bit-hint>
</bit-form-field>
</form>
<tools-catchall-settings
*ngIf="(algorithm$ | async)?.id === 'catchall'"
@ -47,6 +54,7 @@
[userId]="this.userId$ | async"
(onUpdated)="generate$.next()"
/>
<!-- TODO: list forwarder settings components -->
</bit-card>
</div>
</bit-section>

View File

@ -23,6 +23,7 @@ import {
GeneratedCredential,
Generators,
isEmailAlgorithm,
isForwarderIntegration,
isUsernameAlgorithm,
} from "@bitwarden/generator-core";
@ -78,10 +79,16 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy {
this.generatorService
.algorithms$(["email", "username"], { userId$: this.userId$ })
.pipe(
map((algorithms) => this.toOptions(algorithms)),
map((algorithms) => [
this.toOptions(algorithms.filter(a => !isForwarderIntegration(a.id))),
this.toOptions(algorithms.filter(a => isForwarderIntegration(a.id)))
] as const),
takeUntil(this.destroyed),
)
.subscribe(this.typeOptions$);
.subscribe(([type, forwarder]) => {
this.typeOptions$.next(type);
this.forwarderOptions$.next(forwarder);
});
this.algorithm$
.pipe(
@ -184,6 +191,9 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy {
/** Lists the credential types supported by the component. */
protected typeOptions$ = new BehaviorSubject<Option<CredentialAlgorithm>[]>([]);
/** Lists the credential types supported by the component. */
protected forwarderOptions$ = new BehaviorSubject<Option<CredentialAlgorithm>[]>([]);
/** tracks the currently selected credential type */
protected algorithm$ = new ReplaySubject<CredentialGeneratorInfo>(1);

View File

@ -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", "addyio"] as const);
export const EmailAlgorithms = Object.freeze(["catchall", "subaddress"] as const);
/** All types of credentials that may be generated by the credential generator */
export const CredentialAlgorithms = Object.freeze([

View File

@ -1,11 +1,10 @@
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 { ApiSettings } from "@bitwarden/common/tools/integration/rpc";
import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint";
import { EmailRandomizer, PasswordRandomizer, UsernameRandomizer } from "../engine";
import { Forwarder } from "../engine/forwarder";
import { AddyIoSettings } from "../integration";
import {
DefaultPolicyEvaluator,
DynamicPasswordPolicyConstraints,
@ -27,6 +26,7 @@ import {
CredentialGenerator,
CredentialGeneratorConfiguration,
EffUsernameGenerationOptions,
ForwarderIntegration,
GeneratorDependencyProvider,
NoPolicy,
PassphraseGenerationOptions,
@ -36,7 +36,6 @@ 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";
@ -44,6 +43,8 @@ import { DefaultPassphraseGenerationOptions } from "./default-passphrase-generat
import { DefaultPasswordBoundaries } from "./default-password-boundaries";
import { DefaultPasswordGenerationOptions } from "./default-password-generation-options";
import { DefaultSubaddressOptions } from "./default-subaddress-generator-options";
import { getForwarderConfiguration } from "./integrations";
const PASSPHRASE = Object.freeze({
id: "passphrase",
@ -219,41 +220,45 @@ 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>();
},
export function toCredentialGeneratorConfiguration<Settings extends ApiSettings = ApiSettings>(id :ForwarderIntegration) {
// TODO: eliminate the type erasure
const configuration = getForwarderConfiguration(id.forwarder) as any;
if(!configuration) {
throw new Error(`Invalid forwarder id: ${id.forwarder}`);
}
} satisfies CredentialGeneratorConfiguration<AddyIoSettings, NoPolicy>);
const forwarder = Object.freeze({
id: { forwarder: configuration.id },
category: "email",
nameKey: configuration.name,
onlyOnRequest: true,
engine: {
create(dependencies: GeneratorDependencyProvider) {
return new Forwarder(configuration, dependencies.client, dependencies.i18nService);
}
},
settings: {
initial: configuration.forwarder.defaultSettings,
constraints: {},
account: configuration.forwarder.settings
},
policy: {
type: PolicyType.PasswordGenerator,
disabledValue: {},
combine(_acc: NoPolicy, _policy: Policy) {
return {};
},
createEvaluator(_policy: NoPolicy) {
return new DefaultPolicyEvaluator<Settings>();
},
toConstraints(_policy: NoPolicy) {
return new IdentityConstraint<Settings>();
},
}
} satisfies CredentialGeneratorConfiguration<Settings, NoPolicy>);
return forwarder;
}
/** Generator configurations */
export const Generators = Object.freeze({
@ -271,6 +276,4 @@ export const Generators = Object.freeze({
/** Email subaddress generator configuration */
subaddress: SUBADDRESS,
addyio: ADDYIO,
});

View File

@ -20,6 +20,12 @@ export const Integrations = Object.freeze({
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]
export function getForwarderConfiguration(id: IntegrationId) : ForwarderConfiguration<ApiSettings> {
const maybeForwarder = integrations[id as string];
if("forwarder" in maybeForwarder) {
return maybeForwarder as ForwarderConfiguration<ApiSettings>
} else {
return null;
}
}

View File

@ -35,9 +35,11 @@ export type ForwarderConfiguration<
defaultSettings: Partial<Settings>;
/** forwarder settings storage */
// FIXME: this should be a `SubjectConfiguration<Settings>`
settings: UserKeyDefinition<Settings>;
/** forwarder settings import buffer; `undefined` when there is no buffer. */
// FIXME: this should be a `SubjectConfiguration<Settings>`
importBuffer?: BufferedKeyDefinition<Settings>;
/** createForwardingEmail RPC definition */

View File

@ -2,7 +2,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { ApiSettings, IntegrationRequest, RestClient } from "@bitwarden/common/tools/integration/rpc";
import { GenerationRequest } from "@bitwarden/common/tools/types";
import { getIntegration } from "../data";
import {
CredentialGenerator,
GeneratedCredential,
@ -20,32 +19,24 @@ export class Forwarder
/** Instantiates the email randomizer
* @param random data source for random data
*/
constructor(private client: RestClient, private i18nService: I18nService) {}
constructor(private configuration: ForwarderConfiguration<ApiSettings>, 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);
const getAccount = await this.getAccountId(this.configuration, settings);
if (getAccount) {
requestOptions.accountId = await this.client.fetchJson(getAccount, requestOptions);
}
const create = this.createForwardingAddress(integration, settings);
const create = this.createForwardingAddress(this.configuration, settings);
const result = await this.client.fetchJson(create, requestOptions);
const id = { forwarder: this.configuration.id };
return new GeneratedCredential(result, "forwarder", Date.now());
return new GeneratedCredential(result, id, Date.now());
}
private createContext<Settings>(

View File

@ -36,7 +36,7 @@ import { isDynamic } from "@bitwarden/common/tools/state/state-constraints-depen
import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subject";
import { Randomizer } from "../abstractions";
import { Generators } from "../data";
import { Generators, toCredentialGeneratorConfiguration } from "../data";
import { availableAlgorithms } from "../policies/available-algorithms-policy";
import { mapPolicyToConstraints } from "../rx";
import {
@ -45,6 +45,7 @@ import {
CredentialCategory,
CredentialGeneratorInfo,
CredentialPreference,
isForwarderIntegration
} from "../types";
import { CredentialGeneratorConfiguration as Configuration, GeneratorDependencyProvider } from "../types/credential-generator-configuration";
import { GeneratorConstraints } from "../types/generator-constraints";
@ -182,7 +183,7 @@ export class CredentialGeneratorService {
return policies$;
}),
map((available) => {
const filtered = algorithms.filter((c) => available.has(c.id));
const filtered = algorithms.filter((c) => isForwarderIntegration(c.id) || available.has(c.id));
return filtered;
}),
);
@ -200,7 +201,7 @@ export class CredentialGeneratorService {
const categories = Array.isArray(category) ? category : [category];
const algorithms = categories
.flatMap((c) => CredentialCategories[c])
.map((c) => (c === "forwarder" ? null : Generators[c]))
.map((id) => this.algorithm(id))
.filter((info) => info !== null);
return algorithms;
@ -211,7 +212,11 @@ export class CredentialGeneratorService {
* @returns the requested metadata, or `null` if the metadata wasn't found.
*/
algorithm(id: CredentialAlgorithm): CredentialGeneratorInfo {
return (id === "forwarder" ? null : Generators[id]) ?? null;
if(isForwarderIntegration(id)) {
return toCredentialGeneratorConfiguration(id);
} else {
return Generators[id];
}
}
/** Get the settings for the provided configuration

View File

@ -60,6 +60,9 @@ export type CredentialGeneratorConfiguration<Settings, Policy> = CredentialGener
/** storage location for account-global settings */
account: UserKeyDefinition<Settings>;
/** storage location for *plaintext* settings imports */
import?: UserKeyDefinition<Settings>;
};
/** defines how to construct policy for this settings instance */

View File

@ -1,3 +1,5 @@
import { IntegrationId } from "@bitwarden/common/tools/integration";
import { EmailAlgorithms, PasswordAlgorithms, UsernameAlgorithms } from "../data/generator-types";
/** A type of password that may be generated by the credential generator. */
@ -9,8 +11,15 @@ export type UsernameAlgorithm = (typeof UsernameAlgorithms)[number];
/** A type of email address that may be generated by the credential generator. */
export type EmailAlgorithm = (typeof EmailAlgorithms)[number];
export type ForwarderIntegration = { forwarder: IntegrationId };
/** Returns true when the input algorithm is a forwarder integration. */
export function isForwarderIntegration(algorithm: CredentialAlgorithm) : algorithm is ForwarderIntegration {
return (typeof algorithm === "object") && "forwarder" in algorithm;
}
/** A type of credential that may be generated by the credential generator. */
export type CredentialAlgorithm = PasswordAlgorithm | UsernameAlgorithm | EmailAlgorithm;
export type CredentialAlgorithm = PasswordAlgorithm | UsernameAlgorithm | EmailAlgorithm | ForwarderIntegration;
/** Compound credential types supported by the credential generator. */
export const CredentialCategories = Object.freeze({