mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
fix(two-factor) [PM-21204]: Users without premium cannot disable premium 2FA (#17134)
* refactor(two-factor-service) [PM-21204]: Stub API methods in TwoFactorService (domain). * refactor(two-factor-service) [PM-21204]: Build out stubs and add documentation. * refactor(two-factor-service) [PM-21204]: Update TwoFactorApiService call sites to use TwoFactorService. * refactor(two-fatcor) [PM-21204]: Remove deprecated and unused formPromise methods. * refactor(two-factor) [PM-21204]: Move 2FA-supporting services into common/auth/two-factor feature namespace. * refactor(two-factor) [PM-21204]: Update imports for service/init containers. * feat(two-factor) [PM-21204]: Add a disabling flow for Premium 2FA when enabled on a non-Premium account. * fix(two-factor-service) [PM-21204]: Fix type-safety of module constants. * fix(multiple) [PM-21204]: Prettier. * fix(user-verification-dialog) [PM-21204]: Remove bodyText configuration for this use. * fix(user-verification-dialog) [PM-21204]: Improve the error message displayed to the user.
This commit is contained in:
@@ -2,7 +2,7 @@ import { DOCUMENT } from "@angular/common";
|
||||
import { inject, Inject, Injectable } from "@angular/core";
|
||||
|
||||
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
@@ -20,7 +20,6 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
@@ -28,7 +27,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
|
||||
import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request";
|
||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||
import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService, TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
|
||||
@@ -49,10 +49,14 @@ import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/defau
|
||||
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/services/master-password/master-password-api.service.implementation";
|
||||
import { TokenService } from "@bitwarden/common/auth/services/token.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/services/two-factor.service";
|
||||
import { UserVerificationApiService } from "@bitwarden/common/auth/services/user-verification/user-verification-api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/services/user-verification/user-verification.service";
|
||||
import { TwoFactorApiService, DefaultTwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import {
|
||||
DefaultTwoFactorService,
|
||||
TwoFactorService,
|
||||
TwoFactorApiService,
|
||||
DefaultTwoFactorApiService,
|
||||
} from "@bitwarden/common/auth/two-factor";
|
||||
import {
|
||||
AutofillSettingsService,
|
||||
AutofillSettingsServiceAbstraction,
|
||||
@@ -627,10 +631,11 @@ export class ServiceContainer {
|
||||
this.stateProvider,
|
||||
);
|
||||
|
||||
this.twoFactorService = new TwoFactorService(
|
||||
this.twoFactorService = new DefaultTwoFactorService(
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.globalStateProvider,
|
||||
this.twoFactorApiService,
|
||||
);
|
||||
|
||||
const sdkClientFactory = flagEnabled("sdk")
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -39,7 +39,7 @@ export class InitService {
|
||||
private vaultTimeoutService: DefaultVaultTimeoutService,
|
||||
private i18nService: I18nServiceAbstraction,
|
||||
private eventUploadService: EventUploadServiceAbstraction,
|
||||
private twoFactorService: TwoFactorServiceAbstraction,
|
||||
private twoFactorService: TwoFactorService,
|
||||
private notificationsService: ServerNotificationsService,
|
||||
private platformUtilsService: PlatformUtilsServiceAbstraction,
|
||||
private stateService: StateServiceAbstraction,
|
||||
|
||||
@@ -11,16 +11,17 @@ import {
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||
import { DialogRef, DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { TwoFactorSetupDuoComponent } from "../../../auth/settings/two-factor/two-factor-setup-duo.component";
|
||||
import { TwoFactorSetupComponent as BaseTwoFactorSetupComponent } from "../../../auth/settings/two-factor/two-factor-setup.component";
|
||||
@@ -37,7 +38,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme
|
||||
tabbedHeader = false;
|
||||
constructor(
|
||||
dialogService: DialogService,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
messagingService: MessagingService,
|
||||
policyService: PolicyService,
|
||||
private route: ActivatedRoute,
|
||||
@@ -46,16 +47,20 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme
|
||||
protected accountService: AccountService,
|
||||
configService: ConfigService,
|
||||
i18nService: I18nService,
|
||||
protected userVerificationService: UserVerificationService,
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
dialogService,
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
messagingService,
|
||||
policyService,
|
||||
billingAccountProfileStateService,
|
||||
accountService,
|
||||
configService,
|
||||
i18nService,
|
||||
userVerificationService,
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +123,7 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme
|
||||
}
|
||||
|
||||
protected getTwoFactorProviders() {
|
||||
return this.twoFactorApiService.getTwoFactorOrganizationProviders(this.organizationId);
|
||||
return this.twoFactorService.getTwoFactorOrganizationProviders(this.organizationId);
|
||||
}
|
||||
|
||||
protected filterProvider(type: TwoFactorProviderType): boolean {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { TwoFactorProviderResponse } from "@bitwarden/common/auth/models/response/two-factor-provider.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -23,14 +23,14 @@ describe("ChangeEmailComponent", () => {
|
||||
let fixture: ComponentFixture<ChangeEmailComponent>;
|
||||
|
||||
let apiService: MockProxy<ApiService>;
|
||||
let twoFactorApiService: MockProxy<TwoFactorApiService>;
|
||||
let twoFactorService: MockProxy<TwoFactorService>;
|
||||
let accountService: FakeAccountService;
|
||||
let keyService: MockProxy<KeyService>;
|
||||
let kdfConfigService: MockProxy<KdfConfigService>;
|
||||
|
||||
beforeEach(async () => {
|
||||
apiService = mock<ApiService>();
|
||||
twoFactorApiService = mock<TwoFactorApiService>();
|
||||
twoFactorService = mock<TwoFactorService>();
|
||||
keyService = mock<KeyService>();
|
||||
kdfConfigService = mock<KdfConfigService>();
|
||||
accountService = mockAccountServiceWith("UserId" as UserId);
|
||||
@@ -40,7 +40,7 @@ describe("ChangeEmailComponent", () => {
|
||||
providers: [
|
||||
{ provide: AccountService, useValue: accountService },
|
||||
{ provide: ApiService, useValue: apiService },
|
||||
{ provide: TwoFactorApiService, useValue: twoFactorApiService },
|
||||
{ provide: TwoFactorService, useValue: twoFactorService },
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{ provide: KeyService, useValue: keyService },
|
||||
{ provide: MessagingService, useValue: mock<MessagingService>() },
|
||||
@@ -61,7 +61,7 @@ describe("ChangeEmailComponent", () => {
|
||||
|
||||
describe("ngOnInit", () => {
|
||||
beforeEach(() => {
|
||||
twoFactorApiService.getTwoFactorProviders.mockResolvedValue({
|
||||
twoFactorService.getEnabledTwoFactorProviders.mockResolvedValue({
|
||||
data: [{ type: TwoFactorProviderType.Email, enabled: true } as TwoFactorProviderResponse],
|
||||
} as ListResponse<TwoFactorProviderResponse>);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
|
||||
import { EmailTokenRequest } from "@bitwarden/common/auth/models/request/email-token.request";
|
||||
import { EmailRequest } from "@bitwarden/common/auth/models/request/email.request";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
@@ -40,7 +40,7 @@ export class ChangeEmailComponent implements OnInit {
|
||||
constructor(
|
||||
private accountService: AccountService,
|
||||
private apiService: ApiService,
|
||||
private twoFactorApiService: TwoFactorApiService,
|
||||
private twoFactorService: TwoFactorService,
|
||||
private i18nService: I18nService,
|
||||
private keyService: KeyService,
|
||||
private messagingService: MessagingService,
|
||||
@@ -52,7 +52,7 @@ export class ChangeEmailComponent implements OnInit {
|
||||
async ngOnInit() {
|
||||
this.userId = await firstValueFrom(getUserId(this.accountService.activeAccount$));
|
||||
|
||||
const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders();
|
||||
const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders();
|
||||
this.showTwoFactorEmailWarning = twoFactorProviders.data.some(
|
||||
(p) => p.type === TwoFactorProviderType.Email && p.enabled,
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-a
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { SetVerifyDevicesRequest } from "@bitwarden/common/auth/models/request/set-verify-devices.request";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -66,7 +66,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy
|
||||
private userVerificationService: UserVerificationService,
|
||||
private dialogRef: DialogRef,
|
||||
private toastService: ToastService,
|
||||
private twoFactorApiService: TwoFactorApiService,
|
||||
private twoFactorService: TwoFactorService,
|
||||
) {
|
||||
this.accountService.accountVerifyNewDeviceLogin$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
@@ -76,7 +76,7 @@ export class SetAccountVerifyDevicesDialogComponent implements OnInit, OnDestroy
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
const twoFactorProviders = await this.twoFactorApiService.getTwoFactorProviders();
|
||||
const twoFactorProviders = await this.twoFactorService.getEnabledTwoFactorProviders();
|
||||
this.has2faConfigured = twoFactorProviders.data.length > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
|
||||
import { DisableTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/disable-two-factor-authenticator.request";
|
||||
import { UpdateTwoFactorAuthenticatorRequest } from "@bitwarden/common/auth/models/request/update-two-factor-authenticator.request";
|
||||
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -96,7 +96,7 @@ export class TwoFactorSetupAuthenticatorComponent
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorAuthenticatorResponse>,
|
||||
private dialogRef: DialogRef,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
i18nService: I18nService,
|
||||
userVerificationService: UserVerificationService,
|
||||
private formBuilder: FormBuilder,
|
||||
@@ -108,7 +108,7 @@ export class TwoFactorSetupAuthenticatorComponent
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
@@ -158,7 +158,7 @@ export class TwoFactorSetupAuthenticatorComponent
|
||||
request.key = this.key;
|
||||
request.userVerificationToken = this.userVerificationToken;
|
||||
|
||||
const response = await this.twoFactorApiService.putTwoFactorAuthenticator(request);
|
||||
const response = await this.twoFactorService.putTwoFactorAuthenticator(request);
|
||||
await this.processResponse(response);
|
||||
this.onUpdated.emit(true);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ export class TwoFactorSetupAuthenticatorComponent
|
||||
request.type = this.type;
|
||||
request.key = this.key;
|
||||
request.userVerificationToken = this.userVerificationToken;
|
||||
await this.twoFactorApiService.deleteTwoFactorAuthenticator(request);
|
||||
await this.twoFactorService.deleteTwoFactorAuthenticator(request);
|
||||
this.enabled = false;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { UpdateTwoFactorDuoRequest } from "@bitwarden/common/auth/models/request/update-two-factor-duo.request";
|
||||
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -67,7 +67,7 @@ export class TwoFactorSetupDuoComponent
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: TwoFactorDuoComponentConfig,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
@@ -78,7 +78,7 @@ export class TwoFactorSetupDuoComponent
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
@@ -143,12 +143,12 @@ export class TwoFactorSetupDuoComponent
|
||||
let response: TwoFactorDuoResponse;
|
||||
|
||||
if (this.organizationId != null) {
|
||||
response = await this.twoFactorApiService.putTwoFactorOrganizationDuo(
|
||||
response = await this.twoFactorService.putTwoFactorOrganizationDuo(
|
||||
this.organizationId,
|
||||
request,
|
||||
);
|
||||
} else {
|
||||
response = await this.twoFactorApiService.putTwoFactorDuo(request);
|
||||
response = await this.twoFactorService.putTwoFactorDuo(request);
|
||||
}
|
||||
|
||||
this.processResponse(response);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
|
||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||
import { UpdateTwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/update-two-factor-email.request";
|
||||
import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -70,7 +70,7 @@ export class TwoFactorSetupEmailComponent
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorEmailResponse>,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
@@ -82,7 +82,7 @@ export class TwoFactorSetupEmailComponent
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
@@ -135,7 +135,7 @@ export class TwoFactorSetupEmailComponent
|
||||
sendEmail = async () => {
|
||||
const request = await this.buildRequestModel(TwoFactorEmailRequest);
|
||||
request.email = this.email;
|
||||
this.emailPromise = this.twoFactorApiService.postTwoFactorEmailSetup(request);
|
||||
this.emailPromise = this.twoFactorService.postTwoFactorEmailSetup(request);
|
||||
await this.emailPromise;
|
||||
this.sentEmail = this.email;
|
||||
};
|
||||
@@ -145,7 +145,7 @@ export class TwoFactorSetupEmailComponent
|
||||
request.email = this.email;
|
||||
request.token = this.token;
|
||||
|
||||
const response = await this.twoFactorApiService.putTwoFactorEmail(request);
|
||||
const response = await this.twoFactorService.putTwoFactorEmail(request);
|
||||
await this.processResponse(response);
|
||||
this.onUpdated.emit(true);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponseBase } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -32,7 +32,7 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
protected twoFactorApiService: TwoFactorApiService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected logService: LogService,
|
||||
@@ -47,58 +47,6 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
this.authed = true;
|
||||
}
|
||||
|
||||
/** @deprecated used for formPromise flows.*/
|
||||
protected async enable(enableFunction: () => Promise<void>) {
|
||||
try {
|
||||
await enableFunction();
|
||||
this.onUpdated.emit(true);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated used for formPromise flows.
|
||||
* TODO: Remove this method when formPromises are removed from all flows.
|
||||
* */
|
||||
protected async disable(promise: Promise<unknown>) {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "disable" },
|
||||
content: { key: "twoStepDisableDesc" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = await this.buildRequestModel(TwoFactorProviderRequest);
|
||||
if (this.type === undefined) {
|
||||
throw new Error("Two-factor provider type is required");
|
||||
}
|
||||
request.type = this.type;
|
||||
if (this.organizationId != null) {
|
||||
promise = this.twoFactorApiService.putTwoFactorOrganizationDisable(
|
||||
this.organizationId,
|
||||
request,
|
||||
);
|
||||
} else {
|
||||
promise = this.twoFactorApiService.putTwoFactorDisable(request);
|
||||
}
|
||||
await promise;
|
||||
this.enabled = false;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("twoStepDisabled"),
|
||||
});
|
||||
this.onUpdated.emit(false);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected async disableMethod() {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "disable" },
|
||||
@@ -116,9 +64,9 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
}
|
||||
request.type = this.type;
|
||||
if (this.organizationId != null) {
|
||||
await this.twoFactorApiService.putTwoFactorOrganizationDisable(this.organizationId, request);
|
||||
await this.twoFactorService.putTwoFactorOrganizationDisable(this.organizationId, request);
|
||||
} else {
|
||||
await this.twoFactorApiService.putTwoFactorDisable(request);
|
||||
await this.twoFactorService.putTwoFactorDisable(request);
|
||||
}
|
||||
this.enabled = false;
|
||||
this.toastService.showToast({
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
ChallengeResponse,
|
||||
TwoFactorWebAuthnResponse,
|
||||
} from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -72,7 +72,6 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
webAuthnListening: boolean = false;
|
||||
webAuthnResponse: PublicKeyCredential | null = null;
|
||||
challengePromise: Promise<ChallengeResponse> | undefined;
|
||||
formPromise: Promise<TwoFactorWebAuthnResponse> | undefined;
|
||||
|
||||
override componentName = "app-two-factor-webauthn";
|
||||
|
||||
@@ -81,7 +80,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorWebAuthnResponse>,
|
||||
private dialogRef: DialogRef,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private ngZone: NgZone,
|
||||
@@ -91,7 +90,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
@@ -129,7 +128,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
request.id = this.keyIdAvailable;
|
||||
request.name = this.formGroup.value.name || "";
|
||||
|
||||
const response = await this.twoFactorApiService.putTwoFactorWebAuthn(request);
|
||||
const response = await this.twoFactorService.putTwoFactorWebAuthn(request);
|
||||
this.processResponse(response);
|
||||
this.toastService.showToast({
|
||||
title: this.i18nService.t("success"),
|
||||
@@ -165,7 +164,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest);
|
||||
request.id = key.id;
|
||||
try {
|
||||
key.removePromise = this.twoFactorApiService.deleteTwoFactorWebAuthn(request);
|
||||
key.removePromise = this.twoFactorService.deleteTwoFactorWebAuthn(request);
|
||||
const response = await key.removePromise;
|
||||
key.removePromise = null;
|
||||
await this.processResponse(response);
|
||||
@@ -179,7 +178,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
return;
|
||||
}
|
||||
const request = await this.buildRequestModel(SecretVerificationRequest);
|
||||
this.challengePromise = this.twoFactorApiService.getTwoFactorWebAuthnChallenge(request);
|
||||
this.challengePromise = this.twoFactorService.getTwoFactorWebAuthnChallenge(request);
|
||||
const challenge = await this.challengePromise;
|
||||
this.readDevice(challenge);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { UserVerificationService } from "@bitwarden/common/auth/abstractions/use
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { UpdateTwoFactorYubikeyOtpRequest } from "@bitwarden/common/auth/models/request/update-two-factor-yubikey-otp.request";
|
||||
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
@@ -74,9 +74,6 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
keys: Key[] = [];
|
||||
anyKeyHasNfc = false;
|
||||
|
||||
formPromise: Promise<TwoFactorYubiKeyResponse> | undefined;
|
||||
disablePromise: Promise<unknown> | undefined;
|
||||
|
||||
override componentName = "app-two-factor-yubikey";
|
||||
formGroup:
|
||||
| FormGroup<{
|
||||
@@ -95,7 +92,7 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorYubiKeyResponse>,
|
||||
twoFactorApiService: TwoFactorApiService,
|
||||
twoFactorService: TwoFactorService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService,
|
||||
@@ -105,7 +102,7 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
twoFactorApiService,
|
||||
twoFactorService,
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
logService,
|
||||
@@ -178,7 +175,7 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : "";
|
||||
request.nfc = this.formGroup.value.anyKeyHasNfc ?? false;
|
||||
|
||||
this.processResponse(await this.twoFactorApiService.putTwoFactorYubiKey(request));
|
||||
this.processResponse(await this.twoFactorService.putTwoFactorYubiKey(request));
|
||||
this.refreshFormArrayData();
|
||||
this.toastService.showToast({
|
||||
title: this.i18nService.t("success"),
|
||||
|
||||
@@ -71,15 +71,26 @@
|
||||
<div class="tw-mt-2 tw-text-wrap">{{ p.description }}</div>
|
||||
</div>
|
||||
<bit-item-action slot="end">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[disabled]="!(canAccessPremium$ | async) && p.premium"
|
||||
(click)="manage(p.type)"
|
||||
>
|
||||
{{ "manage" | i18n }}
|
||||
</button>
|
||||
@if (p.premium && p.enabled && !(canAccessPremium$ | async)) {
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="danger"
|
||||
(click)="disablePremium2faTypeForNonPremiumUser(p.type)"
|
||||
>
|
||||
{{ "disable" | i18n }}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[disabled]="!(canAccessPremium$ | async) && p.premium"
|
||||
(click)="manage(p.type)"
|
||||
>
|
||||
{{ "manage" | i18n }}
|
||||
</button>
|
||||
}
|
||||
</bit-item-action>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
|
||||
@@ -12,26 +12,28 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
import { PremiumBadgeComponent } from "@bitwarden/angular/billing/components/premium-badge";
|
||||
import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { TwoFactorProviderRequest } from "@bitwarden/common/auth/models/request/two-factor-provider.request";
|
||||
import { TwoFactorAuthenticatorResponse } from "@bitwarden/common/auth/models/response/two-factor-authenticator.response";
|
||||
import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response";
|
||||
import { TwoFactorEmailResponse } from "@bitwarden/common/auth/models/response/two-factor-email.response";
|
||||
import { TwoFactorWebAuthnResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
import { TwoFactorYubiKeyResponse } from "@bitwarden/common/auth/models/response/two-factor-yubi-key.response";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService, TwoFactorProviders } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { DialogRef, DialogService, ItemModule } from "@bitwarden/components";
|
||||
import { DialogRef, DialogService, ItemModule, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { HeaderModule } from "../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
@@ -59,7 +61,6 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
recoveryCodeWarningMessage: string;
|
||||
showPolicyWarning = false;
|
||||
loading = true;
|
||||
formPromise: Promise<any>;
|
||||
|
||||
tabbedHeader = true;
|
||||
|
||||
@@ -69,13 +70,15 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(
|
||||
protected dialogService: DialogService,
|
||||
protected twoFactorApiService: TwoFactorApiService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected messagingService: MessagingService,
|
||||
protected policyService: PolicyService,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
protected accountService: AccountService,
|
||||
protected configService: ConfigService,
|
||||
protected i18nService: I18nService,
|
||||
protected userVerificationService: UserVerificationService,
|
||||
protected toastService: ToastService,
|
||||
) {
|
||||
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
@@ -150,6 +153,50 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
return await lastValueFrom(twoFactorVerifyDialogRef.closed);
|
||||
}
|
||||
|
||||
/**
|
||||
* For users who enabled a premium-only 2fa provider,
|
||||
* they should still be allowed to disable that provider
|
||||
* (without otherwise modifying) if they no longer have
|
||||
* premium access [PM-21204]
|
||||
* @param type the 2FA Provider Type
|
||||
*/
|
||||
async disablePremium2faTypeForNonPremiumUser(type: TwoFactorProviderType) {
|
||||
// Use UserVerificationDialogComponent instead of TwoFactorVerifyComponent
|
||||
// because the latter makes GET API calls that require premium for YubiKey/Duo.
|
||||
// The disable endpoint only requires user verification, not provider configuration.
|
||||
const result = await UserVerificationDialogComponent.open(this.dialogService, {
|
||||
title: "twoStepLogin",
|
||||
verificationType: {
|
||||
type: "custom",
|
||||
verificationFn: async (secret) => {
|
||||
const request = await this.userVerificationService.buildRequest<TwoFactorProviderRequest>(
|
||||
secret,
|
||||
TwoFactorProviderRequest,
|
||||
);
|
||||
request.type = type;
|
||||
|
||||
await this.twoFactorService.putTwoFactorDisable(request);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.userAction === "cancel") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result.verificationSuccess) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: "",
|
||||
message: this.i18nService.t("twoStepDisabled"),
|
||||
});
|
||||
this.updateStatus(false, type);
|
||||
}
|
||||
|
||||
async manage(type: TwoFactorProviderType) {
|
||||
// clear any existing subscriptions before creating a new one
|
||||
this.twoFactorSetupSubscription?.unsubscribe();
|
||||
@@ -264,7 +311,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
protected getTwoFactorProviders() {
|
||||
return this.twoFactorApiService.getTwoFactorProviders();
|
||||
return this.twoFactorService.getEnabledTwoFactorProviders();
|
||||
}
|
||||
|
||||
protected filterProvider(type: TwoFactorProviderType): boolean {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
import { TwoFactorApiService } from "@bitwarden/common/auth/two-factor";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
import { TwoFactorResponse } from "@bitwarden/common/auth/types/two-factor-response";
|
||||
import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification";
|
||||
@@ -54,7 +54,7 @@ export class TwoFactorVerifyComponent {
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: TwoFactorVerifyDialogData,
|
||||
private dialogRef: DialogRef,
|
||||
private twoFactorApiService: TwoFactorApiService,
|
||||
private twoFactorService: TwoFactorService,
|
||||
private i18nService: I18nService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
) {
|
||||
@@ -110,22 +110,22 @@ export class TwoFactorVerifyComponent {
|
||||
private apiCall(request: SecretVerificationRequest): Promise<TwoFactorResponse> {
|
||||
switch (this.type) {
|
||||
case -1 as TwoFactorProviderType:
|
||||
return this.twoFactorApiService.getTwoFactorRecover(request);
|
||||
return this.twoFactorService.getTwoFactorRecover(request);
|
||||
case TwoFactorProviderType.Duo:
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
if (this.organizationId != null) {
|
||||
return this.twoFactorApiService.getTwoFactorOrganizationDuo(this.organizationId, request);
|
||||
return this.twoFactorService.getTwoFactorOrganizationDuo(this.organizationId, request);
|
||||
} else {
|
||||
return this.twoFactorApiService.getTwoFactorDuo(request);
|
||||
return this.twoFactorService.getTwoFactorDuo(request);
|
||||
}
|
||||
case TwoFactorProviderType.Email:
|
||||
return this.twoFactorApiService.getTwoFactorEmail(request);
|
||||
return this.twoFactorService.getTwoFactorEmail(request);
|
||||
case TwoFactorProviderType.WebAuthn:
|
||||
return this.twoFactorApiService.getTwoFactorWebAuthn(request);
|
||||
return this.twoFactorService.getTwoFactorWebAuthn(request);
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
return this.twoFactorApiService.getTwoFactorAuthenticator(request);
|
||||
return this.twoFactorService.getTwoFactorAuthenticator(request);
|
||||
case TwoFactorProviderType.Yubikey:
|
||||
return this.twoFactorApiService.getTwoFactorYubiKey(request);
|
||||
return this.twoFactorService.getTwoFactorYubiKey(request);
|
||||
default:
|
||||
throw new Error(`Unknown two-factor type: ${this.type}`);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AbstractThemingService } from "@bitwarden/angular/platform/services/the
|
||||
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
|
||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
@@ -31,7 +31,7 @@ export class InitService {
|
||||
private vaultTimeoutService: DefaultVaultTimeoutService,
|
||||
private i18nService: I18nServiceAbstraction,
|
||||
private eventUploadService: EventUploadServiceAbstraction,
|
||||
private twoFactorService: TwoFactorServiceAbstraction,
|
||||
private twoFactorService: TwoFactorService,
|
||||
private keyService: KeyServiceAbstraction,
|
||||
private themingService: AbstractThemingService,
|
||||
private encryptService: EncryptService,
|
||||
|
||||
@@ -12178,5 +12178,8 @@
|
||||
},
|
||||
"confirmNoSelectedCriticalApplicationsDesc": {
|
||||
"message": "Are you sure you want to continue?"
|
||||
},
|
||||
"userVerificationFailed": {
|
||||
"message": "User verification failed."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user