Merge branch 'tools/PM-6819/legacy-password-generator-integration' into tools/PM-6819/reactive-generator
This commit is contained in:
commit
47e7c3ddc7
|
@ -137,10 +137,8 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa
|
|||
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
import {
|
||||
PasswordGenerationService,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/generator/password";
|
||||
import { legacyPasswordGenerationServiceFactory } from "@bitwarden/common/tools/generator";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import {
|
||||
UsernameGenerationService,
|
||||
UsernameGenerationServiceAbstraction,
|
||||
|
@ -604,10 +602,12 @@ export default class MainBackground {
|
|||
|
||||
this.passwordStrengthService = new PasswordStrengthService();
|
||||
|
||||
this.passwordGenerationService = new PasswordGenerationService(
|
||||
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
|
||||
this.encryptService,
|
||||
this.cryptoService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.accountService,
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
|
|
|
@ -1,46 +1,52 @@
|
|||
import {
|
||||
PasswordGenerationService,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/generator/password";
|
||||
import { legacyPasswordGenerationServiceFactory } from "@bitwarden/common/tools/generator";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
|
||||
import {
|
||||
policyServiceFactory,
|
||||
PolicyServiceInitOptions,
|
||||
} from "../../admin-console/background/service-factories/policy-service.factory";
|
||||
import {
|
||||
accountServiceFactory,
|
||||
AccountServiceInitOptions,
|
||||
} from "../../auth/background/service-factories/account-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
} from "../../platform/background/service-factories/crypto-service.factory";
|
||||
import {
|
||||
encryptServiceFactory,
|
||||
EncryptServiceInitOptions,
|
||||
} from "../../platform/background/service-factories/encrypt-service.factory";
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
stateServiceFactory,
|
||||
StateServiceInitOptions,
|
||||
} from "../../platform/background/service-factories/state-service.factory";
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../platform/background/service-factories/state-provider.factory";
|
||||
|
||||
type PasswordGenerationServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PasswordGenerationServiceInitOptions = PasswordGenerationServiceFactoryOptions &
|
||||
CryptoServiceInitOptions &
|
||||
EncryptServiceInitOptions &
|
||||
PolicyServiceInitOptions &
|
||||
StateServiceInitOptions;
|
||||
AccountServiceInitOptions &
|
||||
StateProviderInitOptions;
|
||||
|
||||
export function passwordGenerationServiceFactory(
|
||||
cache: { passwordGenerationService?: PasswordGenerationServiceAbstraction } & CachedServices,
|
||||
opts: PasswordGenerationServiceInitOptions,
|
||||
): Promise<PasswordGenerationServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"passwordGenerationService",
|
||||
opts,
|
||||
async () =>
|
||||
new PasswordGenerationService(
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
),
|
||||
return factory(cache, "passwordGenerationService", opts, async () =>
|
||||
legacyPasswordGenerationServiceFactory(
|
||||
await encryptServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await accountServiceFactory(cache, opts),
|
||||
await stateProviderFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,46 +1,52 @@
|
|||
import {
|
||||
PasswordGenerationService,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/generator/password";
|
||||
import { legacyPasswordGenerationServiceFactory } from "@bitwarden/common/tools/generator";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
|
||||
import {
|
||||
policyServiceFactory,
|
||||
PolicyServiceInitOptions,
|
||||
} from "../../../admin-console/background/service-factories/policy-service.factory";
|
||||
import {
|
||||
accountServiceFactory,
|
||||
AccountServiceInitOptions,
|
||||
} from "../../../auth/background/service-factories/account-service.factory";
|
||||
import {
|
||||
CryptoServiceInitOptions,
|
||||
cryptoServiceFactory,
|
||||
} from "../../../platform/background/service-factories/crypto-service.factory";
|
||||
import {
|
||||
encryptServiceFactory,
|
||||
EncryptServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/encrypt-service.factory";
|
||||
import {
|
||||
CachedServices,
|
||||
factory,
|
||||
FactoryOptions,
|
||||
} from "../../../platform/background/service-factories/factory-options";
|
||||
import {
|
||||
stateServiceFactory,
|
||||
StateServiceInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-service.factory";
|
||||
stateProviderFactory,
|
||||
StateProviderInitOptions,
|
||||
} from "../../../platform/background/service-factories/state-provider.factory";
|
||||
|
||||
type PasswordGenerationServiceFactoryOptions = FactoryOptions;
|
||||
|
||||
export type PasswordGenerationServiceInitOptions = PasswordGenerationServiceFactoryOptions &
|
||||
CryptoServiceInitOptions &
|
||||
EncryptServiceInitOptions &
|
||||
PolicyServiceInitOptions &
|
||||
StateServiceInitOptions;
|
||||
AccountServiceInitOptions &
|
||||
StateProviderInitOptions;
|
||||
|
||||
export function passwordGenerationServiceFactory(
|
||||
cache: { passwordGenerationService?: PasswordGenerationServiceAbstraction } & CachedServices,
|
||||
opts: PasswordGenerationServiceInitOptions,
|
||||
): Promise<PasswordGenerationServiceAbstraction> {
|
||||
return factory(
|
||||
cache,
|
||||
"passwordGenerationService",
|
||||
opts,
|
||||
async () =>
|
||||
new PasswordGenerationService(
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await stateServiceFactory(cache, opts),
|
||||
),
|
||||
return factory(cache, "passwordGenerationService", opts, async () =>
|
||||
legacyPasswordGenerationServiceFactory(
|
||||
await encryptServiceFactory(cache, opts),
|
||||
await cryptoServiceFactory(cache, opts),
|
||||
await policyServiceFactory(cache, opts),
|
||||
await accountServiceFactory(cache, opts),
|
||||
await stateProviderFactory(cache, opts),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Location } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
@ -32,6 +33,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
cipherService: CipherService,
|
||||
route: ActivatedRoute,
|
||||
logService: LogService,
|
||||
broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone,
|
||||
changeDetectorRef: ChangeDetectorRef,
|
||||
private location: Location,
|
||||
) {
|
||||
super(
|
||||
|
@ -42,6 +46,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
i18nService,
|
||||
logService,
|
||||
route,
|
||||
broadcasterService,
|
||||
ngZone,
|
||||
changeDetectorRef,
|
||||
window,
|
||||
);
|
||||
this.cipherService = cipherService;
|
||||
|
|
|
@ -104,10 +104,8 @@ import { EventUploadService } from "@bitwarden/common/services/event/event-uploa
|
|||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
|
||||
import {
|
||||
PasswordGenerationService,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/generator/password";
|
||||
import { legacyPasswordGenerationServiceFactory } from "@bitwarden/common/tools/generator";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import {
|
||||
PasswordStrengthService,
|
||||
PasswordStrengthServiceAbstraction,
|
||||
|
@ -465,10 +463,12 @@ export class Main {
|
|||
|
||||
this.passwordStrengthService = new PasswordStrengthService();
|
||||
|
||||
this.passwordGenerationService = new PasswordGenerationService(
|
||||
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
|
||||
this.encryptService,
|
||||
this.cryptoService,
|
||||
this.policyService,
|
||||
this.stateService,
|
||||
this.accountService,
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { NO_ERRORS_SCHEMA } from "@angular/core";
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA, NgZone } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
@ -56,6 +57,22 @@ describe("GeneratorComponent", () => {
|
|||
provide: LogService,
|
||||
useValue: mock<LogService>(),
|
||||
},
|
||||
{
|
||||
provide: BroadcasterService,
|
||||
useValue: mock<BroadcasterService>(),
|
||||
},
|
||||
{
|
||||
provide: NgZone,
|
||||
useValue: new NgZone({
|
||||
enableLongStackTrace: true,
|
||||
shouldCoalesceEventChangeDetection: false,
|
||||
shouldCoalesceRunChangeDetection: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
provide: ChangeDetectorRef,
|
||||
useValue: mock<ChangeDetectorRef>(),
|
||||
},
|
||||
{
|
||||
provide: CipherService,
|
||||
useValue: mock<CipherService>(),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
@ -22,6 +23,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
i18nService: I18nService,
|
||||
route: ActivatedRoute,
|
||||
logService: LogService,
|
||||
broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone,
|
||||
changeDetectorRef: ChangeDetectorRef,
|
||||
) {
|
||||
super(
|
||||
passwordGenerationService,
|
||||
|
@ -31,6 +35,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
i18nService,
|
||||
logService,
|
||||
route,
|
||||
broadcasterService,
|
||||
ngZone,
|
||||
changeDetectorRef,
|
||||
window,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { ChangeDetectorRef, Component, NgZone } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
|
@ -25,6 +26,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
i18nService: I18nService,
|
||||
logService: LogService,
|
||||
route: ActivatedRoute,
|
||||
broadcasterService: BroadcasterService,
|
||||
ngZone: NgZone,
|
||||
changeDetectorRef: ChangeDetectorRef,
|
||||
private dialogService: DialogService,
|
||||
) {
|
||||
super(
|
||||
|
@ -35,6 +39,9 @@ export class GeneratorComponent extends BaseGeneratorComponent {
|
|||
i18nService,
|
||||
logService,
|
||||
route,
|
||||
broadcasterService,
|
||||
ngZone,
|
||||
changeDetectorRef,
|
||||
window,
|
||||
);
|
||||
if (platformUtilsService.isSelfHost()) {
|
||||
|
|
|
@ -192,10 +192,8 @@ import { NotificationsService } from "@bitwarden/common/services/notifications.s
|
|||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
|
||||
import {
|
||||
PasswordGenerationService,
|
||||
PasswordGenerationServiceAbstraction,
|
||||
} from "@bitwarden/common/tools/generator/password";
|
||||
import { legacyPasswordGenerationServiceFactory } from "@bitwarden/common/tools/generator";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||
import {
|
||||
UsernameGenerationService,
|
||||
UsernameGenerationServiceAbstraction,
|
||||
|
@ -556,8 +554,14 @@ const safeProviders: SafeProvider[] = [
|
|||
}),
|
||||
safeProvider({
|
||||
provide: PasswordGenerationServiceAbstraction,
|
||||
useClass: PasswordGenerationService,
|
||||
deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction],
|
||||
useFactory: legacyPasswordGenerationServiceFactory,
|
||||
deps: [
|
||||
EncryptService,
|
||||
CryptoServiceAbstraction,
|
||||
PolicyServiceAbstraction,
|
||||
AccountServiceAbstraction,
|
||||
StateProvider,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: UsernameGenerationServiceAbstraction,
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Directive,
|
||||
EventEmitter,
|
||||
Input,
|
||||
NgZone,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
} from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { debounceTime, first, map } from "rxjs/operators";
|
||||
|
||||
import { PasswordGeneratorPolicyOptions } from "@bitwarden/common/admin-console/models/domain/password-generator-policy-options";
|
||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { GeneratorOptions } from "@bitwarden/common/tools/generator/generator-options";
|
||||
import { GeneratorType } from "@bitwarden/common/tools/generator/generator-type";
|
||||
import {
|
||||
PasswordGenerationServiceAbstraction,
|
||||
PasswordGeneratorOptions,
|
||||
|
@ -20,10 +30,12 @@ import {
|
|||
} from "@bitwarden/common/tools/generator/username";
|
||||
import { EmailForwarderOptions } from "@bitwarden/common/tools/models/domain/email-forwarder-options";
|
||||
|
||||
const ComponentId = "GeneratorComponent";
|
||||
|
||||
@Directive()
|
||||
export class GeneratorComponent implements OnInit {
|
||||
export class GeneratorComponent implements OnInit, OnDestroy {
|
||||
@Input() comingFromAddEdit = false;
|
||||
@Input() type: string;
|
||||
@Input() type: GeneratorType | "";
|
||||
@Output() onSelected = new EventEmitter<string>();
|
||||
|
||||
usernameGeneratingPromise: Promise<string>;
|
||||
|
@ -60,6 +72,9 @@ export class GeneratorComponent implements OnInit {
|
|||
protected i18nService: I18nService,
|
||||
protected logService: LogService,
|
||||
protected route: ActivatedRoute,
|
||||
protected broadcasterService: BroadcasterService,
|
||||
protected ngZone: NgZone,
|
||||
protected changeDetectorRef: ChangeDetectorRef,
|
||||
private win: Window,
|
||||
) {
|
||||
this.typeOptions = [
|
||||
|
@ -95,56 +110,86 @@ export class GeneratorComponent implements OnInit {
|
|||
this.initForwardOptions();
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
const passwordOptionsResponse = await this.passwordGenerationService.getOptions();
|
||||
this.passwordOptions = passwordOptionsResponse[0];
|
||||
this.enforcedPasswordPolicyOptions = passwordOptionsResponse[1];
|
||||
this.avoidAmbiguous = !this.passwordOptions.ambiguous;
|
||||
this.passwordOptions.type =
|
||||
this.passwordOptions.type === "passphrase" ? "passphrase" : "password";
|
||||
async load(navigationType: GeneratorType = undefined) {
|
||||
const passwordOptionsResponse = await this.passwordGenerationService.getOptions();
|
||||
this.passwordOptions = passwordOptionsResponse[0];
|
||||
this.enforcedPasswordPolicyOptions = passwordOptionsResponse[1];
|
||||
this.avoidAmbiguous = !this.passwordOptions.ambiguous;
|
||||
|
||||
this.usernameOptions = await this.usernameGenerationService.getOptions();
|
||||
if (this.usernameOptions.type == null) {
|
||||
this.usernameOptions.type = "word";
|
||||
}
|
||||
if (
|
||||
this.usernameOptions.subaddressEmail == null ||
|
||||
this.usernameOptions.subaddressEmail === ""
|
||||
) {
|
||||
this.usernameOptions.subaddressEmail = await this.stateService.getEmail();
|
||||
}
|
||||
if (this.usernameWebsite == null) {
|
||||
this.usernameOptions.subaddressType = this.usernameOptions.catchallType = "random";
|
||||
if (!this.type) {
|
||||
if (navigationType) {
|
||||
this.type = navigationType;
|
||||
} else {
|
||||
this.usernameOptions.website = this.usernameWebsite;
|
||||
const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" };
|
||||
this.subaddressOptions.push(websiteOption);
|
||||
this.catchallOptions.push(websiteOption);
|
||||
this.type = this.passwordOptions.type === "username" ? "username" : "password";
|
||||
}
|
||||
}
|
||||
|
||||
if (this.type !== "username" && this.type !== "password") {
|
||||
if (qParams.type === "username" || qParams.type === "password") {
|
||||
this.type = qParams.type;
|
||||
} else {
|
||||
const generatorOptions = await this.stateService.getGeneratorOptions();
|
||||
this.type = generatorOptions?.type ?? "password";
|
||||
}
|
||||
}
|
||||
if (this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerate();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.passwordOptions.type =
|
||||
this.passwordOptions.type === "passphrase" ? "passphrase" : "password";
|
||||
|
||||
this.usernameOptions = await this.usernameGenerationService.getOptions();
|
||||
if (this.usernameOptions.type == null) {
|
||||
this.usernameOptions.type = "word";
|
||||
}
|
||||
if (
|
||||
this.usernameOptions.subaddressEmail == null ||
|
||||
this.usernameOptions.subaddressEmail === ""
|
||||
) {
|
||||
this.usernameOptions.subaddressEmail = await this.stateService.getEmail();
|
||||
}
|
||||
if (this.usernameWebsite == null) {
|
||||
this.usernameOptions.subaddressType = this.usernameOptions.catchallType = "random";
|
||||
} else {
|
||||
this.usernameOptions.website = this.usernameWebsite;
|
||||
const websiteOption = { name: this.i18nService.t("websiteName"), value: "website-name" };
|
||||
this.subaddressOptions.push(websiteOption);
|
||||
this.catchallOptions.push(websiteOption);
|
||||
}
|
||||
|
||||
async typeChanged() {
|
||||
await this.stateService.setGeneratorOptions({ type: this.type } as GeneratorOptions);
|
||||
if (this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
await this.load(qParams.type as GeneratorType);
|
||||
});
|
||||
|
||||
// Load all sends if sync completed in background
|
||||
this.broadcasterService.subscribe(ComponentId, (message: any) => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ngZone.run(async () => {
|
||||
switch (message.command) {
|
||||
case "syncCompleted":
|
||||
window.setTimeout(() => {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.load();
|
||||
}, 500);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.changeDetectorRef.detectChanges();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcasterService.unsubscribe(ComponentId);
|
||||
}
|
||||
|
||||
async typeChanged() {
|
||||
if (this.regenerateWithoutButtonPress()) {
|
||||
await this.regenerate();
|
||||
}
|
||||
await this.savePasswordOptions();
|
||||
}
|
||||
|
||||
async regenerate() {
|
||||
if (this.type === "password") {
|
||||
await this.regeneratePassword();
|
||||
|
@ -204,9 +249,19 @@ export class GeneratorComponent implements OnInit {
|
|||
}
|
||||
|
||||
async savePasswordOptions(regenerate = true) {
|
||||
// map navigation state into generator type
|
||||
const restoreType = this.passwordOptions.type;
|
||||
if (this.type === "username") {
|
||||
this.passwordOptions.type = this.type;
|
||||
}
|
||||
|
||||
// save options
|
||||
await this.normalizePasswordOptions();
|
||||
await this.passwordGenerationService.saveOptions(this.passwordOptions);
|
||||
|
||||
// restore the original format
|
||||
this.passwordOptions.type = restoreType;
|
||||
|
||||
if (regenerate && this.regenerateWithoutButtonPress()) {
|
||||
await this.regeneratePassword();
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export class PasswordGeneratorPolicyOptions extends Domain {
|
|||
*/
|
||||
inEffect() {
|
||||
return (
|
||||
this.defaultType !== "" ||
|
||||
this.defaultType ||
|
||||
this.minLength > 0 ||
|
||||
this.numberCount > 0 ||
|
||||
this.specialCount > 0 ||
|
||||
|
|
|
@ -58,13 +58,15 @@ import { RemoveRefreshTokenMigratedFlagMigrator } from "./migrations/58-remove-r
|
|||
import { KdfConfigMigrator } from "./migrations/59-move-kdf-config-to-state-provider";
|
||||
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
|
||||
import { KnownAccountsMigrator } from "./migrations/60-known-accounts";
|
||||
import { PasswordOptionsMigrator } from "./migrations/61-migrate-password-settings";
|
||||
import { GeneratorHistoryMigrator } from "./migrations/62-migrate-generator-history";
|
||||
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 60;
|
||||
export const CURRENT_VERSION = 62;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
|
@ -126,7 +128,9 @@ export function createMigrationBuilder() {
|
|||
.with(CipherServiceMigrator, 56, 57)
|
||||
.with(RemoveRefreshTokenMigratedFlagMigrator, 57, 58)
|
||||
.with(KdfConfigMigrator, 58, 59)
|
||||
.with(KnownAccountsMigrator, 59, CURRENT_VERSION);
|
||||
.with(KnownAccountsMigrator, 59, 60)
|
||||
.with(PasswordOptionsMigrator, 60, 61)
|
||||
.with(GeneratorHistoryMigrator, 61, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import { MigrationHelper } from "../migration-helper";
|
||||
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||
|
||||
import {
|
||||
ExpectedOptions,
|
||||
PasswordOptionsMigrator,
|
||||
NAVIGATION,
|
||||
PASSWORD,
|
||||
PASSPHRASE,
|
||||
} from "./61-migrate-password-settings";
|
||||
|
||||
function migrationHelper(passwordGenerationOptions: ExpectedOptions) {
|
||||
const helper = mockMigrationHelper(
|
||||
{
|
||||
global_account_accounts: {
|
||||
SomeAccount: {
|
||||
email: "SomeAccount",
|
||||
name: "SomeAccount",
|
||||
emailVerified: true,
|
||||
},
|
||||
},
|
||||
SomeAccount: {
|
||||
settings: {
|
||||
passwordGenerationOptions,
|
||||
this: {
|
||||
looks: "important",
|
||||
},
|
||||
},
|
||||
cant: {
|
||||
touch: "this",
|
||||
},
|
||||
},
|
||||
},
|
||||
60,
|
||||
);
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
function expectOtherSettingsRemain(helper: MigrationHelper) {
|
||||
expect(helper.set).toHaveBeenCalledWith("SomeAccount", {
|
||||
settings: {
|
||||
this: {
|
||||
looks: "important",
|
||||
},
|
||||
},
|
||||
cant: {
|
||||
touch: "this",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("PasswordOptionsMigrator", () => {
|
||||
describe("migrate", () => {
|
||||
it("migrates generator type", async () => {
|
||||
const helper = migrationHelper({
|
||||
type: "password",
|
||||
});
|
||||
helper.getFromUser.mockReturnValue(Promise.resolve({ some: { other: "data" } }));
|
||||
const migrator = new PasswordOptionsMigrator(60, 61);
|
||||
|
||||
await migrator.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SomeAccount", NAVIGATION, {
|
||||
type: "password",
|
||||
some: { other: "data" },
|
||||
});
|
||||
expectOtherSettingsRemain(helper);
|
||||
});
|
||||
|
||||
it("migrates password settings", async () => {
|
||||
const helper = migrationHelper({
|
||||
length: 20,
|
||||
ambiguous: true,
|
||||
uppercase: false,
|
||||
minUppercase: 4,
|
||||
lowercase: true,
|
||||
minLowercase: 3,
|
||||
number: false,
|
||||
minNumber: 2,
|
||||
special: true,
|
||||
minSpecial: 1,
|
||||
});
|
||||
const migrator = new PasswordOptionsMigrator(60, 61);
|
||||
|
||||
await migrator.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SomeAccount", PASSWORD, {
|
||||
length: 20,
|
||||
ambiguous: true,
|
||||
uppercase: false,
|
||||
minUppercase: 4,
|
||||
lowercase: true,
|
||||
minLowercase: 3,
|
||||
number: false,
|
||||
minNumber: 2,
|
||||
special: true,
|
||||
minSpecial: 1,
|
||||
});
|
||||
expectOtherSettingsRemain(helper);
|
||||
});
|
||||
|
||||
it("migrates passphrase settings", async () => {
|
||||
const helper = migrationHelper({
|
||||
numWords: 5,
|
||||
wordSeparator: "4",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
const migrator = new PasswordOptionsMigrator(60, 61);
|
||||
|
||||
await migrator.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SomeAccount", PASSPHRASE, {
|
||||
numWords: 5,
|
||||
wordSeparator: "4",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
});
|
||||
expectOtherSettingsRemain(helper);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,150 @@
|
|||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { Migrator } from "../migrator";
|
||||
|
||||
/** settings targeted by migrator */
|
||||
export type AccountType = {
|
||||
settings?: {
|
||||
passwordGenerationOptions?: ExpectedOptions;
|
||||
};
|
||||
};
|
||||
|
||||
export type GeneratorType = "password" | "passphrase" | "username";
|
||||
|
||||
/** username generation options prior to refactoring */
|
||||
export type ExpectedOptions = {
|
||||
type?: GeneratorType;
|
||||
length?: number;
|
||||
minLength?: number;
|
||||
ambiguous?: boolean;
|
||||
uppercase?: boolean;
|
||||
minUppercase?: number;
|
||||
lowercase?: boolean;
|
||||
minLowercase?: number;
|
||||
number?: boolean;
|
||||
minNumber?: number;
|
||||
special?: boolean;
|
||||
minSpecial?: number;
|
||||
numWords?: number;
|
||||
wordSeparator?: string;
|
||||
capitalize?: boolean;
|
||||
includeNumber?: boolean;
|
||||
};
|
||||
|
||||
/** username generation options after refactoring */
|
||||
type ConvertedOptions = {
|
||||
generator: GeneratorNavigation;
|
||||
password: PasswordGenerationOptions;
|
||||
passphrase: PassphraseGenerationOptions;
|
||||
};
|
||||
|
||||
export const NAVIGATION: KeyDefinitionLike = {
|
||||
stateDefinition: {
|
||||
name: "generator",
|
||||
},
|
||||
key: "generatorSettings",
|
||||
};
|
||||
|
||||
export const PASSWORD: KeyDefinitionLike = {
|
||||
stateDefinition: {
|
||||
name: "generator",
|
||||
},
|
||||
key: "passwordGeneratorSettings",
|
||||
};
|
||||
|
||||
export const PASSPHRASE: KeyDefinitionLike = {
|
||||
stateDefinition: {
|
||||
name: "generator",
|
||||
},
|
||||
key: "passphraseGeneratorSettings",
|
||||
};
|
||||
|
||||
export type GeneratorNavigation = {
|
||||
type?: string;
|
||||
};
|
||||
|
||||
export type PassphraseGenerationOptions = {
|
||||
numWords?: number;
|
||||
wordSeparator?: string;
|
||||
capitalize?: boolean;
|
||||
includeNumber?: boolean;
|
||||
};
|
||||
|
||||
export type PasswordGenerationOptions = {
|
||||
length?: number;
|
||||
minLength?: number;
|
||||
ambiguous?: boolean;
|
||||
uppercase?: boolean;
|
||||
minUppercase?: number;
|
||||
lowercase?: boolean;
|
||||
minLowercase?: number;
|
||||
number?: boolean;
|
||||
minNumber?: number;
|
||||
special?: boolean;
|
||||
minSpecial?: number;
|
||||
};
|
||||
|
||||
export class PasswordOptionsMigrator extends Migrator<60, 61> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<AccountType>();
|
||||
|
||||
async function migrateAccount(userId: string, account: AccountType) {
|
||||
const legacyOptions = account?.settings?.passwordGenerationOptions;
|
||||
|
||||
if (legacyOptions) {
|
||||
const converted = convertSettings(legacyOptions);
|
||||
await storeSettings(helper, userId, converted);
|
||||
await deleteSettings(helper, userId, account);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
}
|
||||
|
||||
function convertSettings(options: ExpectedOptions): ConvertedOptions {
|
||||
const password = {
|
||||
length: options.length,
|
||||
ambiguous: options.ambiguous,
|
||||
uppercase: options.uppercase,
|
||||
minUppercase: options.minUppercase,
|
||||
lowercase: options.lowercase,
|
||||
minLowercase: options.minLowercase,
|
||||
number: options.number,
|
||||
minNumber: options.minNumber,
|
||||
special: options.special,
|
||||
minSpecial: options.minSpecial,
|
||||
};
|
||||
|
||||
const generator = {
|
||||
type: options.type,
|
||||
};
|
||||
|
||||
const passphrase = {
|
||||
numWords: options.numWords,
|
||||
wordSeparator: options.wordSeparator,
|
||||
capitalize: options.capitalize,
|
||||
includeNumber: options.includeNumber,
|
||||
};
|
||||
|
||||
return { generator, password, passphrase };
|
||||
}
|
||||
|
||||
async function storeSettings(helper: MigrationHelper, userId: string, converted: ConvertedOptions) {
|
||||
const existing = (await helper.getFromUser(userId, NAVIGATION)) ?? {};
|
||||
const updated = Object.assign(existing, converted.generator);
|
||||
|
||||
await Promise.all([
|
||||
helper.setToUser(userId, NAVIGATION, updated),
|
||||
helper.setToUser(userId, PASSPHRASE, converted.passphrase),
|
||||
helper.setToUser(userId, PASSWORD, converted.password),
|
||||
]);
|
||||
}
|
||||
|
||||
async function deleteSettings(helper: MigrationHelper, userId: string, account: AccountType) {
|
||||
delete account?.settings?.passwordGenerationOptions;
|
||||
await helper.set(userId, account);
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import { MigrationHelper } from "../migration-helper";
|
||||
import { mockMigrationHelper } from "../migration-helper.spec";
|
||||
|
||||
import {
|
||||
EncryptedHistory,
|
||||
GeneratorHistoryMigrator,
|
||||
HISTORY,
|
||||
} from "./62-migrate-generator-history";
|
||||
|
||||
function migrationHelper(encrypted: EncryptedHistory) {
|
||||
const helper = mockMigrationHelper(
|
||||
{
|
||||
authenticatedAccounts: ["SomeAccount"],
|
||||
SomeAccount: {
|
||||
data: {
|
||||
passwordGenerationHistory: {
|
||||
encrypted,
|
||||
},
|
||||
this: {
|
||||
looks: "important",
|
||||
},
|
||||
},
|
||||
cant: {
|
||||
touch: "this",
|
||||
},
|
||||
},
|
||||
},
|
||||
59,
|
||||
);
|
||||
|
||||
return helper;
|
||||
}
|
||||
|
||||
function expectOtherSettingsRemain(helper: MigrationHelper) {
|
||||
expect(helper.set).toHaveBeenCalledWith("SomeAccount", {
|
||||
data: {
|
||||
this: {
|
||||
looks: "important",
|
||||
},
|
||||
},
|
||||
cant: {
|
||||
touch: "this",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("PasswordOptionsMigrator", () => {
|
||||
describe("migrate", () => {
|
||||
it("migrates generator type", async () => {
|
||||
const helper = migrationHelper([{ this: "should be copied" }, { this: "too" }]);
|
||||
const migrator = new GeneratorHistoryMigrator(61, 62);
|
||||
|
||||
await migrator.migrate(helper);
|
||||
|
||||
expect(helper.setToUser).toHaveBeenCalledWith("SomeAccount", HISTORY, [
|
||||
{ this: "should be copied" },
|
||||
{ this: "too" },
|
||||
]);
|
||||
expectOtherSettingsRemain(helper);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import { KeyDefinitionLike, MigrationHelper } from "../migration-helper";
|
||||
import { Migrator } from "../migrator";
|
||||
|
||||
/** settings targeted by migrator */
|
||||
export type AccountType = {
|
||||
data?: {
|
||||
passwordGenerationHistory?: {
|
||||
encrypted: EncryptedHistory;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/** the actual data stored in the history is opaque to the migrator */
|
||||
export type EncryptedHistory = Array<unknown>;
|
||||
|
||||
export const HISTORY: KeyDefinitionLike = {
|
||||
stateDefinition: {
|
||||
name: "generator",
|
||||
},
|
||||
key: "localGeneratorHistoryBuffer",
|
||||
};
|
||||
|
||||
export class GeneratorHistoryMigrator extends Migrator<61, 62> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const accounts = await helper.getAccounts<AccountType>();
|
||||
|
||||
async function migrateAccount(userId: string, account: AccountType) {
|
||||
const data = account?.data?.passwordGenerationHistory;
|
||||
if (data && data.encrypted) {
|
||||
await helper.setToUser(userId, HISTORY, data.encrypted);
|
||||
delete account.data.passwordGenerationHistory;
|
||||
await helper.set(userId, account);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([...accounts.map(({ userId, account }) => migrateAccount(userId, account))]);
|
||||
}
|
||||
|
||||
async rollback(helper: MigrationHelper): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
}
|
|
@ -23,9 +23,6 @@ export abstract class GeneratorStrategy<Options, Policy> {
|
|||
/** Identifies the policy enforced by the generator. */
|
||||
policy: PolicyType;
|
||||
|
||||
/** Length of time in milliseconds to cache the evaluator */
|
||||
cache_ms: number;
|
||||
|
||||
/** Operator function that converts a policy collection observable to a single
|
||||
* policy evaluator observable.
|
||||
* @param policy The policy being evaluated.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { GeneratorHistoryService } from "./generator-history.abstraction";
|
||||
export { GeneratorNavigationService } from "./generator-navigation.service.abstraction";
|
||||
export { GeneratorService } from "./generator.service.abstraction";
|
||||
export { GeneratorStrategy } from "./generator-strategy.abstraction";
|
||||
|
|
|
@ -126,6 +126,8 @@ describe("Password generator service", () => {
|
|||
expect(policy).toBe(evaluator);
|
||||
});
|
||||
|
||||
// FIXME: This test is disabled because the evaluator logic updates conflict with the
|
||||
// skip logic required to work around missing sync events.
|
||||
it("should update the evaluator when the password generator policy changes", async () => {
|
||||
// set up dependencies
|
||||
const state = new BehaviorSubject<Policy[]>([null]);
|
||||
|
|
|
@ -7,6 +7,13 @@ import { UserId } from "../../types/guid";
|
|||
|
||||
import { GeneratorStrategy, GeneratorService, PolicyEvaluator } from "./abstractions";
|
||||
|
||||
type DefaultGeneratorServiceTuning = {
|
||||
/* amount of time to keep the most recent policy after a subscription ends. Once the
|
||||
* cache expires, the ignoreQty and timeoutMs settings apply to the next lookup.
|
||||
*/
|
||||
policyCacheMs: number;
|
||||
};
|
||||
|
||||
/** {@link GeneratorServiceAbstraction} */
|
||||
export class DefaultGeneratorService<Options, Policy> implements GeneratorService<Options, Policy> {
|
||||
/** Instantiates the generator service
|
||||
|
@ -17,8 +24,18 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
|
|||
constructor(
|
||||
private strategy: GeneratorStrategy<Options, Policy>,
|
||||
private policy: PolicyService,
|
||||
) {}
|
||||
tuning: Partial<DefaultGeneratorServiceTuning> = {},
|
||||
) {
|
||||
this.tuning = Object.assign(
|
||||
{
|
||||
// a minute
|
||||
policyCacheMs: 60000,
|
||||
},
|
||||
tuning,
|
||||
);
|
||||
}
|
||||
|
||||
private tuning: DefaultGeneratorServiceTuning;
|
||||
private _evaluators$ = new Map<UserId, Observable<PolicyEvaluator<Policy, Options>>>();
|
||||
|
||||
/** {@link GeneratorService.options$} */
|
||||
|
@ -57,7 +74,7 @@ export class DefaultGeneratorService<Options, Policy> implements GeneratorServic
|
|||
// and reduce GC pressure.
|
||||
share({
|
||||
connector: () => new ReplaySubject(1),
|
||||
resetOnRefCountZero: () => timer(this.strategy.cache_ms),
|
||||
resetOnRefCountZero: () => timer(this.tuning.policyCacheMs),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratedPasswordHistory } from "../password/generated-password-history";
|
||||
|
||||
/** Strategy that decrypts a password history */
|
||||
export class LegacyPasswordHistoryDecryptor {
|
||||
constructor(
|
||||
private userId: UserId,
|
||||
private cryptoService: CryptoService,
|
||||
private encryptService: EncryptService,
|
||||
) {}
|
||||
|
||||
/** Decrypts a password history. */
|
||||
async decrypt(history: GeneratedPasswordHistory[]): Promise<GeneratedPasswordHistory[]> {
|
||||
const key = await this.cryptoService.getUserKey(this.userId);
|
||||
|
||||
// this code uses `decryptToUtf8` because the legacy service does
|
||||
const promises = (history ?? []).map(async (item) => {
|
||||
const encrypted = new EncString(item.password);
|
||||
const decrypted = await this.encryptService.decryptToUtf8(encrypted, key);
|
||||
return new GeneratedPasswordHistory(decrypted, item.date);
|
||||
});
|
||||
|
||||
const decrypted = await Promise.all(promises);
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { mock } from "jest-mock-extended";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { firstValueFrom, of } from "rxjs";
|
||||
|
||||
import { FakeStateProvider, awaitAsync, mockAccountServiceWith } from "../../../../spec";
|
||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
||||
|
@ -24,6 +24,7 @@ describe("LocalGeneratorHistoryService", () => {
|
|||
encryptService.encrypt.mockImplementation((p) => Promise.resolve(p as unknown as EncString));
|
||||
encryptService.decryptToUtf8.mockImplementation((c) => Promise.resolve(c.encryptedString));
|
||||
keyService.getUserKey.mockImplementation(() => Promise.resolve(userKey));
|
||||
keyService.getInMemoryUserKeyFor$.mockImplementation(() => of(true as unknown as UserKey));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -5,12 +5,14 @@ import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
|||
import { SingleUserState, StateProvider } from "../../../platform/state";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { GeneratorHistoryService } from "../abstractions/generator-history.abstraction";
|
||||
import { GENERATOR_HISTORY } from "../key-definitions";
|
||||
import { GENERATOR_HISTORY, GENERATOR_HISTORY_BUFFER } from "../key-definitions";
|
||||
import { BufferedState } from "../state/buffered-state";
|
||||
import { PaddedDataPacker } from "../state/padded-data-packer";
|
||||
import { SecretState } from "../state/secret-state";
|
||||
import { UserKeyEncryptor } from "../state/user-key-encryptor";
|
||||
|
||||
import { GeneratedCredential } from "./generated-credential";
|
||||
import { LegacyPasswordHistoryDecryptor } from "./legacy-password-history-decryptor";
|
||||
import { GeneratorCategory, HistoryServiceOptions } from "./options";
|
||||
|
||||
const OPTIONS_FRAME_SIZE = 2048;
|
||||
|
@ -51,7 +53,7 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService {
|
|||
},
|
||||
{
|
||||
shouldUpdate: (credentials) =>
|
||||
credentials?.some((f) => f.credential !== credential) ?? true,
|
||||
!(credentials?.some((f) => f.credential === credential) ?? false),
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -98,11 +100,12 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService {
|
|||
return state;
|
||||
}
|
||||
|
||||
private createSecretState(userId: UserId) {
|
||||
private createSecretState(userId: UserId): SingleUserState<GeneratedCredential[]> {
|
||||
// construct the encryptor
|
||||
const packer = new PaddedDataPacker(OPTIONS_FRAME_SIZE);
|
||||
const encryptor = new UserKeyEncryptor(this.encryptService, this.keyService, packer);
|
||||
|
||||
// construct the durable state
|
||||
const state = SecretState.from<
|
||||
GeneratedCredential[],
|
||||
number,
|
||||
|
@ -111,6 +114,25 @@ export class LocalGeneratorHistoryService extends GeneratorHistoryService {
|
|||
GeneratedCredential
|
||||
>(userId, GENERATOR_HISTORY, this.stateProvider, encryptor);
|
||||
|
||||
return state;
|
||||
// decryptor is just an algorithm, but it can't run until the key is available;
|
||||
// providing it via an observable makes running it early impossible
|
||||
const decryptor = new LegacyPasswordHistoryDecryptor(
|
||||
userId,
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
);
|
||||
const decryptor$ = this.keyService
|
||||
.getInMemoryUserKeyFor$(userId)
|
||||
.pipe(map((key) => key && decryptor));
|
||||
|
||||
// move data from the old password history once decryptor is available
|
||||
const buffer = new BufferedState(
|
||||
this.stateProvider,
|
||||
GENERATOR_HISTORY_BUFFER,
|
||||
state,
|
||||
decryptor$,
|
||||
);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@ export * from "./abstractions/index";
|
|||
export * from "./password/index";
|
||||
|
||||
export { DefaultGeneratorService } from "./default-generator.service";
|
||||
export { legacyPasswordGenerationServiceFactory } from "./legacy-password-generation.service";
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { GENERATOR_DISK, GENERATOR_MEMORY, UserKeyDefinition } from "../../platform/state";
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { GENERATOR_DISK, UserKeyDefinition } from "../../platform/state";
|
||||
|
||||
import { GeneratedCredential } from "./history/generated-credential";
|
||||
import { LegacyPasswordHistoryDecryptor } from "./history/legacy-password-history-decryptor";
|
||||
import { GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { PassphraseGenerationOptions } from "./passphrase/passphrase-generation-options";
|
||||
import { GeneratedPasswordHistory } from "./password/generated-password-history";
|
||||
import { PasswordGenerationOptions } from "./password/password-generation-options";
|
||||
import { BufferedKeyDefinition } from "./state/buffered-key-definition";
|
||||
import { SecretClassifier } from "./state/secret-classifier";
|
||||
import { SecretKeyDefinition } from "./state/secret-key-definition";
|
||||
import { CatchallGenerationOptions } from "./username/catchall-generator-options";
|
||||
|
@ -18,11 +23,11 @@ import { SubaddressGenerationOptions } from "./username/subaddress-generator-opt
|
|||
|
||||
/** plaintext password generation options */
|
||||
export const GENERATOR_SETTINGS = new UserKeyDefinition<GeneratorNavigation>(
|
||||
GENERATOR_MEMORY,
|
||||
GENERATOR_DISK,
|
||||
"generatorSettings",
|
||||
{
|
||||
deserializer: (value) => value,
|
||||
clearOn: ["lock", "logout"],
|
||||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -146,3 +151,24 @@ export const GENERATOR_HISTORY = SecretKeyDefinition.array(
|
|||
clearOn: ["logout"],
|
||||
},
|
||||
);
|
||||
|
||||
/** encrypted password generation history subject to migration */
|
||||
export const GENERATOR_HISTORY_BUFFER = new BufferedKeyDefinition<
|
||||
GeneratedPasswordHistory[],
|
||||
GeneratedCredential[],
|
||||
LegacyPasswordHistoryDecryptor
|
||||
>(GENERATOR_DISK, "localGeneratorHistoryBuffer", {
|
||||
deserializer(history) {
|
||||
const items = history as Jsonify<GeneratedPasswordHistory>[];
|
||||
return items?.map((h) => new GeneratedPasswordHistory(h.password, h.date));
|
||||
},
|
||||
async isValid(history) {
|
||||
return history && history.length ? true : false;
|
||||
},
|
||||
async map(history, decryptor) {
|
||||
const credentials = await decryptor.decrypt(history);
|
||||
const mapped = credentials.map((c) => new GeneratedCredential(c.password, "password", c.date));
|
||||
return mapped;
|
||||
},
|
||||
clearOn: ["logout"],
|
||||
});
|
||||
|
|
|
@ -8,7 +8,12 @@ import { of } from "rxjs";
|
|||
import { mockAccountServiceWith } from "../../../spec";
|
||||
import { UserId } from "../../types/guid";
|
||||
|
||||
import { GeneratorNavigationService, GeneratorService } from "./abstractions";
|
||||
import {
|
||||
GeneratorHistoryService,
|
||||
GeneratorNavigationService,
|
||||
GeneratorService,
|
||||
} from "./abstractions";
|
||||
import { GeneratedCredential } from "./history";
|
||||
import { LegacyPasswordGenerationService } from "./legacy-password-generation.service";
|
||||
import { DefaultGeneratorNavigation, GeneratorNavigation } from "./navigation/generator-navigation";
|
||||
import { GeneratorNavigationEvaluator } from "./navigation/generator-navigation-evaluator";
|
||||
|
@ -22,6 +27,7 @@ import {
|
|||
import { DisabledPassphraseGeneratorPolicy } from "./passphrase/passphrase-generator-policy";
|
||||
import {
|
||||
DefaultPasswordGenerationOptions,
|
||||
GeneratedPasswordHistory,
|
||||
PasswordGenerationOptions,
|
||||
PasswordGeneratorOptions,
|
||||
PasswordGeneratorOptionsEvaluator,
|
||||
|
@ -97,10 +103,10 @@ function createNavigationGenerator(
|
|||
defaults$(id: UserId) {
|
||||
return of(DefaultGeneratorNavigation);
|
||||
},
|
||||
saveOptions(userId, options) {
|
||||
saveOptions: jest.fn((userId, options) => {
|
||||
savedOptions = options;
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return generator;
|
||||
|
@ -113,7 +119,7 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
describe("generatePassword", () => {
|
||||
it("invokes the inner password generator to generate passwords", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null);
|
||||
const generator = new LegacyPasswordGenerationService(null, null, innerPassword, null, null);
|
||||
const options = { type: "password" } as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassword(options);
|
||||
|
@ -123,7 +129,13 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
|
||||
it("invokes the inner passphrase generator to generate passphrases", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, null, innerPassphrase);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
const options = { type: "passphrase" } as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassword(options);
|
||||
|
@ -135,7 +147,13 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
describe("generatePassphrase", () => {
|
||||
it("invokes the inner passphrase generator", async () => {
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const generator = new LegacyPasswordGenerationService(null, null, null, innerPassphrase);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
const options = {} as PasswordGeneratorOptions;
|
||||
|
||||
await generator.generatePassphrase(options);
|
||||
|
@ -176,14 +194,13 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "passphrase",
|
||||
username: "word",
|
||||
forwarder: "simplelogin",
|
||||
length: 29,
|
||||
minLength: 20,
|
||||
ambiguous: false,
|
||||
|
@ -212,12 +229,13 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [result] = await generator.getOptions();
|
||||
|
||||
expect(result).toEqual({
|
||||
...DefaultGeneratorNavigation,
|
||||
type: DefaultGeneratorNavigation.type,
|
||||
...DefaultPassphraseGenerationOptions,
|
||||
...DefaultPasswordGenerationOptions,
|
||||
});
|
||||
|
@ -256,6 +274,7 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.getOptions();
|
||||
|
@ -301,6 +320,7 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
|
@ -340,6 +360,7 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [result] = await generator.enforcePasswordGeneratorPoliciesOnOptions(options);
|
||||
|
@ -385,6 +406,7 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
|
||||
const [, policy] = await generator.enforcePasswordGeneratorPoliciesOnOptions({});
|
||||
|
@ -416,11 +438,10 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
const options = {
|
||||
type: "password" as const,
|
||||
username: "word" as const,
|
||||
forwarder: "simplelogin" as const,
|
||||
length: 29,
|
||||
minLength: 20,
|
||||
ambiguous: false,
|
||||
|
@ -450,11 +471,10 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
username: "word" as const,
|
||||
forwarder: "simplelogin" as const,
|
||||
numWords: 10,
|
||||
wordSeparator: "-",
|
||||
capitalize: true,
|
||||
|
@ -466,5 +486,78 @@ describe("LegacyPasswordGenerationService", () => {
|
|||
|
||||
expect(result).toMatchObject(options);
|
||||
});
|
||||
|
||||
it("preserves saved navigation options", async () => {
|
||||
const innerPassword = createPasswordGenerator();
|
||||
const innerPassphrase = createPassphraseGenerator();
|
||||
const navigation = createNavigationGenerator({
|
||||
type: "password",
|
||||
username: "forwarded",
|
||||
forwarder: "firefoxrelay",
|
||||
});
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
innerPassword,
|
||||
innerPassphrase,
|
||||
null,
|
||||
);
|
||||
const options = {
|
||||
type: "passphrase" as const,
|
||||
numWords: 10,
|
||||
wordSeparator: "-",
|
||||
capitalize: true,
|
||||
includeNumber: false,
|
||||
};
|
||||
|
||||
await generator.saveOptions(options);
|
||||
|
||||
expect(navigation.saveOptions).toHaveBeenCalledWith(SomeUser, {
|
||||
type: "passphrase",
|
||||
username: "forwarded",
|
||||
forwarder: "firefoxrelay",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getHistory", () => {
|
||||
it("gets the active user's history from the history service", async () => {
|
||||
const history = mock<GeneratorHistoryService>();
|
||||
history.credentials$.mockReturnValue(
|
||||
of([new GeneratedCredential("foo", "password", new Date(100))]),
|
||||
);
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
history,
|
||||
);
|
||||
|
||||
const result = await generator.getHistory();
|
||||
|
||||
expect(history.credentials$).toHaveBeenCalledWith(SomeUser);
|
||||
expect(result).toEqual([new GeneratedPasswordHistory("foo", 100)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addHistory", () => {
|
||||
it("adds a history item as a password credential", async () => {
|
||||
const history = mock<GeneratorHistoryService>();
|
||||
const accountService = mockAccountServiceWith(SomeUser);
|
||||
const generator = new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
history,
|
||||
);
|
||||
|
||||
await generator.addHistory("foo");
|
||||
|
||||
expect(history.track).toHaveBeenCalledWith(SomeUser, "foo", "password");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,11 +4,18 @@ import { PolicyService } from "../../admin-console/abstractions/policy/policy.se
|
|||
import { PasswordGeneratorPolicyOptions } from "../../admin-console/models/domain/password-generator-policy-options";
|
||||
import { AccountService } from "../../auth/abstractions/account.service";
|
||||
import { CryptoService } from "../../platform/abstractions/crypto.service";
|
||||
import { EncryptService } from "../../platform/abstractions/encrypt.service";
|
||||
import { StateProvider } from "../../platform/state";
|
||||
|
||||
import { GeneratorService, GeneratorNavigationService } from "./abstractions";
|
||||
import {
|
||||
GeneratorHistoryService,
|
||||
GeneratorService,
|
||||
GeneratorNavigationService,
|
||||
} from "./abstractions";
|
||||
import { PasswordGenerationServiceAbstraction } from "./abstractions/password-generation.service.abstraction";
|
||||
import { DefaultGeneratorService } from "./default-generator.service";
|
||||
import { LocalGeneratorHistoryService } from "./history/local-generator-history.service";
|
||||
import { GeneratorNavigation } from "./navigation";
|
||||
import { DefaultGeneratorNavigationService } from "./navigation/default-generator-navigation.service";
|
||||
import {
|
||||
PassphraseGenerationOptions,
|
||||
|
@ -16,6 +23,7 @@ import {
|
|||
PassphraseGeneratorStrategy,
|
||||
} from "./passphrase";
|
||||
import {
|
||||
GeneratedPasswordHistory,
|
||||
PasswordGenerationOptions,
|
||||
PasswordGenerationService,
|
||||
PasswordGeneratorOptions,
|
||||
|
@ -23,7 +31,14 @@ import {
|
|||
PasswordGeneratorStrategy,
|
||||
} from "./password";
|
||||
|
||||
type MappedOptions = {
|
||||
generator: GeneratorNavigation;
|
||||
password: PasswordGenerationOptions;
|
||||
passphrase: PassphraseGenerationOptions;
|
||||
};
|
||||
|
||||
export function legacyPasswordGenerationServiceFactory(
|
||||
encryptService: EncryptService,
|
||||
cryptoService: CryptoService,
|
||||
policyService: PolicyService,
|
||||
accountService: AccountService,
|
||||
|
@ -45,7 +60,15 @@ export function legacyPasswordGenerationServiceFactory(
|
|||
|
||||
const navigation = new DefaultGeneratorNavigationService(stateProvider, policyService);
|
||||
|
||||
return new LegacyPasswordGenerationService(accountService, navigation, passwords, passphrases);
|
||||
const history = new LocalGeneratorHistoryService(encryptService, cryptoService, stateProvider);
|
||||
|
||||
return new LegacyPasswordGenerationService(
|
||||
accountService,
|
||||
navigation,
|
||||
passwords,
|
||||
passphrases,
|
||||
history,
|
||||
);
|
||||
}
|
||||
|
||||
/** Adapts the generator 2.0 design to 1.0 angular services. */
|
||||
|
@ -61,6 +84,7 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic
|
|||
PassphraseGenerationOptions,
|
||||
PassphraseGeneratorPolicy
|
||||
>,
|
||||
private readonly history: GeneratorHistoryService,
|
||||
) {}
|
||||
|
||||
generatePassword(options: PasswordGeneratorOptions) {
|
||||
|
@ -102,12 +126,11 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic
|
|||
generatorDefaults,
|
||||
generatorEvaluator,
|
||||
]) => {
|
||||
const options: PasswordGeneratorOptions = Object.assign(
|
||||
{},
|
||||
passwordOptions ?? passwordDefaults,
|
||||
passphraseOptions ?? passphraseDefaults,
|
||||
generatorOptions ?? generatorDefaults,
|
||||
);
|
||||
const options = this.toPasswordGeneratorOptions({
|
||||
password: passwordOptions ?? passwordDefaults,
|
||||
passphrase: passphraseOptions ?? passphraseDefaults,
|
||||
generator: generatorOptions ?? generatorDefaults,
|
||||
});
|
||||
|
||||
const policy = Object.assign(
|
||||
new PasswordGeneratorPolicyOptions(),
|
||||
|
@ -168,17 +191,93 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic
|
|||
}
|
||||
|
||||
async saveOptions(options: PasswordGeneratorOptions) {
|
||||
const stored = this.toStoredOptions(options);
|
||||
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
|
||||
|
||||
await this.navigation.saveOptions(activeAccount.id, options);
|
||||
if (options.type === "password") {
|
||||
await this.passwords.saveOptions(activeAccount.id, options);
|
||||
} else {
|
||||
await this.passphrases.saveOptions(activeAccount.id, options);
|
||||
}
|
||||
// generator settings needs to preserve whether password or passphrase is selected,
|
||||
// so `navigationOptions` is mutated.
|
||||
const navigationOptions$ = zip(
|
||||
this.navigation.options$(activeAccount.id),
|
||||
this.navigation.defaults$(activeAccount.id),
|
||||
).pipe(map(([options, defaults]) => options ?? defaults));
|
||||
let navigationOptions = await firstValueFrom(navigationOptions$);
|
||||
navigationOptions = Object.assign(navigationOptions, stored.generator);
|
||||
await this.navigation.saveOptions(activeAccount.id, navigationOptions);
|
||||
|
||||
// overwrite all other settings with latest values
|
||||
await this.passwords.saveOptions(activeAccount.id, stored.password);
|
||||
await this.passphrases.saveOptions(activeAccount.id, stored.passphrase);
|
||||
}
|
||||
|
||||
getHistory: () => Promise<any[]>;
|
||||
addHistory: (password: string) => Promise<void>;
|
||||
clear: (userId?: string) => Promise<void>;
|
||||
private toStoredOptions(options: PasswordGeneratorOptions): MappedOptions {
|
||||
return {
|
||||
generator: {
|
||||
type: options.type,
|
||||
},
|
||||
password: {
|
||||
length: options.length,
|
||||
minLength: options.minLength,
|
||||
ambiguous: options.ambiguous,
|
||||
uppercase: options.uppercase,
|
||||
minUppercase: options.minUppercase,
|
||||
lowercase: options.lowercase,
|
||||
minLowercase: options.minLowercase,
|
||||
number: options.number,
|
||||
minNumber: options.minNumber,
|
||||
special: options.special,
|
||||
minSpecial: options.minSpecial,
|
||||
},
|
||||
passphrase: {
|
||||
numWords: options.numWords,
|
||||
wordSeparator: options.wordSeparator,
|
||||
capitalize: options.capitalize,
|
||||
includeNumber: options.includeNumber,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private toPasswordGeneratorOptions(options: MappedOptions): PasswordGeneratorOptions {
|
||||
return {
|
||||
type: options.generator.type,
|
||||
length: options.password.length,
|
||||
minLength: options.password.minLength,
|
||||
ambiguous: options.password.ambiguous,
|
||||
uppercase: options.password.uppercase,
|
||||
minUppercase: options.password.minUppercase,
|
||||
lowercase: options.password.lowercase,
|
||||
minLowercase: options.password.minLowercase,
|
||||
number: options.password.number,
|
||||
minNumber: options.password.minNumber,
|
||||
special: options.password.special,
|
||||
minSpecial: options.password.minSpecial,
|
||||
numWords: options.passphrase.numWords,
|
||||
wordSeparator: options.passphrase.wordSeparator,
|
||||
capitalize: options.passphrase.capitalize,
|
||||
includeNumber: options.passphrase.includeNumber,
|
||||
};
|
||||
}
|
||||
|
||||
getHistory() {
|
||||
const history = this.accountService.activeAccount$.pipe(
|
||||
concatMap((account) => this.history.credentials$(account.id)),
|
||||
map((history) =>
|
||||
history.map(
|
||||
(item) => new GeneratedPasswordHistory(item.credential, item.generationDate.valueOf()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return firstValueFrom(history);
|
||||
}
|
||||
|
||||
async addHistory(password: string) {
|
||||
const account = await firstValueFrom(this.accountService.activeAccount$);
|
||||
// legacy service doesn't distinguish credential types
|
||||
await this.history.track(account.id, password, "password");
|
||||
}
|
||||
|
||||
clear() {
|
||||
// clear is handled by the state provider's "clearon" configuration
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,15 +84,6 @@ describe("Password generation strategy", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PassphraseGeneratorStrategy(legacy, null);
|
||||
|
||||
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
|
|
|
@ -19,8 +19,6 @@ import {
|
|||
leastPrivilege,
|
||||
} from "./passphrase-generator-policy";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** {@link GeneratorStrategy} */
|
||||
export class PassphraseGeneratorStrategy
|
||||
implements GeneratorStrategy<PassphraseGenerationOptions, PassphraseGeneratorPolicy>
|
||||
|
@ -49,11 +47,6 @@ export class PassphraseGeneratorStrategy
|
|||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.cache_ms} */
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(
|
||||
|
|
|
@ -93,15 +93,6 @@ describe("Password generation strategy", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
const strategy = new PasswordGeneratorStrategy(legacy, null);
|
||||
|
||||
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<PasswordGenerationServiceAbstraction>();
|
||||
|
|
|
@ -19,8 +19,6 @@ import {
|
|||
leastPrivilege,
|
||||
} from "./password-generator-policy";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** {@link GeneratorStrategy} */
|
||||
export class PasswordGeneratorStrategy
|
||||
implements GeneratorStrategy<PasswordGenerationOptions, PasswordGeneratorPolicy>
|
||||
|
@ -48,10 +46,6 @@ export class PasswordGeneratorStrategy
|
|||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(
|
||||
|
|
|
@ -59,15 +59,6 @@ describe("Email subaddress list generation strategy", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
const strategy = new CatchallGeneratorStrategy(legacy, null);
|
||||
|
||||
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
|
|
|
@ -11,8 +11,6 @@ import { NoPolicy } from "../no-policy";
|
|||
|
||||
import { CatchallGenerationOptions, DefaultCatchallOptions } from "./catchall-generator-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** Strategy for creating usernames using a catchall email address */
|
||||
export class CatchallGeneratorStrategy
|
||||
implements GeneratorStrategy<CatchallGenerationOptions, NoPolicy>
|
||||
|
@ -42,11 +40,6 @@ export class CatchallGeneratorStrategy
|
|||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.cache_ms} */
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(map((_) => new DefaultPolicyEvaluator<CatchallGenerationOptions>()));
|
||||
|
|
|
@ -59,15 +59,6 @@ describe("EFF long word list generation strategy", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
const strategy = new EffUsernameGeneratorStrategy(legacy, null);
|
||||
|
||||
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
|
|
|
@ -14,8 +14,6 @@ import {
|
|||
EffUsernameGenerationOptions,
|
||||
} from "./eff-username-generator-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** Strategy for creating usernames from the EFF wordlist */
|
||||
export class EffUsernameGeneratorStrategy
|
||||
implements GeneratorStrategy<EffUsernameGenerationOptions, NoPolicy>
|
||||
|
@ -45,11 +43,6 @@ export class EffUsernameGeneratorStrategy
|
|||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.cache_ms} */
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(map((_) => new DefaultPolicyEvaluator<EffUsernameGenerationOptions>()));
|
||||
|
|
|
@ -16,7 +16,6 @@ import { UserKeyEncryptor } from "../state/user-key-encryptor";
|
|||
|
||||
import { ApiOptions } from "./options/forwarder-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
const OPTIONS_FRAME_SIZE = 512;
|
||||
|
||||
/** An email forwarding service configurable through an API. */
|
||||
|
@ -37,8 +36,6 @@ export abstract class ForwarderGeneratorStrategy<
|
|||
// Uses password generator since there aren't policies
|
||||
// specific to usernames.
|
||||
this.policy = PolicyType.PasswordGenerator;
|
||||
|
||||
this.cache_ms = ONE_MINUTE;
|
||||
}
|
||||
|
||||
private durableStates = new Map<UserId, SingleUserState<Options>>();
|
||||
|
|
|
@ -62,15 +62,6 @@ describe("Email subaddress list generation strategy", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("cache_ms", () => {
|
||||
it("should be a positive non-zero number", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
const strategy = new SubaddressGeneratorStrategy(legacy, null);
|
||||
|
||||
expect(strategy.cache_ms).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("policy", () => {
|
||||
it("should use password generator policy", () => {
|
||||
const legacy = mock<UsernameGenerationServiceAbstraction>();
|
||||
|
|
|
@ -14,8 +14,6 @@ import {
|
|||
SubaddressGenerationOptions,
|
||||
} from "./subaddress-generator-options";
|
||||
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
/** Strategy for creating an email subaddress
|
||||
* @remarks The subaddress is the part following the `+`.
|
||||
* For example, if the email address is `jd+xyz@domain.io`,
|
||||
|
@ -49,11 +47,6 @@ export class SubaddressGeneratorStrategy
|
|||
return PolicyType.PasswordGenerator;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.cache_ms} */
|
||||
get cache_ms() {
|
||||
return ONE_MINUTE;
|
||||
}
|
||||
|
||||
/** {@link GeneratorStrategy.toEvaluator} */
|
||||
toEvaluator() {
|
||||
return pipe(map((_) => new DefaultPolicyEvaluator<SubaddressGenerationOptions>()));
|
||||
|
|
Loading…
Reference in New Issue