1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

Merge branch 'main' into ps/PM-7846-rust-ipc

This commit is contained in:
Daniel García
2024-07-09 18:24:51 +02:00
99 changed files with 2369 additions and 791 deletions

View File

@@ -0,0 +1,151 @@
import { CommonModule } from "@angular/common";
import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { TwoFactorAuthAuthenticatorComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-options.component";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/platform/sync";
import {
ButtonModule,
FormFieldModule,
AsyncActionsModule,
CheckboxModule,
DialogModule,
LinkModule,
TypographyModule,
DialogService,
} from "@bitwarden/components";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
} from "../../../../../libs/auth/src/common/abstractions";
import { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
@Component({
standalone: true,
templateUrl:
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent implements OnInit {
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
dialogService: DialogService,
protected route: ActivatedRoute,
logService: LogService,
protected twoFactorService: TwoFactorService,
loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window,
private syncService: SyncService,
private messagingService: MessagingService,
) {
super(
loginStrategyService,
router,
i18nService,
platformUtilsService,
environmentService,
dialogService,
route,
logService,
twoFactorService,
loginEmailService,
userDecryptionOptionsService,
ssoLoginService,
configService,
masterPasswordService,
accountService,
formBuilder,
win,
);
super.onSuccessfulLoginTdeNavigate = async () => {
this.win.close();
};
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
async ngOnInit(): Promise<void> {
await super.ngOnInit();
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
// WebAuthn fallback response
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
super.onSuccessfulLogin = async () => {
// 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.syncService.fullSync(true);
this.messagingService.send("reloadPopup");
window.close();
};
this.remember = this.route.snapshot.paramMap.get("remember") === "true";
await this.submit();
return;
}
if (await BrowserPopupUtils.inPopout(this.win)) {
this.selectedProviderType = TwoFactorProviderType.Email;
}
// WebAuthn prompt appears inside the popup on linux, and requires a larger popup width
// than usual to avoid cutting off the dialog.
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.add("linux-webauthn");
}
}
async ngOnDestroy() {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.remove("linux-webauthn");
}
}
async isLinux() {
return (await BrowserApi.getPlatformInfo()).os === "linux";
}
}

View File

