diff --git a/libs/tools/generator/components/src/catchall-settings.component.html b/libs/tools/generator/components/src/catchall-settings.component.html index 0b2a9e69ef39..7438da6c468e 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.html +++ b/libs/tools/generator/components/src/catchall-settings.component.html @@ -1,6 +1,6 @@
{{ "domainName" | i18n }} - +
diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 55ddc1f8102c..379b6d4c0de8 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; -import { BehaviorSubject, skip, Subject, takeUntil } from "rxjs"; +import { BehaviorSubject, map, skip, Subject, takeUntil, withLatestFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserId } from "@bitwarden/common/types/guid"; @@ -12,6 +12,11 @@ import { import { completeOnAccountSwitch } from "./util"; +/** Splits an email into a username, subaddress, and domain named group. + * Subaddress is optional. + */ +export const DOMAIN_PARSER = new RegExp("[^@]+@(?.+)"); + /** Options group for catchall emails */ @Component({ selector: "tools-catchall-settings", @@ -53,14 +58,43 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy { const singleUserId$ = this.singleUserId$(); const settings = await this.generatorService.settings(Generators.catchall, { singleUserId$ }); - settings.pipe(takeUntil(this.destroyed$)).subscribe((s) => { - this.settings.patchValue(s, { emitEvent: false }); - }); + settings + .pipe( + withLatestFrom(this.accountService.activeAccount$), + map(([settings, activeAccount]) => { + // if the subaddress isn't specified, copy it from + // the user's settings + if ((settings.catchallDomain ?? "").trim().length < 1) { + const parsed = DOMAIN_PARSER.exec(activeAccount.email); + if (parsed) { + settings.catchallDomain = parsed.groups.domain; + } + } + + return settings; + }), + takeUntil(this.destroyed$), + ) + .subscribe((s) => { + this.settings.patchValue(s, { emitEvent: false }); + }); // the first emission is the current value; subsequent emissions are updates settings.pipe(skip(1), takeUntil(this.destroyed$)).subscribe(this.onUpdated); - this.settings.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(settings); + // now that outputs are set up, connect inputs + this.saveSettings + .pipe( + withLatestFrom(this.settings.valueChanges), + map(([, settings]) => settings), + takeUntil(this.destroyed$), + ) + .subscribe(settings); + } + + private saveSettings = new Subject(); + save(site: string = "component api call") { + this.saveSettings.next(site); } private singleUserId$() { @@ -78,6 +112,7 @@ export class CatchallSettingsComponent implements OnInit, OnDestroy { private readonly destroyed$ = new Subject(); ngOnDestroy(): void { + this.destroyed$.next(); this.destroyed$.complete(); } } diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 2ccad318d756..211eef8301c6 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -538,6 +538,10 @@ export class CredentialGeneratorComponent implements OnInit, OnDestroy { await this.passwordComponent.save("credential generator"); } + if (this.catchallComponent) { + await this.catchallComponent.save("credential generator"); + } + this.generate$.next(requestor); } diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index dac3b061d8cf..c67c8d30c140 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -211,13 +211,15 @@ export class PasswordGeneratorComponent implements OnInit, OnDestroy { }); // generate on load unless the generator prohibits it - this.algorithm$ - .pipe( - distinctUntilChanged((prev, next) => prev.id === next.id), - filter((a) => !a.onlyOnRequest), - takeUntil(this.destroyed), - ) - .subscribe(() => this.generate("autogenerate")); + this.algorithm$.pipe(takeUntil(this.destroyed)).subscribe((a) => { + this.zone.run(() => { + if (!a || a.onlyOnRequest) { + this.value$.next("-"); + } else { + this.generate("autogenerate").catch((e: unknown) => this.logService.error(e)); + } + }); + }); } private typeToGenerator$(type: CredentialAlgorithm) { diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index bd6ca899db75..cefba063ceeb 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -59,7 +59,7 @@ export class SubaddressSettingsComponent implements OnInit, OnDestroy { map(([settings, activeAccount]) => { // if the subaddress isn't specified, copy it from // the user's settings - if ((settings.subaddressEmail ?? "").length < 1) { + if ((settings.subaddressEmail ?? "").trim().length < 1) { settings.subaddressEmail = activeAccount.email; } diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 8d67c00778fb..e9175bd9958d 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -348,7 +348,7 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { if (!a || a.onlyOnRequest) { this.value$.next("-"); } else { - this.generate("autogenerate"); + this.generate("autogenerate").catch((e: unknown) => this.logService.error(e)); } }); }); @@ -440,7 +440,11 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { * @param requestor a label used to trace generation request * origin in the debugger. */ - protected generate(requestor: string) { + protected async generate(requestor: string) { + if (this.catchallComponent) { + await this.catchallComponent.save("credential generator"); + } + this.generate$.next(requestor); } @@ -455,6 +459,7 @@ export class UsernameGeneratorComponent implements OnInit, OnDestroy { private readonly destroyed = new Subject(); ngOnDestroy() { + this.destroyed.next(); this.destroyed.complete(); // finalize subjects