diff --git a/libs/tools/generator/components/src/credential-generator.component.html b/libs/tools/generator/components/src/credential-generator.component.html index 6102f644a3..9e20f8821a 100644 --- a/libs/tools/generator/components/src/credential-generator.component.html +++ b/libs/tools/generator/components/src/credential-generator.component.html @@ -48,7 +48,7 @@
-
+ {{ "type" | i18n }} @@ -57,9 +57,9 @@ }}
-
+ - {{ "forwarder" | i18n }} + {{ "service" | i18n }}
@@ -69,7 +69,7 @@ (onUpdated)="generate$.next()" /> diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 2d94991f25..ad15f2ad66 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -8,6 +8,7 @@ import { map, of, ReplaySubject, + startWith, Subject, switchMap, takeUntil, @@ -49,6 +50,8 @@ type UsernameNavValue = UsernameAlgorithm | EmailAlgorithm | typeof FORWARDER; const NONE_SELECTED = "none"; type ForwarderNavValue = ForwarderIntegration | typeof NONE_SELECTED; +const FORWARDER_INITIALIZED = new GeneratedCredential("-", null, Date.now()); + @Component({ selector: "tools-credential-generator", templateUrl: "credential-generator.component.html", @@ -112,7 +115,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { map((algorithms) => { const usernames = algorithms.filter((a) => !isForwarderIntegration(a.id)); const usernameOptions = this.toOptions(usernames) as Option[]; - usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwarder") }); + usernameOptions.push({ value: FORWARDER, label: this.i18nService.t("forwardedEmail") }); const forwarders = algorithms.filter((a) => isForwarderIntegration(a.id)); const forwarderOptions = this.toOptions(forwarders) as Option[]; @@ -226,12 +229,20 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { } else if (isPasswordAlgorithm(algorithm)) { setPreference("password"); } else { + this.showForwarder$.next(false); return; } preferences.next(preference); }); + this.username.valueChanges + .pipe( + map(({ nav }) => nav === FORWARDER), + takeUntil(this.destroyed), + ) + .subscribe(this.showForwarder$); + // populate the form with the user's preferences to kick off interactivity preferences.pipe(takeUntil(this.destroyed)).subscribe(({ email, username, password }) => { // the last preference set by the user "wins" @@ -305,7 +316,9 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { if (isForwarderIntegration(type)) { const forwarder = getForwarderConfiguration(type.forwarder); const configuration = toCredentialGeneratorConfiguration(forwarder); - return this.generatorService.generate$(configuration, dependencies); + const generator = this.generatorService.generate$(configuration, dependencies); + + return generator.pipe(startWith(FORWARDER_INITIALIZED)); } throw new Error(`Invalid generator type: "${type}"`); @@ -323,6 +336,9 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { /** Tracks the currently selected forwarder. */ protected forwarderId$ = new BehaviorSubject(null); + /** Tracks forwarder control visibility */ + protected showForwarder$ = new BehaviorSubject(false); + /** tracks the currently selected credential type */ protected algorithm$ = new ReplaySubject(1); @@ -344,7 +360,7 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { private toOptions(algorithms: AlgorithmInfo[]) { const options: Option[] = algorithms.map((algorithm) => ({ value: algorithm.id, - label: this.i18nService.t(algorithm.name), + label: algorithm.name, })); return options; diff --git a/libs/tools/generator/core/src/services/credential-generator.service.ts b/libs/tools/generator/core/src/services/credential-generator.service.ts index 412188a738..dcd3bcd862 100644 --- a/libs/tools/generator/core/src/services/credential-generator.service.ts +++ b/libs/tools/generator/core/src/services/credential-generator.service.ts @@ -43,7 +43,12 @@ import { UserStateSubject } from "@bitwarden/common/tools/state/user-state-subje import { UserId } from "@bitwarden/common/types/guid"; import { Randomizer } from "../abstractions"; -import { Generators, getForwarderConfiguration, toCredentialGeneratorConfiguration } from "../data"; +import { + Generators, + getForwarderConfiguration, + Integrations, + toCredentialGeneratorConfiguration, +} from "../data"; import { availableAlgorithms } from "../policies/available-algorithms-policy"; import { mapPolicyToConstraints } from "../rx"; import { @@ -53,6 +58,7 @@ import { AlgorithmInfo, CredentialPreference, isForwarderIntegration, + ForwarderIntegration, } from "../types"; import { CredentialGeneratorConfiguration as Configuration, @@ -208,12 +214,20 @@ export class CredentialGeneratorService { algorithms(category: CredentialCategory[]): AlgorithmInfo[]; algorithms(category: CredentialCategory | CredentialCategory[]): AlgorithmInfo[] { const categories: CredentialCategory[] = Array.isArray(category) ? category : [category]; + const algorithms = categories .flatMap((c) => CredentialCategories[c] as CredentialAlgorithm[]) .map((id) => this.algorithm(id)) .filter((info) => info !== null); - return algorithms; + const forwarders = Object.keys(Integrations) + .map((key: keyof typeof Integrations) => { + const forwarder: ForwarderIntegration = { forwarder: Integrations[key].id }; + return this.algorithm(forwarder); + }) + .filter((forwarder) => categories.includes(forwarder.category)); + + return algorithms.concat(forwarders); } /** Look up the metadata for a specific generator algorithm