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 @@
-
-
@@ -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