@@ -17,7 +17,6 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service";
import { BrowserApi } from "../../platform/browser/browser-api";
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum";
import { FormData } from "../services/abstractions/autofill.service";
import AutofillService from "../services/autofill.service";
@@ -49,7 +48,6 @@ describe("NotificationBackground", () => {
const authService = mock<AuthService>();
const policyService = mock<PolicyService>();
const folderService = mock<FolderService>();
const stateService = mock<DefaultBrowserStateService>();
const userNotificationSettingsService = mock<UserNotificationSettingsService>();
const domainSettingsService = mock<DomainSettingsService>();
const environmentService = mock<EnvironmentService>();
@@ -64,7 +62,6 @@ describe("NotificationBackground", () => {
authService,
policyService,
folderService,
stateService,
userNotificationSettingsService,
domainSettingsService,
environmentService,

View File

@@ -23,7 +23,6 @@ import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
import { BrowserApi } from "../../platform/browser/browser-api";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import { openAddEditVaultItemPopout } from "../../vault/popup/utils/vault-popout-window";
import { NotificationQueueMessageType } from "../enums/notification-queue-message-type.enum";
import { AutofillService } from "../services/abstractions/autofill.service";
@@ -76,7 +75,6 @@ export default class NotificationBackground {
private authService: AuthService,
private policyService: PolicyService,
private folderService: FolderService,
private stateService: BrowserStateService,
private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction,
private domainSettingsService: DomainSettingsService,
private environmentService: EnvironmentService,

View File

@@ -33,7 +33,6 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
import { BrowserApi } from "../../platform/browser/browser-api";
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
import { BrowserPlatformUtilsService } from "../../platform/services/platform-utils/browser-platform-utils.service";
import { AutofillService } from "../services/abstractions/autofill.service";
import {
@@ -73,7 +72,6 @@ describe("OverlayBackground", () => {
urls: { icons: "https://icons.bitwarden.com/" },
}),
);
const stateService = mock<DefaultBrowserStateService>();
const autofillSettingsService = mock<AutofillSettingsService>();
const i18nService = mock<I18nService>();
const platformUtilsService = mock<BrowserPlatformUtilsService>();
@@ -104,7 +102,6 @@ describe("OverlayBackground", () => {
authService,
environmentService,
domainSettingsService,
stateService,
autofillSettingsService,
i18nService,
platformUtilsService,

View File

@@ -9,7 +9,6 @@ import { InlineMenuVisibilitySetting } from "@bitwarden/common/autofill/types";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -101,7 +100,6 @@ class OverlayBackground implements OverlayBackgroundInterface {
private authService: AuthService,
private environmentService: EnvironmentService,
private domainSettingsService: DomainSettingsService,
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService,
private platformUtilsService: PlatformUtilsService,

View File

@@ -6,16 +6,15 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import { MainContextMenuHandler } from "./main-context-menu-handler";
describe("context-menu", () => {
let stateService: MockProxy<BrowserStateService>;
let stateService: MockProxy<StateService>;
let autofillSettingsService: MockProxy<AutofillSettingsServiceAbstraction>;
let i18nService: MockProxy<I18nService>;
let logService: MockProxy<LogService>;

View File

@@ -20,12 +20,11 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { BrowserStateService } from "../../platform/services/abstractions/browser-state.service";
import { InitContextMenuItems } from "./abstractions/main-context-menu-handler";
export class MainContextMenuHandler {
@@ -143,7 +142,7 @@ export class MainContextMenuHandler {
];
constructor(
private stateService: BrowserStateService,
private stateService: StateService,
private autofillSettingsService: AutofillSettingsServiceAbstraction,
private i18nService: I18nService,
private logService: LogService,

View File

@@ -7,8 +7,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { VaultTimeoutAction } from "@bitwarden/common/enums/vault-timeout-action.enum";
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
const IdleInterval = 60 * 5; // 5 minutes
export default class IdleBackground {
@@ -18,7 +16,6 @@ export default class IdleBackground {
constructor(
private vaultTimeoutService: VaultTimeoutService,
private stateService: BrowserStateService,
private notificationsService: NotificationsService,
private accountService: AccountService,
private vaultTimeoutSettingsService: VaultTimeoutSettingsService,

View File

@@ -86,6 +86,7 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import {
AbstractStorageService,
ObservableStorageService,
@@ -101,6 +102,7 @@ import { Message, MessageListener, MessageSender } from "@bitwarden/common/platf
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { Lazy } from "@bitwarden/common/platform/misc/lazy";
import { clearCaches } from "@bitwarden/common/platform/misc/sequentialize";
import { Account } from "@bitwarden/common/platform/models/domain/account";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { AppIdService } from "@bitwarden/common/platform/services/app-id.service";
@@ -116,6 +118,7 @@ import { FileUploadService } from "@bitwarden/common/platform/services/file-uplo
import { KeyGenerationService } from "@bitwarden/common/platform/services/key-generation.service";
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
@@ -206,7 +209,6 @@ import { Fido2Background } from "../autofill/fido2/background/fido2.background";
import { AutofillService as AutofillServiceAbstraction } from "../autofill/services/abstractions/autofill.service";
import AutofillService from "../autofill/services/autofill.service";
import { SafariApp } from "../browser/safariApp";
import { Account } from "../models/account";
import { BrowserApi } from "../platform/browser/browser-api";
import { flagEnabled } from "../platform/flags";
import { UpdateBadge } from "../platform/listeners/update-badge";
@@ -215,13 +217,11 @@ import { ChromeMessageSender } from "../platform/messaging/chrome-message.sender
/* eslint-enable no-restricted-imports */
import { OffscreenDocumentService } from "../platform/offscreen-document/abstractions/offscreen-document";
import { DefaultOffscreenDocumentService } from "../platform/offscreen-document/offscreen-document.service";
import { BrowserStateService as StateServiceAbstraction } from "../platform/services/abstractions/browser-state.service";
import { BrowserCryptoService } from "../platform/services/browser-crypto.service";
import { BrowserEnvironmentService } from "../platform/services/browser-environment.service";
import BrowserLocalStorageService from "../platform/services/browser-local-storage.service";
import BrowserMemoryStorageService from "../platform/services/browser-memory-storage.service";
import { BrowserScriptInjectorService } from "../platform/services/browser-script-injector.service";
import { DefaultBrowserStateService } from "../platform/services/default-browser-state.service";
import I18nService from "../platform/services/i18n.service";
import { LocalBackedSessionStorageService } from "../platform/services/local-backed-session-storage.service";
import { BackgroundPlatformUtilsService } from "../platform/services/platform-utils/background-platform-utils.service";
@@ -540,7 +540,7 @@ export default class MainBackground {
ClientType.Browser,
);
this.stateService = new DefaultBrowserStateService(
this.stateService = new StateService(
this.storageService,
this.secureStorageService,
this.memoryStorageService,
@@ -968,7 +968,6 @@ export default class MainBackground {
this.messagingService,
this.platformUtilsService,
systemUtilsServiceReloadCallback,
this.stateService,
this.autofillSettingsService,
this.vaultTimeoutSettingsService,
this.biometricStateService,
@@ -1028,7 +1027,6 @@ export default class MainBackground {
this.authService,
this.policyService,
this.folderService,
this.stateService,
this.userNotificationSettingsService,
this.domainSettingsService,
this.environmentService,
@@ -1042,7 +1040,6 @@ export default class MainBackground {
this.authService,
this.environmentService,
this.domainSettingsService,
this.stateService,
this.autofillSettingsService,
this.i18nService,
this.platformUtilsService,
@@ -1100,7 +1097,6 @@ export default class MainBackground {
this.idleBackground = new IdleBackground(
this.vaultTimeoutService,
this.stateService,
this.notificationsService,
this.accountService,
this.vaultTimeoutSettingsService,
@@ -1227,11 +1223,6 @@ export default class MainBackground {
async switchAccount(userId: UserId) {
let nextAccountStatus: AuthenticationStatus;
try {
const currentlyActiveAccount = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((account) => account?.id)),
);
// can be removed once password generation history is migrated to state providers
await this.stateService.clearDecryptedData(currentlyActiveAccount);
// HACK to ensure account is switched before proceeding
const switchPromise = firstValueFrom(
this.accountService.activeAccount$.pipe(

View File

@@ -1,36 +0,0 @@
import { Jsonify } from "type-fest";
import { Account as BaseAccount } from "@bitwarden/common/platform/models/domain/account";
import { BrowserComponentState } from "./browserComponentState";
import { BrowserGroupingsComponentState } from "./browserGroupingsComponentState";
import { BrowserSendComponentState } from "./browserSendComponentState";
export class Account extends BaseAccount {
groupings?: BrowserGroupingsComponentState;
send?: BrowserSendComponentState;
ciphers?: BrowserComponentState;
sendType?: BrowserComponentState;
constructor(init: Partial<Account>) {
super(init);
this.groupings = init?.groupings ?? new BrowserGroupingsComponentState();
this.send = init?.send ?? new BrowserSendComponentState();
this.ciphers = init?.ciphers ?? new BrowserComponentState();
this.sendType = init?.sendType ?? new BrowserComponentState();
}
static fromJSON(json: Jsonify<Account>): Account {
if (json == null) {
return null;
}
return Object.assign(new Account({}), json, super.fromJSON(json), {
groupings: BrowserGroupingsComponentState.fromJSON(json.groupings),
send: BrowserSendComponentState.fromJSON(json.send),
ciphers: BrowserComponentState.fromJSON(json.ciphers),
sendType: BrowserComponentState.fromJSON(json.sendType),
});
}
}

View File

@@ -1,5 +0,0 @@
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { Account } from "../../../models/account";
export abstract class BrowserStateService extends BaseStateServiceAbstraction<Account> {}

View File

@@ -1,77 +0,0 @@
import { mock, MockProxy } from "jest-mock-extended";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { State } from "@bitwarden/common/platform/models/domain/state";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { mockAccountServiceWith } from "@bitwarden/common/spec";
import { UserId } from "@bitwarden/common/types/guid";
import { Account } from "../../models/account";
import { DefaultBrowserStateService } from "./default-browser-state.service";
describe("Browser State Service", () => {
let secureStorageService: MockProxy<AbstractStorageService>;
let diskStorageService: MockProxy<AbstractStorageService>;
let logService: MockProxy<LogService>;
let stateFactory: MockProxy<StateFactory<GlobalState, Account>>;
let environmentService: MockProxy<EnvironmentService>;
let tokenService: MockProxy<TokenService>;
let migrationRunner: MockProxy<MigrationRunner>;
let state: State<GlobalState, Account>;
const userId = "userId" as UserId;
const accountService = mockAccountServiceWith(userId);
let sut: DefaultBrowserStateService;
beforeEach(() => {
secureStorageService = mock();
diskStorageService = mock();
logService = mock();
stateFactory = mock();
environmentService = mock();
tokenService = mock();
migrationRunner = mock();
state = new State(new GlobalState());
state.accounts[userId] = new Account({
profile: { userId: userId },
});
});
afterEach(() => {
jest.resetAllMocks();
});
describe("state methods", () => {
let memoryStorageService: MockProxy<AbstractStorageService>;
beforeEach(() => {
memoryStorageService = mock();
const stateGetter = (key: string) => Promise.resolve(state);
memoryStorageService.get.mockImplementation(stateGetter);
sut = new DefaultBrowserStateService(
diskStorageService,
secureStorageService,
memoryStorageService,
logService,
stateFactory,
accountService,
environmentService,
tokenService,
migrationRunner,
);
});
it("exists", () => {
expect(sut).toBeDefined();
});
});
});

View File

@@ -1,64 +0,0 @@
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
import { Account } from "../../models/account";
import { BrowserStateService } from "./abstractions/browser-state.service";
export class DefaultBrowserStateService
extends BaseStateService<GlobalState, Account>
implements BrowserStateService
{
protected accountDeserializer = Account.fromJSON;
constructor(
storageService: AbstractStorageService,
secureStorageService: AbstractStorageService,
memoryStorageService: AbstractStorageService,
logService: LogService,
stateFactory: StateFactory<GlobalState, Account>,
accountService: AccountService,
environmentService: EnvironmentService,
tokenService: TokenService,
migrationRunner: MigrationRunner,
) {
super(
storageService,
secureStorageService,
memoryStorageService,
logService,
stateFactory,
accountService,
environmentService,
tokenService,
migrationRunner,
);
}
async addAccount(account: Account) {
// Apply browser overrides to default account values
account = new Account(account);
await super.addAccount(account);
}
// Overriding the base class to prevent deleting the cache on save. We register a storage listener
// to delete the cache in the constructor above.
protected override async saveAccountToDisk(
account: Account,
options: StorageOptions,
): Promise<void> {
const storageLocation = options.useSecureStorage
? this.secureStorageService
: this.storageService;
await storageLocation.save(`${options.userId}`, account, options);
}
}

View File

@@ -19,6 +19,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import { EnvironmentComponent } from "../auth/popup/environment.component";
@@ -33,6 +34,7 @@ import { RemovePasswordComponent } from "../auth/popup/remove-password.component
import { SetPasswordComponent } from "../auth/popup/set-password.component";
import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component";
import { SsoComponent } from "../auth/popup/sso.component";
import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
@@ -137,12 +139,26 @@ const routes: Routes = [
canActivate: [lockGuard()],
data: { state: "lock", doNotSaveUrl: true },
},
{
path: "2fa",
component: TwoFactorComponent,
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
},
...twofactorRefactorSwap(
TwoFactorComponent,
AnonLayoutWrapperComponent,
{
path: "2fa",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
},
{
path: "2fa",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
children: [
{
path: "",
component: TwoFactorAuthComponent,
},
],
},
),
{
path: "2fa-options",
component: TwoFactorOptionsComponent,

View File

@@ -8,6 +8,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { MessageListener } from "@bitwarden/common/platform/messaging";
import { UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
@@ -19,7 +20,6 @@ import {
} from "@bitwarden/components";
import { BrowserApi } from "../platform/browser/browser-api";
import { BrowserStateService } from "../platform/services/abstractions/browser-state.service";
import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service";
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
@@ -45,7 +45,7 @@ export class AppComponent implements OnInit, OnDestroy {
private authService: AuthService,
private i18nService: I18nService,
private router: Router,
private stateService: BrowserStateService,
private stateService: StateService,
private browserSendStateService: BrowserSendStateService,
private vaultBrowserStateService: VaultBrowserStateService,
private cipherService: CipherService,

View File

@@ -6,16 +6,16 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService as LogServiceAbstraction } 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 { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
@Injectable()
export class InitService {
constructor(
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private stateService: StateServiceAbstraction,
private stateService: StateService,
private twoFactorService: TwoFactorService,
private logService: LogServiceAbstraction,
private themingService: AbstractThemingService,

View File

@@ -17,7 +17,7 @@ import {
CLIENT_TYPE,
} from "@bitwarden/angular/services/injection-tokens";
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { AuthRequestServiceAbstraction, PinServiceAbstraction } from "@bitwarden/auth/common";
import { PinServiceAbstraction } from "@bitwarden/auth/common";
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service";
@@ -32,7 +32,6 @@ import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import {
AutofillSettingsService,
@@ -57,20 +56,17 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import {
AbstractStorageService,
ObservableStorageService,
} from "@bitwarden/common/platform/abstractions/storage.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
import {
@@ -94,7 +90,6 @@ import { UnauthGuardService } from "../../auth/popup/services";
import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service";
import AutofillService from "../../autofill/services/autofill.service";
import MainBackground from "../../background/main.background";
import { Account } from "../../models/account";
import { BrowserApi } from "../../platform/browser/browser-api";
import { runInsideAngular } from "../../platform/browser/run-inside-angular.operator";
/* eslint-disable no-restricted-imports */
@@ -104,13 +99,11 @@ import { OffscreenDocumentService } from "../../platform/offscreen-document/abst
import { DefaultOffscreenDocumentService } from "../../platform/offscreen-document/offscreen-document.service";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
import { BrowserFileDownloadService } from "../../platform/popup/services/browser-file-download.service";
import { BrowserStateService as StateServiceAbstraction } from "../../platform/services/abstractions/browser-state.service";
import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service";
import { BrowserCryptoService } from "../../platform/services/browser-crypto.service";
import { BrowserEnvironmentService } from "../../platform/services/browser-environment.service";
import BrowserLocalStorageService from "../../platform/services/browser-local-storage.service";
import { BrowserScriptInjectorService } from "../../platform/services/browser-script-injector.service";
import { DefaultBrowserStateService } from "../../platform/services/default-browser-state.service";
import I18nService from "../../platform/services/i18n.service";
import { ForegroundPlatformUtilsService } from "../../platform/services/platform-utils/foreground-platform-utils.service";
import { BrowserStorageServiceProvider } from "../../platform/storage/browser-storage-service.provider";
@@ -219,7 +212,7 @@ const safeProviders: SafeProvider[] = [
encryptService: EncryptService,
platformUtilsService: PlatformUtilsService,
logService: LogService,
stateService: StateServiceAbstraction,
stateService: StateService,
accountService: AccountServiceAbstraction,
stateProvider: StateProvider,
biometricStateService: BiometricStateService,
@@ -250,7 +243,7 @@ const safeProviders: SafeProvider[] = [
EncryptService,
PlatformUtilsService,
LogService,
StateServiceAbstraction,
StateService,
AccountServiceAbstraction,
StateProvider,
BiometricStateService,
@@ -262,11 +255,6 @@ const safeProviders: SafeProvider[] = [
useClass: TotpService,
deps: [CryptoFunctionService, LogService],
}),
safeProvider({
provide: AuthRequestServiceAbstraction,
useFactory: getBgService<AuthRequestServiceAbstraction>("authRequestService"),
deps: [],
}),
safeProvider({
provide: DeviceTrustServiceAbstraction,
useFactory: getBgService<DeviceTrustServiceAbstraction>("deviceTrustService"),
@@ -436,46 +424,6 @@ const safeProviders: SafeProvider[] = [
},
deps: [StateProvider],
}),
safeProvider({
provide: StateServiceAbstraction,
useFactory: (
storageService: AbstractStorageService,
secureStorageService: AbstractStorageService,
memoryStorageService: AbstractStorageService,
logService: LogService,
accountService: AccountServiceAbstraction,
environmentService: EnvironmentService,
tokenService: TokenService,
migrationRunner: MigrationRunner,
) => {
return new DefaultBrowserStateService(
storageService,
secureStorageService,
memoryStorageService,
logService,
new StateFactory(GlobalState, Account),
accountService,
environmentService,
tokenService,
migrationRunner,
);
},
deps: [
AbstractStorageService,
SECURE_STORAGE,
MEMORY_STORAGE,
LogService,
AccountServiceAbstraction,
EnvironmentService,
TokenService,
MigrationRunner,
],
}),
safeProvider({
provide: BaseStateServiceAbstraction,
useExisting: StateServiceAbstraction,
deps: [],
}),
safeProvider({
provide: FileDownloadService,
useClass: BrowserFileDownloadService,

View File

@@ -13,12 +13,12 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils";
import { BrowserStateService } from "../../../platform/services/abstractions/browser-state.service";
import { FilePopoutUtilsService } from "../services/file-popout-utils.service";
@Component({
@@ -37,7 +37,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
constructor(
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
stateService: BrowserStateService,
stateService: StateService,
messagingService: MessagingService,
policyService: PolicyService,
environmentService: EnvironmentService,

View File

@@ -47,7 +47,13 @@
(change)="onFileChange($event)"
/>
<div class="tw-flex tw-gap-2 tw-items-center" aria-hidden="true">
<button bitButton buttonType="secondary" type="button" (click)="fileInput.click()">
<button
bitButton
buttonType="secondary"
type="button"
(click)="fileInput.click()"
class="tw-whitespace-nowrap"
>
{{ "chooseFile" | i18n }}
</button>
<p bitTypography="body2" class="tw-text-muted tw-mb-0">

View File

@@ -0,0 +1,3 @@
{
"extends": "../../../../libs/admin-console/.eslintrc.json"
}

View File

@@ -19,6 +19,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { HintComponent } from "../auth/hint.component";
@@ -30,6 +31,7 @@ import { RegisterComponent } from "../auth/register.component";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { SsoComponent } from "../auth/sso.component";
import { TwoFactorAuthComponent } from "../auth/two-factor-auth.component";
import { TwoFactorComponent } from "../auth/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VaultComponent } from "../vault/app/vault/vault.component";
@@ -61,7 +63,24 @@ const routes: Routes = [
path: "admin-approval-requested",
component: LoginViaAuthRequestComponent,
},
{ path: "2fa", component: TwoFactorComponent },
...twofactorRefactorSwap(
TwoFactorComponent,
AnonLayoutWrapperComponent,
{
path: "2fa",
},
{
path: "2fa",
component: AnonLayoutWrapperComponent,
children: [
{
path: "",
component: TwoFactorAuthComponent,
canActivate: [unauthGuardFn()],
},
],
},
),
{
path: "login-initiated",
component: LoginDecryptionOptionsComponent,

View File

@@ -403,7 +403,6 @@ export class AppComponent implements OnInit, OnDestroy {
// Clear sequentialized caches
clearCaches();
if (message.userId != null) {
await this.stateService.clearDecryptedData(message.userId);
await this.accountService.switchAccount(message.userId);
}
const locked =

View File

@@ -165,8 +165,6 @@ export class AccountSwitcherComponent {
async addAccount() {
this.close();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
await this.stateService.clearDecryptedData(activeAccount?.id as UserId);
await this.accountService.switchAccount(null);
await this.router.navigate(["/login"]);
}

View File

@@ -13,7 +13,6 @@ import {
SUPPORTS_SECURE_STORAGE,
SYSTEM_THEME_OBSERVABLE,
SafeInjectionToken,
STATE_FACTORY,
DEFAULT_VAULT_TIMEOUT,
INTRAPROCESS_MESSAGING_SUBJECT,
CLIENT_TYPE,
@@ -25,13 +24,11 @@ import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/a
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { KdfConfigService as KdfConfigServiceAbstraction } from "@bitwarden/common/auth/abstractions/kdf-config.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { ClientType } from "@bitwarden/common/enums";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KeyGenerationService as KeyGenerationServiceAbstraction } from "@bitwarden/common/platform/abstractions/key-generation.service";
@@ -45,13 +42,10 @@ import { StateService as StateServiceAbstraction } from "@bitwarden/common/platf
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/platform/abstractions/system.service";
import { BiometricStateService } from "@bitwarden/common/platform/biometrics/biometric-state.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Message, MessageListener, MessageSender } from "@bitwarden/common/platform/messaging";
// eslint-disable-next-line no-restricted-imports -- Used for dependency injection
import { SubjectMessageSender } from "@bitwarden/common/platform/messaging/internal";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { GlobalStateProvider, StateProvider } from "@bitwarden/common/platform/state";
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
@@ -63,7 +57,6 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac
import { PinServiceAbstraction } from "../../../../../libs/auth/src/common/abstractions";
import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service";
import { Account } from "../../models/account";
import { DesktopSettingsService } from "../../platform/services/desktop-settings.service";
import { ElectronCryptoService } from "../../platform/services/electron-crypto.service";
import { ElectronLogRendererService } from "../../platform/services/electron-log.renderer.service";
@@ -74,7 +67,6 @@ import {
import { ElectronRendererMessageSender } from "../../platform/services/electron-renderer-message.sender";
import { ElectronRendererSecureStorageService } from "../../platform/services/electron-renderer-secure-storage.service";
import { ElectronRendererStorageService } from "../../platform/services/electron-renderer-storage.service";
import { ElectronStateService } from "../../platform/services/electron-state.service";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
import { fromIpcMessaging } from "../../platform/utils/from-ipc-messaging";
import { fromIpcSystemTheme } from "../../platform/utils/from-ipc-system-theme";
@@ -90,11 +82,6 @@ import { RendererCryptoFunctionService } from "./renderer-crypto-function.servic
const RELOAD_CALLBACK = new SafeInjectionToken<() => any>("RELOAD_CALLBACK");
// Desktop has its own Account definition which must be used in its StateService
const DESKTOP_STATE_FACTORY = new SafeInjectionToken<StateFactory<GlobalState, Account>>(
"DESKTOP_STATE_FACTORY",
);
/**
* Provider definitions used in the ngModule.
* Add your provider definition here using the safeProvider function as a wrapper. This will give you type safety.
@@ -111,14 +98,6 @@ const safeProviders: SafeProvider[] = [
deps: [InitService],
multi: true,
}),
safeProvider({
provide: DESKTOP_STATE_FACTORY,
useValue: new StateFactory(GlobalState, Account),
}),
safeProvider({
provide: STATE_FACTORY,
useValue: null,
}),
safeProvider({
provide: RELOAD_CALLBACK,
useValue: null,
@@ -194,28 +173,12 @@ const safeProviders: SafeProvider[] = [
MessagingServiceAbstraction,
PlatformUtilsServiceAbstraction,
RELOAD_CALLBACK,
StateServiceAbstraction,
AutofillSettingsServiceAbstraction,
VaultTimeoutSettingsService,
BiometricStateService,
AccountServiceAbstraction,
],
}),
safeProvider({
provide: StateServiceAbstraction,
useClass: ElectronStateService,
deps: [
AbstractStorageService,
SECURE_STORAGE,
MEMORY_STORAGE,
LogService,
DESKTOP_STATE_FACTORY,
AccountServiceAbstraction,
EnvironmentService,
TokenService,
MigrationRunner,
],
}),
safeProvider({
provide: FileDownloadService,
useClass: DesktopFileDownloadService,

View File

@@ -0,0 +1,41 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { RouterLink } from "@angular/router";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
import { JslibModule } from "../../../../libs/angular/src/jslib.module";
import { AsyncActionsModule } from "../../../../libs/components/src/async-actions";
import { ButtonModule } from "../../../../libs/components/src/button";
import { CheckboxModule } from "../../../../libs/components/src/checkbox";
import { FormFieldModule } from "../../../../libs/components/src/form-field";
import { LinkModule } from "../../../../libs/components/src/link";
import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe";
import { TypographyModule } from "../../../../libs/components/src/typography";
@Component({
standalone: true,
templateUrl:
"../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {}

View File

@@ -1,20 +0,0 @@
import {
Account as BaseAccount,
AccountSettings as BaseAccountSettings,
} from "@bitwarden/common/platform/models/domain/account";
export class AccountSettings extends BaseAccountSettings {
dismissedBiometricRequirePasswordOnStartCallout?: boolean;
}
export class Account extends BaseAccount {
settings?: AccountSettings = new AccountSettings();
constructor(init: Partial<Account>) {
super(init);
Object.assign(this.settings, {
...new AccountSettings(),
...this.settings,
});
}
}

View File

@@ -1,12 +0,0 @@
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
import { Account } from "../../models/account";
export class ElectronStateService extends BaseStateService<GlobalState, Account> {
async addAccount(account: Account) {
// Apply desktop overides to default account values
account = new Account(account);
await super.addAccount(account);
}
}

View File

@@ -0,0 +1,3 @@
{
"extends": "../../../../../libs/admin-console/.eslintrc.json"
}

View File

@@ -24,7 +24,7 @@ export class PaidOrganizationOnlyComponent {}
@Component({
template: "<h1>This is the organization upgrade screen!</h1>",
})
export class OrganizationUpgradeScreen {}
export class OrganizationUpgradeScreenComponent {}
const orgFactory = (props: Partial<Organization> = {}) =>
Object.assign(
@@ -62,7 +62,7 @@ describe("Is Paid Org Guard", () => {
},
{
path: "organizations/:organizationId/billing/subscription",
component: OrganizationUpgradeScreen,
component: OrganizationUpgradeScreenComponent,
},
]),
],

View File

@@ -1,5 +1,5 @@
import { KeyValue } from "@angular/common";
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from "@angular/core";
import { Component, Input, OnInit, OnDestroy } from "@angular/core";
import { FormControl, FormGroup } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
@@ -14,8 +14,6 @@ export class NestedCheckboxComponent implements OnInit, OnDestroy {
@Input() parentId: string;
@Input() checkboxes: FormGroup<Record<string, FormControl<boolean>>>;
@Output() onSavedUser = new EventEmitter();
@Output() onDeletedUser = new EventEmitter();
get parentIndeterminate() {
return (

View File

@@ -31,7 +31,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
@Input() email: string;
@Input() id: string;
@Input() organizationId: string;
@Output() onPasswordReset = new EventEmitter();
@Output() passwordReset = new EventEmitter();
@ViewChild(PasswordStrengthComponent) passwordStrengthComponent: PasswordStrengthComponent;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
@@ -156,7 +156,7 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
null,
this.i18nService.t("resetPasswordSuccess"),
);
this.onPasswordReset.emit();
this.passwordReset.emit();
} catch (e) {
this.logService.error(e);
}

View File

@@ -635,7 +635,7 @@ export class MembersComponent extends NewBasePeopleComponent<OrganizationUserVie
comp.id = user != null ? user.id : null;
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
comp.onPasswordReset.subscribe(() => {
comp.passwordReset.subscribe(() => {
modal.close();
// 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

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
@@ -21,7 +21,7 @@ export class MasterPasswordPolicy extends BasePolicy {
selector: "policy-master-password",
templateUrl: "master-password.component.html",
})
export class MasterPasswordPolicyComponent extends BasePolicyComponent {
export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit {
MinPasswordLength = Utils.minimumPasswordLength;
data: FormGroup<ControlsOf<MasterPasswordPolicyOptions>> = this.formBuilder.group({

View File

@@ -1,5 +1,12 @@
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
import { ChangeDetectorRef, Component, Inject, ViewChild, ViewContainerRef } from "@angular/core";
import {
AfterViewInit,
ChangeDetectorRef,
Component,
Inject,
ViewChild,
ViewContainerRef,
} from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
@@ -28,7 +35,7 @@ export enum PolicyEditDialogResult {
selector: "app-policy-edit",
templateUrl: "policy-edit.component.html",
})
export class PolicyEditComponent {
export class PolicyEditComponent implements AfterViewInit {
@ViewChild("policyForm", { read: ViewContainerRef, static: true })
policyFormRef: ViewContainerRef;

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
@@ -22,7 +22,7 @@ export class ResetPasswordPolicy extends BasePolicy {
selector: "policy-reset-password",
templateUrl: "reset-password.component.html",
})
export class ResetPasswordPolicyComponent extends BasePolicyComponent {
export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit {
data = this.formBuilder.group({
autoEnrollEnabled: false,
});

View File

@@ -1,4 +1,4 @@
import { Component, ViewChild, ViewContainerRef } from "@angular/core";
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { combineLatest, from, lastValueFrom, of, Subject, switchMap, takeUntil } from "rxjs";
@@ -27,7 +27,7 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./
selector: "app-org-account",
templateUrl: "account.component.html",
})
export class AccountComponent {
export class AccountComponent implements OnInit, OnDestroy {
@ViewChild("apiKeyTemplate", { read: ViewContainerRef, static: true })
apiKeyModalRef: ViewContainerRef;
@ViewChild("rotateApiKeyTemplate", { read: ViewContainerRef, static: true })

View File

@@ -1,5 +1,5 @@
import { DialogRef } from "@angular/cdk/dialog";
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { concatMap, takeUntil, map, lastValueFrom } from "rxjs";
import { first, tap } from "rxjs/operators";
@@ -24,7 +24,7 @@ import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor-veri
templateUrl: "../../../auth/settings/two-factor-setup.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent implements OnInit {
tabbedHeader = false;
constructor(
dialogService: DialogService,

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -19,7 +19,10 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent
templateUrl: "../../../tools/reports/pages/exposed-passwords-report.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent {
export class ExposedPasswordsReportComponent
extends BaseExposedPasswordsReportComponent
implements OnInit
{
manageableCiphers: Cipher[];
constructor(

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -18,7 +18,10 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen
templateUrl: "../../../tools/reports/pages/inactive-two-factor-report.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent {
export class InactiveTwoFactorReportComponent
extends BaseInactiveTwoFactorReportComponent
implements OnInit
{
constructor(
cipherService: CipherService,
modalService: ModalService,

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -18,7 +18,10 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent }
templateUrl: "../../../tools/reports/pages/reused-passwords-report.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent {
export class ReusedPasswordsReportComponent
extends BaseReusedPasswordsReportComponent
implements OnInit
{
manageableCiphers: Cipher[];
constructor(

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -17,7 +17,10 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen
templateUrl: "../../../tools/reports/pages/unsecured-websites-report.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent {
export class UnsecuredWebsitesReportComponent
extends BaseUnsecuredWebsitesReportComponent
implements OnInit
{
constructor(
cipherService: CipherService,
modalService: ModalService,

View File

@@ -1,4 +1,4 @@
import { Component } from "@angular/core";
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { ModalService } from "@bitwarden/angular/services/modal.service";
@@ -19,7 +19,10 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from
templateUrl: "../../../tools/reports/pages/weak-passwords-report.component.html",
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent {
export class WeakPasswordsReportComponent
extends BaseWeakPasswordsReportComponent
implements OnInit
{
manageableCiphers: Cipher[];
constructor(

View File

@@ -72,7 +72,14 @@
</ng-container>
<hr *ngIf="enabled" />
<p class="tw-text-center tw-mb-0">
<canvas id="qr"></canvas><br />
<ng-container *ngIf="qrScriptError" class="tw-mt-2">
<i class="bwi bwi-error tw-text-3xl tw-text-danger" aria-hidden="true"></i>
<p>
{{ "twoStepAuthenticatorQRCanvasError" | i18n }}
</p>
</ng-container>
<canvas *ngIf="!qrScriptError" id="qr"></canvas>
<br />
<code appA11yTitle="{{ 'key' | i18n }}">{{ key }}</code>
</p>
<bit-form-field *ngIf="!enabled" [disableMargin]="true">
@@ -90,7 +97,7 @@
>
{{ (enabled ? "disable" : "enable") | i18n }}
</button>
<button bitButton bitFormButton type="button" buttonType="secondary" [bitAction]="close">
<button bitButton bitFormButton type="button" buttonType="secondary" bitDialogClose>
{{ "close" | i18n }}
</button>
</ng-container>

View File

@@ -43,9 +43,9 @@ export class TwoFactorAuthenticatorComponent
@Output() onChangeStatus = new EventEmitter<boolean>();
type = TwoFactorProviderType.Authenticator;
key: string;
formPromise: Promise<TwoFactorAuthenticatorResponse>;
override componentName = "app-two-factor-authenticator";
qrScriptError = false;
private qrScript: HTMLScriptElement;
formGroup = this.formBuilder.group({
@@ -90,7 +90,7 @@ export class TwoFactorAuthenticatorComponent
this.formGroup.controls.token.markAsTouched();
}
auth(authResponse: AuthResponse<TwoFactorAuthenticatorResponse>) {
async auth(authResponse: AuthResponse<TwoFactorAuthenticatorResponse>) {
super.auth(authResponse);
return this.processResponse(authResponse.response);
}
@@ -100,56 +100,69 @@ export class TwoFactorAuthenticatorComponent
return;
}
if (this.enabled) {
await this.disableAuthentication(this.formPromise);
this.onChangeStatus.emit(this.enabled);
this.close();
await this.disableMethod();
this.dialogRef.close(this.enabled);
} else {
await this.enable();
this.onChangeStatus.emit(this.enabled);
}
this.onChangeStatus.emit(this.enabled);
};
private async disableAuthentication(promise: Promise<unknown>) {
return super.disable(promise);
}
protected async enable() {
const request = await this.buildRequestModel(UpdateTwoFactorAuthenticatorRequest);
request.token = this.formGroup.value.token;
request.key = this.key;
return super.enable(async () => {
this.formPromise = this.apiService.putTwoFactorAuthenticator(request);
const response = await this.formPromise;
await this.processResponse(response);
});
const response = await this.apiService.putTwoFactorAuthenticator(request);
await this.processResponse(response);
this.onUpdated.emit(true);
}
private async processResponse(response: TwoFactorAuthenticatorResponse) {
this.formGroup.get("token").setValue(null);
this.enabled = response.enabled;
this.key = response.key;
await this.waitForQRiousToLoadOrError().catch((error) => {
this.logService.error(error);
this.qrScriptError = true;
});
await this.createQRCode();
}
private async waitForQRiousToLoadOrError(): Promise<void> {
// Check if QRious is already loaded or if there was an error loading it either way don't wait for it to try and load again
if (typeof window.QRious !== "undefined" || this.qrScriptError) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
this.qrScript.onload = () => resolve();
this.qrScript.onerror = () =>
reject(new Error(this.i18nService.t("twoStepAuthenticatorQRCanvasError")));
});
}
private async createQRCode() {
if (this.qrScriptError) {
return;
}
const email = await firstValueFrom(
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
);
window.setTimeout(() => {
new window.QRious({
element: document.getElementById("qr"),
value:
"otpauth://totp/Bitwarden:" +
Utils.encodeRFC3986URIComponent(email) +
"?secret=" +
encodeURIComponent(this.key) +
"&issuer=Bitwarden",
size: 160,
});
}, 100);
new window.QRious({
element: document.getElementById("qr"),
value:
"otpauth://totp/Bitwarden:" +
Utils.encodeRFC3986URIComponent(email) +
"?secret=" +
encodeURIComponent(this.key) +
"&issuer=Bitwarden",
size: 160,
});
}
close = () => {
this.dialogRef.close(this.enabled);
};
static open(
dialogService: DialogService,
config: DialogConfig<AuthResponse<TwoFactorAuthenticatorResponse>>,

View File

@@ -0,0 +1,107 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.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 { LinkModule, TypographyModule, CheckboxModule, DialogService } from "@bitwarden/components";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
} from "../../../../../libs/auth/src/common/abstractions";
import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions";
import { ButtonModule } from "../../../../../libs/components/src/button";
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
@Component({
standalone: true,
templateUrl:
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
dialogService: DialogService,
protected route: ActivatedRoute,
logService: LogService,
protected twoFactorService: TwoFactorService,
loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window,
) {
super(
loginStrategyService,
router,
i18nService,
platformUtilsService,
environmentService,
dialogService,
route,
logService,
twoFactorService,
loginEmailService,
userDecryptionOptionsService,
ssoLoginService,
configService,
masterPasswordService,
accountService,
formBuilder,
win,
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
if (!result.requiresEncryptionKeyMigration) {
return false;
}
// 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.router.navigate(["migrate-legacy-encryption"]);
return true;
}
}

View File

@@ -4,7 +4,6 @@ import { APP_INITIALIZER, NgModule, Optional, SkipSelf } from "@angular/core";
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
import {
SECURE_STORAGE,
STATE_FACTORY,
LOCALES_DIRECTORY,
SYSTEM_LANGUAGE,
MEMORY_STORAGE,
@@ -30,10 +29,9 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { ThemeType } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
@@ -64,7 +62,7 @@ import { EventService } from "./event.service";
import { InitService } from "./init.service";
import { ModalService } from "./modal.service";
import { RouterService } from "./router.service";
import { Account, GlobalState, StateService } from "./state";
import { StateService as WebStateService } from "./state";
import { WebFileDownloadService } from "./web-file-download.service";
import { WebPlatformUtilsService } from "./web-platform-utils.service";
@@ -90,10 +88,6 @@ const safeProviders: SafeProvider[] = [
deps: [InitService],
multi: true,
}),
safeProvider({
provide: STATE_FACTORY,
useValue: new StateFactory(GlobalState, Account),
}),
safeProvider({
provide: I18nServiceAbstraction,
useClass: I18nService,
@@ -132,10 +126,10 @@ const safeProviders: SafeProvider[] = [
useClass: ModalService,
useAngularDecorators: true,
}),
safeProvider(StateService),
safeProvider(WebStateService),
safeProvider({
provide: BaseStateServiceAbstraction,
useExisting: StateService,
provide: StateService,
useExisting: WebStateService,
}),
safeProvider({
provide: FileDownloadService,

View File

@@ -1,8 +0,0 @@
import { Account as BaseAccount } from "@bitwarden/common/platform/models/domain/account";
// TODO: platform to clean up accounts in later PR
export class Account extends BaseAccount {
constructor(init: Partial<Account>) {
super(init);
}
}

View File

@@ -1,5 +0,0 @@
import { GlobalState as BaseGlobalState } from "@bitwarden/common/platform/models/domain/global-state";
export class GlobalState extends BaseGlobalState {
rememberEmail = true;
}

View File

@@ -1,3 +1 @@
export * from "./account";
export * from "./global-state";
export * from "./state.service";

View File

@@ -11,13 +11,12 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { Account } from "@bitwarden/common/platform/models/domain/account";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
import { Account } from "./account";
import { GlobalState } from "./global-state";
@Injectable()
export class StateService extends BaseStateService<GlobalState, Account> {
constructor(
@@ -44,12 +43,6 @@ export class StateService extends BaseStateService<GlobalState, Account> {
);
}
async addAccount(account: Account) {
// Apply web overrides to default account values
account = new Account(account);
await super.addAccount(account);
}
override async getLastSync(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(options, await this.defaultInMemoryOptions());
return await super.getLastSync(options);

View File

@@ -20,6 +20,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { flagEnabled, Flags } from "../utils/flags";
import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component";
@@ -46,6 +47,7 @@ import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/v
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
import { SsoComponent } from "./auth/sso.component";
import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component";
import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component";
import { TwoFactorComponent } from "./auth/two-factor.component";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
@@ -248,10 +250,9 @@ const routes: Routes = [
path: "2fa",
canActivate: [unauthGuardFn()],
children: [
{
...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, {
path: "",
component: TwoFactorComponent,
},
}),
{
path: "",
component: EnvironmentSelectorComponent,

View File

@@ -1691,6 +1691,9 @@
"twoStepAuthenticatorScanCodeV2": {
"message": "Scan the QR code below with your authenticator app or enter the key."
},
"twoStepAuthenticatorQRCanvasError": {
"message": "Could not load QR code. Try again or use the key below."
},
"key": {
"message": "Key"
},