diff --git a/apps/web/src/app/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/organizations/settings/two-factor-setup.component.ts
index 12675fcf52..a8566e12db 100644
--- a/apps/web/src/app/organizations/settings/two-factor-setup.component.ts
+++ b/apps/web/src/app/organizations/settings/two-factor-setup.component.ts
@@ -3,7 +3,10 @@ import { ActivatedRoute } from "@angular/router";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
+import { I18nService } from "jslib-common/abstractions/i18n.service";
+import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
@@ -22,9 +25,21 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent {
messagingService: MessagingService,
policyService: PolicyService,
private route: ActivatedRoute,
- stateService: StateService
+ stateService: StateService,
+ platformUtilsService: PlatformUtilsService,
+ i18nService: I18nService,
+ logService: LogService
) {
- super(apiService, modalService, messagingService, policyService, stateService);
+ super(
+ apiService,
+ modalService,
+ messagingService,
+ policyService,
+ stateService,
+ platformUtilsService,
+ i18nService,
+ logService
+ );
}
async ngOnInit() {
diff --git a/apps/web/src/app/settings/two-factor-setup.component.html b/apps/web/src/app/settings/two-factor-setup.component.html
index 27fc2990e6..8a4a1f698c 100644
--- a/apps/web/src/app/settings/two-factor-setup.component.html
+++ b/apps/web/src/app/settings/two-factor-setup.component.html
@@ -55,6 +55,47 @@
+
diff --git a/apps/web/src/app/settings/two-factor-setup.component.ts b/apps/web/src/app/settings/two-factor-setup.component.ts
index bab2594237..d83177e13c 100644
--- a/apps/web/src/app/settings/two-factor-setup.component.ts
+++ b/apps/web/src/app/settings/two-factor-setup.component.ts
@@ -3,11 +3,15 @@ import { Component, OnInit, Type, ViewChild, ViewContainerRef } from "@angular/c
import { ModalRef } from "jslib-angular/components/modal/modal.ref";
import { ModalService } from "jslib-angular/services/modal.service";
import { ApiService } from "jslib-common/abstractions/api.service";
+import { I18nService } from "jslib-common/abstractions/i18n.service";
+import { LogService } from "jslib-common/abstractions/log.service";
import { MessagingService } from "jslib-common/abstractions/messaging.service";
+import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
import { PolicyService } from "jslib-common/abstractions/policy.service";
import { StateService } from "jslib-common/abstractions/state.service";
import { PolicyType } from "jslib-common/enums/policyType";
import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType";
+import { DeviceVerificationRequest } from "jslib-common/models/request/deviceVerificationRequest";
import { TwoFactorProviders } from "jslib-common/services/twoFactor.service";
import { TwoFactorAuthenticatorComponent } from "./two-factor-authenticator.component";
@@ -39,18 +43,32 @@ export class TwoFactorSetupComponent implements OnInit {
canAccessPremium: boolean;
showPolicyWarning = false;
loading = true;
+ enableDeviceVerification: boolean;
+ isDeviceVerificationSectionEnabled: boolean;
modal: ModalRef;
+ formPromise: Promise;
constructor(
protected apiService: ApiService,
protected modalService: ModalService,
protected messagingService: MessagingService,
protected policyService: PolicyService,
- private stateService: StateService
+ private stateService: StateService,
+ private platformUtilsService: PlatformUtilsService,
+ private i18nService: I18nService,
+ private logService: LogService
) {}
async ngOnInit() {
this.canAccessPremium = await this.stateService.getCanAccessPremium();
+ try {
+ const deviceVerificationSettings = await this.apiService.getDeviceVerificationSettings();
+ this.isDeviceVerificationSectionEnabled =
+ deviceVerificationSettings.isDeviceVerificationSectionEnabled;
+ this.enableDeviceVerification = deviceVerificationSettings.unknownDeviceVerificationEnabled;
+ } catch (e) {
+ this.logService.error(e);
+ }
for (const key in TwoFactorProviders) {
// eslint-disable-next-line
@@ -186,4 +204,37 @@ export class TwoFactorSetupComponent implements OnInit {
this.showPolicyWarning = false;
}
}
+
+ async submit() {
+ try {
+ if (this.enableDeviceVerification) {
+ const email = await this.stateService.getEmail();
+ const confirmed = await this.platformUtilsService.showDialog(
+ this.i18nService.t(
+ "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX",
+ email
+ ),
+ this.i18nService.t("deviceVerification"),
+ this.i18nService.t("yes"),
+ this.i18nService.t("no"),
+ "warning"
+ );
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ this.formPromise = this.apiService.putDeviceVerificationSettings(
+ new DeviceVerificationRequest(this.enableDeviceVerification)
+ );
+ await this.formPromise;
+ this.platformUtilsService.showToast(
+ "success",
+ null,
+ this.i18nService.t("updatedDeviceVerification")
+ );
+ } catch (e) {
+ this.logService.error(e);
+ }
+ }
}
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index 53cbcec8d4..a930e76fbf 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -5075,5 +5075,26 @@
},
"apiAccessToken": {
"message": "API Access Token"
+ },
+ "deviceVerification": {
+ "message": "Device Verification"
+ },
+ "enableDeviceVerification": {
+ "message": "Enable Device Verification"
+ },
+ "deviceVerificationDesc": {
+ "message": "When enabled, verification codes are sent to your email address when logging in from an unrecognized device"
+ },
+ "updatedDeviceVerification": {
+ "message": "Updated Device Verification"
+ },
+ "areYouSureYouWantToEnableDeviceVerificationTheVerificationCodeEmailsWillArriveAtX": {
+ "message": "Are you sure you want to enable Device Verification? The verification code emails will arrive at: $EMAIL$",
+ "placeholders": {
+ "email": {
+ "content": "$1",
+ "example": "My Email"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts
index 21a5373aa7..bda6e2d0a5 100644
--- a/libs/common/src/abstractions/api.service.ts
+++ b/libs/common/src/abstractions/api.service.ts
@@ -1,7 +1,9 @@
import { OrganizationConnectionType } from "jslib-common/enums/organizationConnectionType";
+import { DeviceVerificationRequest } from "jslib-common/models/request/deviceVerificationRequest";
import { OrganizationConnectionRequest } from "jslib-common/models/request/organizationConnectionRequest";
import { BillingHistoryResponse } from "jslib-common/models/response/billingHistoryResponse";
import { BillingPaymentResponse } from "jslib-common/models/response/billingPaymentResponse";
+import { DeviceVerificationResponse } from "jslib-common/models/response/deviceVerificationResponse";
import {
OrganizationConnectionConfigApis,
OrganizationConnectionResponse,
@@ -494,6 +496,10 @@ export abstract class ApiService {
postTwoFactorRecover: (request: TwoFactorRecoveryRequest) => Promise;
postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise;
postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise;
+ getDeviceVerificationSettings: () => Promise;
+ putDeviceVerificationSettings: (
+ request: DeviceVerificationRequest
+ ) => Promise;
getEmergencyAccessTrusted: () => Promise>;
getEmergencyAccessGranted: () => Promise>;
diff --git a/libs/common/src/models/request/deviceVerificationRequest.ts b/libs/common/src/models/request/deviceVerificationRequest.ts
new file mode 100644
index 0000000000..5e119efa19
--- /dev/null
+++ b/libs/common/src/models/request/deviceVerificationRequest.ts
@@ -0,0 +1,7 @@
+export class DeviceVerificationRequest {
+ unknownDeviceVerificationEnabled: boolean;
+
+ constructor(unknownDeviceVerificationEnabled: boolean) {
+ this.unknownDeviceVerificationEnabled = unknownDeviceVerificationEnabled;
+ }
+}
diff --git a/libs/common/src/models/response/deviceVerificationResponse.ts b/libs/common/src/models/response/deviceVerificationResponse.ts
new file mode 100644
index 0000000000..65762bb9b6
--- /dev/null
+++ b/libs/common/src/models/response/deviceVerificationResponse.ts
@@ -0,0 +1,16 @@
+import { BaseResponse } from "./baseResponse";
+
+export class DeviceVerificationResponse extends BaseResponse {
+ isDeviceVerificationSectionEnabled: boolean;
+ unknownDeviceVerificationEnabled: boolean;
+
+ constructor(response: any) {
+ super(response);
+ this.isDeviceVerificationSectionEnabled = this.getResponseProperty(
+ "IsDeviceVerificationSectionEnabled"
+ );
+ this.unknownDeviceVerificationEnabled = this.getResponseProperty(
+ "UnknownDeviceVerificationEnabled"
+ );
+ }
+}
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index 2e0e3b92cf..abb4bf485e 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -31,6 +31,7 @@ import { CipherRequest } from "../models/request/cipherRequest";
import { CipherShareRequest } from "../models/request/cipherShareRequest";
import { CollectionRequest } from "../models/request/collectionRequest";
import { DeleteRecoverRequest } from "../models/request/deleteRecoverRequest";
+import { DeviceVerificationRequest } from "../models/request/deviceVerificationRequest";
import { EmailRequest } from "../models/request/emailRequest";
import { EmailTokenRequest } from "../models/request/emailTokenRequest";
import { EmergencyAccessAcceptRequest } from "../models/request/emergencyAccessAcceptRequest";
@@ -121,6 +122,7 @@ import {
CollectionGroupDetailsResponse,
CollectionResponse,
} from "../models/response/collectionResponse";
+import { DeviceVerificationResponse } from "../models/response/deviceVerificationResponse";
import { DomainsResponse } from "../models/response/domainsResponse";
import {
EmergencyAccessGranteeDetailsResponse,
@@ -1572,6 +1574,30 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("POST", "/two-factor/send-email-login", request, false, false);
}
+ async getDeviceVerificationSettings(): Promise {
+ const r = await this.send(
+ "GET",
+ "/two-factor/get-device-verification-settings",
+ null,
+ true,
+ true
+ );
+ return new DeviceVerificationResponse(r);
+ }
+
+ async putDeviceVerificationSettings(
+ request: DeviceVerificationRequest
+ ): Promise {
+ const r = await this.send(
+ "PUT",
+ "/two-factor/device-verification-settings",
+ request,
+ true,
+ true
+ );
+ return new DeviceVerificationResponse(r);
+ }
+
// Emergency Access APIs
async getEmergencyAccessTrusted(): Promise> {