mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-12 13:39:14 +01:00
Feature/PM-1049 - TDEFflow 3 login decryption options - PR feedback changes (#5642)
* PM-1049 - PR Feedback change - Browser - replace incorrect use of routerlink with manual attribute styling to keep anchor styling + tab focus while not having a router action race condition for the log out action to complete. * PM-1049 - PR Feedback - State Service changes - rename get/setAcctDecryptionOptions to get/setAccountDecryptionOptions * PM-1049 - PR Feedback changes - LoginDecryptionOptionsComp - Remove unncessary appA11yTitle directives as title / aria text would be identical to the displayed inner button text. * DeviceType - Create sets of device types which other components can reference to avoid having to manually define groups of device types. * PM-1049 - PR Feedback Changes - Update base-login-decryption-options component to leverage async piped observables per best practices. Updated all client templates to leverage new data streams. * PM-1049 - BaseLoginDecryptionOptionsComp - Add validation service for generic error handling * PM-1049 - DeviceResponse mistakenly had name as a number instead of a string * PM-1049 - First draft of creating observable based data store service for Devices so that the base login comp can leverage it instead of calling the devices API service directly (as it will be moved into the SDK in the future). * PM-1049 - Register new DevicesService on jslib-services module for use in components. * PM-1049 - Add new hasDevicesOfTypes call to devices data store svc + devices API service. * PM-1049 - BaseLoginDecryptionOptionsComp - wire up call to devicesService.hasDevicesOfTypes to replace getDevices() to avoid bringing down all trusted device information unnecessarily. * PM-1049 - LoginDecryptionOptionsComp - Web HTML - clean up loading state so it displays spinner centered properly. * PM-1049 - LoginDecryptionOptionsComp - Desktop HTML - Don't show login initiated title while page is loading to match other clients behavior. * PM-1049 - Devices Services - Update naming of hasDevicesOfTypes to match new name on back end + route change to getDevicesExistenseByTypes * PM-1049 - Device Response & View models - remove keys which are going to be deprecated on the base model * PM-1049 - DevicesService - devicesBSubject --> devicesSubject rename per PR feedback * PM-1049 - Devices Services - correct spelling of existence (*facepalm*) * PM-1049 - Update comment for clarity per PR feedback * PM-1049 - DevicesSvc - UserSymKey --> UserKey rename * PM-1049 - BaseLoginDecryptionOptions - replace user email source - get from stateService vs tokenService. * PM-1049 - BaseLoginDecryptionOptions - Remove uncessary check for userEmail as we will always have it here otherwise everything in the app is broken. * PM-1049 - BaseLoginDecryptionOptions - Finish cleaning up removal of user email from showReqAdminApprovalBtn$ stream * PM-1049 - LoginDecryptionOptionsComp - HTML revisions in web & browser to better space out buttons using tailwind or top margin to avoid need for multiple async pipes and shareReplay. * PM-1049 - DevicesService - of course all observables should have $ suffix. Facepalm. * PM-1049 - BaseLoginDecryptionOptionsComp - Update verbiage and style of destroy observable used for hooking into ngOnDestroy lifecycle to clean up all observables * PM-1049 - BaseLoginDecryptionOptions - PR feedback changes - refactor user email to have an underlying bSubject stream to ensure subscription/promise execution separately from the template async pipe subscribing to the stream. * PM-1049 - DevicesApiService - getDevicesExistenceByTypes - PR feedback - explicitly convert result to boolean instead of casting. * PM-1049 - BaseLoginDecryptionOptionsComp - Add ShareReplay for getAccountDecryptionOptions + context per PR feedback * PM-1049 - LoginDecryptionOptionsComp - Completely back away from template async pipe reactive approach as it caused massively increased complexity for little gain. Instead, just focus on reactively pulling asynchronously retrieved data and setting page loading state simply. This just works and is so much less overhead. + Add comments re flows of the component to be done later * PM-1049- Revert DevicesService implementation from smart data store cache service giant mess into simple, clean data passthrough service to avoid complexity and keep moving forward. YAGNI Co-authored-by: Andreas Coroiu <andreas@andreascoroiu.com> * PM-1049 - DeviceCryptoService - Add decryptUserKey method (WIP) * PM-1049 - AccountDecryptionOptions - add get helpers for checking for trusted device / key connector decryption option existence. * PM-1049 - SSO Login Strategy - added comments in setUserKey method for where we will probably be consuming device keys and determining if the device is trusted or not (i.e., if we can get a decrypted user sym key in memory) * PM-1049 - DeviceCryptoSvc.decryptUserKey - Update method to properly use state service device key retrieval + add TODO to figure out what to do if user has previously had a device key and has cleared their local cache (which will result in the device being untrusted now) * PM-1049 - SSO Login Strategy - add comment re future passkey login strategy support * PM-2759 - SSO & 2FA components updated with v0 of navigation logic to send users to LoginDecryptionOptions * PM-1049 - Account > AccountDecryptionOptions - can't create getter helper methods for determining if user has decryption options b/c of issues w/ account deserialization. Moving past b/c I can just easily check if the given options are not undefined. * PM-2759 - Add TODOs for deprecation of id token response resetMasterPassword logic and replacement with use of accountDecryptionOptions --------- Co-authored-by: Andreas Coroiu <andreas@andreascoroiu.com>
This commit is contained in:
parent
9ff4bbbbe0
commit
fa11b60c5b
@ -41,9 +41,6 @@
|
|||||||
(click)="approveFromOtherDevice()"
|
(click)="approveFromOtherDevice()"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn primary block"
|
class="btn primary block"
|
||||||
[ngClass]="{
|
|
||||||
'btn-bottom-margin': showReqAdminApprovalBtn || showApproveWithMasterPasswordBtn
|
|
||||||
}"
|
|
||||||
>
|
>
|
||||||
<b>{{ "approveFromYourOtherDevice" | i18n }}</b>
|
<b>{{ "approveFromYourOtherDevice" | i18n }}</b>
|
||||||
</button>
|
</button>
|
||||||
@ -51,14 +48,13 @@
|
|||||||
*ngIf="showReqAdminApprovalBtn"
|
*ngIf="showReqAdminApprovalBtn"
|
||||||
(click)="requestAdminApproval()"
|
(click)="requestAdminApproval()"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn block"
|
class="btn block btn-top-margin"
|
||||||
[ngClass]="{ 'btn-bottom-margin': showApproveWithMasterPasswordBtn }"
|
|
||||||
>
|
>
|
||||||
{{ "requestAdminApproval" | i18n }}
|
{{ "requestAdminApproval" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn block"
|
class="btn block btn-top-margin"
|
||||||
*ngIf="showApproveWithMasterPasswordBtn"
|
*ngIf="showApproveWithMasterPasswordBtn"
|
||||||
(click)="approveWithMasterPassword()"
|
(click)="approveWithMasterPassword()"
|
||||||
>
|
>
|
||||||
@ -70,7 +66,9 @@
|
|||||||
|
|
||||||
<div class="small mx-5px">
|
<div class="small mx-5px">
|
||||||
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ userEmail }}</p>
|
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ userEmail }}</p>
|
||||||
<a [routerLink]="[]" (click)="logOut()">{{ "notYou" | i18n }}</a>
|
<a tabindex="0" role="button" style="cursor: pointer" (click)="logOut()">{{
|
||||||
|
"notYou" | i18n
|
||||||
|
}}</a>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,11 +3,11 @@ import { FormBuilder } from "@angular/forms";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "browser-login-decryption-options",
|
selector: "browser-login-decryption-options",
|
||||||
@ -16,21 +16,21 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
|||||||
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
||||||
constructor(
|
constructor(
|
||||||
formBuilder: FormBuilder,
|
formBuilder: FormBuilder,
|
||||||
devicesApiService: DevicesApiServiceAbstraction,
|
devicesService: DevicesServiceAbstraction,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
router: Router,
|
router: Router,
|
||||||
messagingService: MessagingService,
|
messagingService: MessagingService,
|
||||||
tokenService: TokenService,
|
loginService: LoginService,
|
||||||
loginService: LoginService
|
validationService: ValidationService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
formBuilder,
|
formBuilder,
|
||||||
devicesApiService,
|
devicesService,
|
||||||
stateService,
|
stateService,
|
||||||
router,
|
router,
|
||||||
messagingService,
|
messagingService,
|
||||||
tokenService,
|
loginService,
|
||||||
loginService
|
validationService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|||||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -35,6 +36,7 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
syncService: SyncService,
|
syncService: SyncService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
private vaultTimeoutService: VaultTimeoutService
|
private vaultTimeoutService: VaultTimeoutService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -48,7 +50,8 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService
|
logService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
|
|
||||||
const url = this.environmentService.getWebVaultUrl();
|
const url = this.environmentService.getWebVaultUrl();
|
||||||
|
@ -11,6 +11,7 @@ import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor
|
|||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@ -48,6 +49,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
loginService: LoginService,
|
loginService: LoginService,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
private dialogService: DialogServiceAbstraction
|
private dialogService: DialogServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -63,7 +65,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
logService,
|
logService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService
|
loginService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
super.onSuccessfulLogin = () => {
|
super.onSuccessfulLogin = () => {
|
||||||
this.loginService.clearValues();
|
this.loginService.clearValues();
|
||||||
|
@ -671,8 +671,8 @@ main {
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-bottom-margin {
|
.btn-top-margin {
|
||||||
margin-bottom: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rememberThisDeviceHintText {
|
#rememberThisDeviceHintText {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<div id="login-decryption-options-page">
|
<div id="login-decryption-options-page">
|
||||||
<div id="content" class="content">
|
<div id="content" class="content">
|
||||||
<img class="logo-image" alt="Bitwarden" />
|
<img class="logo-image" alt="Bitwarden" />
|
||||||
<h1 id="heading">{{ "logInInitiated" | i18n }}</h1>
|
|
||||||
|
|
||||||
<div class="container loading-spinner" *ngIf="loading">
|
<div class="container loading-spinner" *ngIf="loading">
|
||||||
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
|
<i class="bwi bwi-spinner bwi-spin bwi-3x" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="!loading">
|
<ng-container *ngIf="!loading">
|
||||||
|
<h1 id="heading">{{ "logInInitiated" | i18n }}</h1>
|
||||||
<h6 id="subHeading" class="standard-bottom-margin">{{ "deviceApprovalRequired" | i18n }}</h6>
|
<h6 id="subHeading" class="standard-bottom-margin">{{ "deviceApprovalRequired" | i18n }}</h6>
|
||||||
|
|
||||||
<form id="rememberDeviceForm" class="standard-bottom-margin" [formGroup]="rememberDeviceForm">
|
<form id="rememberDeviceForm" class="standard-bottom-margin" [formGroup]="rememberDeviceForm">
|
||||||
|
@ -3,11 +3,11 @@ import { FormBuilder } from "@angular/forms";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "desktop-login-decryption-options",
|
selector: "desktop-login-decryption-options",
|
||||||
@ -16,21 +16,21 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
|||||||
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
||||||
constructor(
|
constructor(
|
||||||
formBuilder: FormBuilder,
|
formBuilder: FormBuilder,
|
||||||
devicesApiService: DevicesApiServiceAbstraction,
|
devicesService: DevicesServiceAbstraction,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
router: Router,
|
router: Router,
|
||||||
messagingService: MessagingService,
|
messagingService: MessagingService,
|
||||||
tokenService: TokenService,
|
loginService: LoginService,
|
||||||
loginService: LoginService
|
validationService: ValidationService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
formBuilder,
|
formBuilder,
|
||||||
devicesApiService,
|
devicesService,
|
||||||
stateService,
|
stateService,
|
||||||
router,
|
router,
|
||||||
messagingService,
|
messagingService,
|
||||||
tokenService,
|
loginService,
|
||||||
loginService
|
validationService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router";
|
|||||||
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
import { SsoComponent as BaseSsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -30,7 +31,8 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
cryptoFunctionService: CryptoFunctionService,
|
cryptoFunctionService: CryptoFunctionService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
logService: LogService
|
logService: LogService,
|
||||||
|
configService: ConfigServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
authService,
|
authService,
|
||||||
@ -43,7 +45,8 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService
|
logService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
super.onSuccessfulLogin = () => {
|
super.onSuccessfulLogin = () => {
|
||||||
return syncService.fullSync(true);
|
return syncService.fullSync(true);
|
||||||
|
@ -9,6 +9,7 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"
|
|||||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@ -43,7 +44,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
logService: LogService,
|
logService: LogService,
|
||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
loginService: LoginService
|
loginService: LoginService,
|
||||||
|
configService: ConfigServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
authService,
|
authService,
|
||||||
@ -58,7 +60,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
logService,
|
logService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService
|
loginService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
super.onSuccessfulLogin = () => {
|
super.onSuccessfulLogin = () => {
|
||||||
this.loginService.clearValues();
|
this.loginService.clearValues();
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
<ng-container *ngIf="loading">
|
<div class="tw-container tw-mx-auto">
|
||||||
<i
|
|
||||||
class="bwi bwi-spinner bwi-spin text-muted"
|
|
||||||
title="{{ 'loading' | i18n }}"
|
|
||||||
aria-hidden="true"
|
|
||||||
></i>
|
|
||||||
<span class="sr-only">{{ "loading" | i18n }}</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<div class="tw-container tw-mx-auto" *ngIf="!loading">
|
|
||||||
<div
|
<div
|
||||||
class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8"
|
class="tw-mx-auto tw-mt-5 tw-flex tw-max-w-lg tw-flex-col tw-items-center tw-justify-center tw-p-8"
|
||||||
>
|
>
|
||||||
@ -15,7 +6,19 @@
|
|||||||
<img class="logo logo-themed" alt="Bitwarden" />
|
<img class="logo logo-themed" alt="Bitwarden" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="loading">
|
||||||
|
<p class="text-center">
|
||||||
|
<i
|
||||||
|
class="bwi bwi-spinner bwi-spin bwi-2x text-muted"
|
||||||
|
title="{{ 'loading' | i18n }}"
|
||||||
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
<span class="sr-only">{{ "loading" | i18n }}</span>
|
||||||
|
</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
*ngIf="!loading"
|
||||||
class="tw-w-full tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
|
class="tw-w-full tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
|
||||||
>
|
>
|
||||||
<h2 bitTypography="h2" class="tw-mb-6">{{ "loginInitiated" | i18n }}</h2>
|
<h2 bitTypography="h2" class="tw-mb-6">{{ "loginInitiated" | i18n }}</h2>
|
||||||
@ -30,7 +33,7 @@
|
|||||||
</bit-form-control>
|
</bit-form-control>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="tw-mb-6 tw-flex tw-flex-col">
|
<div class="tw-mb-6 tw-flex tw-flex-col tw-space-y-3">
|
||||||
<button
|
<button
|
||||||
*ngIf="showApproveFromOtherDeviceBtn"
|
*ngIf="showApproveFromOtherDeviceBtn"
|
||||||
(click)="approveFromOtherDevice()"
|
(click)="approveFromOtherDevice()"
|
||||||
@ -38,8 +41,6 @@
|
|||||||
type="button"
|
type="button"
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
[block]="true"
|
[block]="true"
|
||||||
[ngClass]="{ 'tw-mb-3': showReqAdminApprovalBtn || showApproveWithMasterPasswordBtn }"
|
|
||||||
[appA11yTitle]="'approveFromYourOtherDevice' | i18n"
|
|
||||||
>
|
>
|
||||||
{{ "approveFromYourOtherDevice" | i18n }}
|
{{ "approveFromYourOtherDevice" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
@ -50,8 +51,6 @@
|
|||||||
bitButton
|
bitButton
|
||||||
type="button"
|
type="button"
|
||||||
buttonType="secondary"
|
buttonType="secondary"
|
||||||
[ngClass]="{ 'tw-mb-3': showApproveWithMasterPasswordBtn }"
|
|
||||||
[appA11yTitle]="'requestAdminApproval' | i18n"
|
|
||||||
>
|
>
|
||||||
{{ "requestAdminApproval" | i18n }}
|
{{ "requestAdminApproval" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
@ -62,7 +61,6 @@
|
|||||||
bitButton
|
bitButton
|
||||||
type="button"
|
type="button"
|
||||||
buttonType="secondary"
|
buttonType="secondary"
|
||||||
[appA11yTitle]="'approveWithMasterPassword' | i18n"
|
|
||||||
>
|
>
|
||||||
{{ "approveWithMasterPassword" | i18n }}
|
{{ "approveWithMasterPassword" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -3,11 +3,11 @@ import { FormBuilder } from "@angular/forms";
|
|||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
import { BaseLoginDecryptionOptionsComponent } from "@bitwarden/angular/auth/components/base-login-decryption-options.component";
|
||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
@Component({
|
@Component({
|
||||||
selector: "web-login-decryption-options",
|
selector: "web-login-decryption-options",
|
||||||
templateUrl: "login-decryption-options.component.html",
|
templateUrl: "login-decryption-options.component.html",
|
||||||
@ -15,21 +15,21 @@ import { StateService } from "@bitwarden/common/platform/abstractions/state.serv
|
|||||||
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
export class LoginDecryptionOptionsComponent extends BaseLoginDecryptionOptionsComponent {
|
||||||
constructor(
|
constructor(
|
||||||
formBuilder: FormBuilder,
|
formBuilder: FormBuilder,
|
||||||
devicesApiService: DevicesApiServiceAbstraction,
|
devicesService: DevicesServiceAbstraction,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
router: Router,
|
router: Router,
|
||||||
messagingService: MessagingService,
|
messagingService: MessagingService,
|
||||||
tokenService: TokenService,
|
loginService: LoginService,
|
||||||
loginService: LoginService
|
validationService: ValidationService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
formBuilder,
|
formBuilder,
|
||||||
devicesApiService,
|
devicesService,
|
||||||
stateService,
|
stateService,
|
||||||
router,
|
router,
|
||||||
messagingService,
|
messagingService,
|
||||||
tokenService,
|
loginService,
|
||||||
loginService
|
validationService
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
|||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { HttpStatusCode } from "@bitwarden/common/enums";
|
import { HttpStatusCode } from "@bitwarden/common/enums";
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -37,6 +38,7 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
logService: LogService,
|
logService: LogService,
|
||||||
|
configService: ConfigServiceAbstraction,
|
||||||
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
private orgDomainApiService: OrgDomainApiServiceAbstraction,
|
||||||
private loginService: LoginService,
|
private loginService: LoginService,
|
||||||
private validationService: ValidationService
|
private validationService: ValidationService
|
||||||
@ -52,7 +54,8 @@ export class SsoComponent extends BaseSsoComponent {
|
|||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService
|
logService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
this.redirectUri = window.location.origin + "/sso-connector.html";
|
this.redirectUri = window.location.origin + "/sso-connector.html";
|
||||||
this.clientId = "web";
|
this.clientId = "web";
|
||||||
|
@ -9,6 +9,7 @@ import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"
|
|||||||
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
|
||||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
@ -42,7 +43,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
twoFactorService: TwoFactorService,
|
twoFactorService: TwoFactorService,
|
||||||
appIdService: AppIdService,
|
appIdService: AppIdService,
|
||||||
private routerService: RouterService,
|
private routerService: RouterService,
|
||||||
loginService: LoginService
|
loginService: LoginService,
|
||||||
|
configService: ConfigServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
authService,
|
authService,
|
||||||
@ -57,7 +59,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||||||
logService,
|
logService,
|
||||||
twoFactorService,
|
twoFactorService,
|
||||||
appIdService,
|
appIdService,
|
||||||
loginService
|
loginService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { SsoComponent } from "@bitwarden/angular/auth/components/sso.component";
|
|||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -32,7 +33,8 @@ export class LinkSsoComponent extends SsoComponent implements AfterContentInit {
|
|||||||
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
stateService: StateService,
|
stateService: StateService,
|
||||||
environmentService: EnvironmentService,
|
environmentService: EnvironmentService,
|
||||||
logService: LogService
|
logService: LogService,
|
||||||
|
configService: ConfigServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
authService,
|
authService,
|
||||||
@ -45,7 +47,8 @@ export class LinkSsoComponent extends SsoComponent implements AfterContentInit {
|
|||||||
cryptoFunctionService,
|
cryptoFunctionService,
|
||||||
environmentService,
|
environmentService,
|
||||||
passwordGenerationService,
|
passwordGenerationService,
|
||||||
logService
|
logService,
|
||||||
|
configService
|
||||||
);
|
);
|
||||||
|
|
||||||
this.returnUri = "/settings/organizations";
|
this.returnUri = "/settings/organizations";
|
||||||
|
@ -1,22 +1,28 @@
|
|||||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormBuilder } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { Subject } from "rxjs";
|
import { Observable, Subject, catchError, forkJoin, from, of, finalize, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
|
||||||
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import {
|
||||||
import { DeviceType } from "@bitwarden/common/enums/device-type.enum";
|
DesktopDeviceTypes,
|
||||||
|
DeviceType,
|
||||||
|
MobileDeviceTypes,
|
||||||
|
} from "@bitwarden/common/enums/device-type.enum";
|
||||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
|
||||||
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||||
|
|
||||||
// TODO: replace this base component with a service per latest ADR
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
||||||
private componentDestroyed$: Subject<void> = new Subject();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
userEmail: string = null;
|
showApproveFromOtherDeviceBtn: boolean;
|
||||||
|
showReqAdminApprovalBtn: boolean;
|
||||||
|
showApproveWithMasterPasswordBtn: boolean;
|
||||||
|
userEmail: string;
|
||||||
|
|
||||||
rememberDeviceForm = this.formBuilder.group({
|
rememberDeviceForm = this.formBuilder.group({
|
||||||
rememberDevice: [true],
|
rememberDevice: [true],
|
||||||
@ -24,55 +30,99 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
loading = true;
|
loading = true;
|
||||||
|
|
||||||
showApproveFromOtherDeviceBtn = false;
|
|
||||||
showReqAdminApprovalBtn = false;
|
|
||||||
showApproveWithMasterPasswordBtn = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected formBuilder: FormBuilder,
|
protected formBuilder: FormBuilder,
|
||||||
protected devicesApiService: DevicesApiServiceAbstraction,
|
protected devicesService: DevicesServiceAbstraction,
|
||||||
protected stateService: StateService,
|
protected stateService: StateService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected messagingService: MessagingService,
|
protected messagingService: MessagingService,
|
||||||
protected tokenService: TokenService,
|
protected loginService: LoginService,
|
||||||
protected loginService: LoginService
|
private validationService: ValidationService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
ngOnInit() {
|
||||||
// Determine if the user has any mobile or desktop devices
|
// Note: this is probably not a comprehensive write up of all scenarios:
|
||||||
// to determine if we should show the approve from other device button
|
|
||||||
const devicesListResponse = await this.devicesApiService.getDevices();
|
|
||||||
for (const device of devicesListResponse.data) {
|
|
||||||
if (
|
|
||||||
device.type === DeviceType.Android ||
|
|
||||||
device.type === DeviceType.iOS ||
|
|
||||||
device.type === DeviceType.AndroidAmazon ||
|
|
||||||
device.type === DeviceType.WindowsDesktop ||
|
|
||||||
device.type === DeviceType.MacOsDesktop ||
|
|
||||||
device.type === DeviceType.LinuxDesktop ||
|
|
||||||
device.type === DeviceType.UWP
|
|
||||||
) {
|
|
||||||
this.showApproveFromOtherDeviceBtn = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const acctDecryptionOptions: AccountDecryptionOptions =
|
// If the TDE feature flag is enabled and TDE is configured for the org that the user is a member of,
|
||||||
await this.stateService.getAcctDecryptionOptions();
|
// then new and existing users can be redirected here after completing the SSO flow (and 2FA if enabled).
|
||||||
|
|
||||||
// Get user's email from access token:
|
// First we must determine user type (new or existing):
|
||||||
this.userEmail = await this.tokenService.getEmail();
|
|
||||||
|
|
||||||
// Show the admin approval btn if user has TDE enabled and the org admin approval policy is set && user email is not null
|
// New User
|
||||||
this.showReqAdminApprovalBtn =
|
// - present user with option to remember the device or not (trust the device)
|
||||||
!!acctDecryptionOptions.trustedDeviceOption?.hasAdminApproval && this.userEmail != null;
|
// - present a continue button to proceed to the vault
|
||||||
|
// - loadNewUserData() --> will need to load enrollment status and user email address.
|
||||||
|
|
||||||
this.showApproveWithMasterPasswordBtn = acctDecryptionOptions.hasMasterPassword;
|
// Existing User
|
||||||
|
// - Determine if user is an admin with access to account recovery in admin console
|
||||||
|
// - Determine if user has a MP or not, if not, they must be redirected to set one (see PM-1035)
|
||||||
|
// - Determine if device is trusted or not via device crypto service (method not yet written)
|
||||||
|
// - If not trusted, present user with login decryption options (approve from other device, approve with master password, request admin approval)
|
||||||
|
// - loadUntrustedDeviceData()
|
||||||
|
|
||||||
// TODO: do I extend the lock guard for the lock screen to prevent the user from getting to the lock screen
|
this.loadUntrustedDeviceData();
|
||||||
// if they do not have a master password set
|
}
|
||||||
|
|
||||||
this.loading = false;
|
loadUntrustedDeviceData() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const mobileAndDesktopDeviceTypes: DeviceType[] = Array.from(MobileDeviceTypes).concat(
|
||||||
|
Array.from(DesktopDeviceTypes)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Note: Each obs must handle error here and protect inner observable b/c we are using forkJoin below
|
||||||
|
// as per RxJs docs: if any given observable errors at some point, then
|
||||||
|
// forkJoin will error as well and immediately unsubscribe from the other observables.
|
||||||
|
const mobileOrDesktopDevicesExistence$ = this.devicesService
|
||||||
|
.getDevicesExistenceByTypes$(mobileAndDesktopDeviceTypes)
|
||||||
|
.pipe(
|
||||||
|
catchError((err: unknown) => {
|
||||||
|
this.validationService.showError(err);
|
||||||
|
return of(undefined);
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
);
|
||||||
|
|
||||||
|
const accountDecryptionOptions$: Observable<AccountDecryptionOptions> = from(
|
||||||
|
this.stateService.getAccountDecryptionOptions()
|
||||||
|
).pipe(
|
||||||
|
catchError((err: unknown) => {
|
||||||
|
this.validationService.showError(err);
|
||||||
|
return of(undefined);
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
);
|
||||||
|
|
||||||
|
const email$ = from(this.stateService.getEmail()).pipe(
|
||||||
|
catchError((err: unknown) => {
|
||||||
|
this.validationService.showError(err);
|
||||||
|
return of(undefined);
|
||||||
|
}),
|
||||||
|
takeUntil(this.destroy$)
|
||||||
|
);
|
||||||
|
|
||||||
|
forkJoin({
|
||||||
|
mobileOrDesktopDevicesExistence: mobileOrDesktopDevicesExistence$,
|
||||||
|
accountDecryptionOptions: accountDecryptionOptions$,
|
||||||
|
email: email$,
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
takeUntil(this.destroy$),
|
||||||
|
finalize(() => {
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe(({ mobileOrDesktopDevicesExistence, accountDecryptionOptions, email }) => {
|
||||||
|
this.showApproveFromOtherDeviceBtn = mobileOrDesktopDevicesExistence || false;
|
||||||
|
|
||||||
|
this.showReqAdminApprovalBtn =
|
||||||
|
!!accountDecryptionOptions?.trustedDeviceOption?.hasAdminApproval || false;
|
||||||
|
|
||||||
|
this.showApproveWithMasterPasswordBtn =
|
||||||
|
accountDecryptionOptions?.hasMasterPassword || false;
|
||||||
|
|
||||||
|
this.userEmail = email;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
approveFromOtherDevice() {
|
approveFromOtherDevice() {
|
||||||
@ -108,7 +158,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.componentDestroyed$.next();
|
this.destroy$.next();
|
||||||
this.componentDestroyed$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
|
|||||||
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
|
||||||
import { SsoLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
|
import { SsoLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
|
||||||
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
|
import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
@ -15,6 +17,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
|||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
@ -32,6 +35,7 @@ export class SsoComponent {
|
|||||||
|
|
||||||
protected twoFactorRoute = "2fa";
|
protected twoFactorRoute = "2fa";
|
||||||
protected successRoute = "lock";
|
protected successRoute = "lock";
|
||||||
|
protected trustedDeviceEncRoute = "login-initiated";
|
||||||
protected changePasswordRoute = "set-password";
|
protected changePasswordRoute = "set-password";
|
||||||
protected forcePasswordResetRoute = "update-temp-password";
|
protected forcePasswordResetRoute = "update-temp-password";
|
||||||
protected clientId: string;
|
protected clientId: string;
|
||||||
@ -50,7 +54,8 @@ export class SsoComponent {
|
|||||||
protected cryptoFunctionService: CryptoFunctionService,
|
protected cryptoFunctionService: CryptoFunctionService,
|
||||||
protected environmentService: EnvironmentService,
|
protected environmentService: EnvironmentService,
|
||||||
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
protected logService: LogService
|
protected logService: LogService,
|
||||||
|
protected configService: ConfigServiceAbstraction
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
@ -183,13 +188,8 @@ export class SsoComponent {
|
|||||||
orgIdFromState
|
orgIdFromState
|
||||||
);
|
);
|
||||||
this.formPromise = this.authService.logIn(credentials);
|
this.formPromise = this.authService.logIn(credentials);
|
||||||
|
|
||||||
// if device is trusted go to success route
|
|
||||||
// what does it mean for a device to not be trusted:
|
|
||||||
// -- no device key stored locally in secure storage
|
|
||||||
// -- no device encrypted user symmetric key from server
|
|
||||||
|
|
||||||
const response = await this.formPromise;
|
const response = await this.formPromise;
|
||||||
|
|
||||||
if (response.requiresTwoFactor) {
|
if (response.requiresTwoFactor) {
|
||||||
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||||
await this.onSuccessfulLoginTwoFactorNavigate();
|
await this.onSuccessfulLoginTwoFactorNavigate();
|
||||||
@ -202,6 +202,10 @@ export class SsoComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (response.resetMasterPassword) {
|
} else if (response.resetMasterPassword) {
|
||||||
|
// TODO: for TDE, we are going to deprecate using response.resetMasterPassword
|
||||||
|
// and instead rely on accountDecryptionOptions to determine if the user needs to set a password
|
||||||
|
// Users are allowed to not have a MP if TDE feature enabled + TDE configured. Otherwise, they must set a MP
|
||||||
|
// src: https://bitwarden.atlassian.net/browse/PM-2759?focusedCommentId=39438
|
||||||
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
|
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
|
||||||
await this.onSuccessfulLoginChangePasswordNavigate();
|
await this.onSuccessfulLoginChangePasswordNavigate();
|
||||||
} else {
|
} else {
|
||||||
@ -224,7 +228,21 @@ export class SsoComponent {
|
|||||||
if (this.onSuccessfulLoginNavigate != null) {
|
if (this.onSuccessfulLoginNavigate != null) {
|
||||||
await this.onSuccessfulLoginNavigate();
|
await this.onSuccessfulLoginNavigate();
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate([this.successRoute]);
|
const trustedDeviceEncryptionFeatureActive = await this.configService.getFeatureFlagBool(
|
||||||
|
FeatureFlag.TrustedDeviceEncryption
|
||||||
|
);
|
||||||
|
|
||||||
|
const accountDecryptionOptions: AccountDecryptionOptions =
|
||||||
|
await this.stateService.getAccountDecryptionOptions();
|
||||||
|
|
||||||
|
if (
|
||||||
|
trustedDeviceEncryptionFeatureActive &&
|
||||||
|
accountDecryptionOptions.trustedDeviceOption !== undefined
|
||||||
|
) {
|
||||||
|
this.router.navigate([this.trustedDeviceEncRoute]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.successRoute]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -14,12 +14,15 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
|
|||||||
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request";
|
||||||
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service";
|
||||||
import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
|
import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe";
|
||||||
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
|
import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config.service.abstraction";
|
||||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||||
|
import { AccountDecryptionOptions } from "@bitwarden/common/platform/models/domain/account";
|
||||||
|
|
||||||
import { CaptchaProtectedComponent } from "./captcha-protected.component";
|
import { CaptchaProtectedComponent } from "./captcha-protected.component";
|
||||||
|
|
||||||
@ -44,6 +47,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
|||||||
|
|
||||||
protected loginRoute = "login";
|
protected loginRoute = "login";
|
||||||
protected successRoute = "vault";
|
protected successRoute = "vault";
|
||||||
|
protected trustedDeviceEncRoute = "login-initiated";
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
@ -58,7 +62,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
|||||||
protected logService: LogService,
|
protected logService: LogService,
|
||||||
protected twoFactorService: TwoFactorService,
|
protected twoFactorService: TwoFactorService,
|
||||||
protected appIdService: AppIdService,
|
protected appIdService: AppIdService,
|
||||||
protected loginService: LoginService
|
protected loginService: LoginService,
|
||||||
|
protected configService: ConfigServiceAbstraction
|
||||||
) {
|
) {
|
||||||
super(environmentService, i18nService, platformUtilsService);
|
super(environmentService, i18nService, platformUtilsService);
|
||||||
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
||||||
@ -207,6 +212,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
|||||||
this.onSuccessfulLogin();
|
this.onSuccessfulLogin();
|
||||||
}
|
}
|
||||||
if (response.resetMasterPassword) {
|
if (response.resetMasterPassword) {
|
||||||
|
// TODO: for TDE, we are going to deprecate using response.resetMasterPassword
|
||||||
|
// and instead rely on accountDecryptionOptions to determine if the user needs to set a password
|
||||||
|
// Users are allowed to not have a MP if TDE feature enabled + TDE configured. Otherwise, they must set a MP
|
||||||
|
// src: https://bitwarden.atlassian.net/browse/PM-2759?focusedCommentId=39438
|
||||||
this.successRoute = "set-password";
|
this.successRoute = "set-password";
|
||||||
}
|
}
|
||||||
if (response.forcePasswordReset !== ForceResetPasswordReason.None) {
|
if (response.forcePasswordReset !== ForceResetPasswordReason.None) {
|
||||||
@ -217,11 +226,28 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
|||||||
await this.onSuccessfulLoginNavigate();
|
await this.onSuccessfulLoginNavigate();
|
||||||
} else {
|
} else {
|
||||||
this.loginService.clearValues();
|
this.loginService.clearValues();
|
||||||
this.router.navigate([this.successRoute], {
|
|
||||||
queryParams: {
|
const ssoTo2faFlowActive = this.route.snapshot.queryParamMap.get("sso") === "true";
|
||||||
identifier: this.identifier,
|
const trustedDeviceEncryptionFeatureActive = await this.configService.getFeatureFlagBool(
|
||||||
},
|
FeatureFlag.TrustedDeviceEncryption
|
||||||
});
|
);
|
||||||
|
|
||||||
|
const accountDecryptionOptions: AccountDecryptionOptions =
|
||||||
|
await this.stateService.getAccountDecryptionOptions();
|
||||||
|
|
||||||
|
if (
|
||||||
|
ssoTo2faFlowActive &&
|
||||||
|
trustedDeviceEncryptionFeatureActive &&
|
||||||
|
accountDecryptionOptions.trustedDeviceOption !== undefined
|
||||||
|
) {
|
||||||
|
this.router.navigate([this.trustedDeviceEncRoute]);
|
||||||
|
} else {
|
||||||
|
this.router.navigate([this.successRoute], {
|
||||||
|
queryParams: {
|
||||||
|
identifier: this.identifier,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstracti
|
|||||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||||
import { DeviceCryptoServiceAbstraction } from "@bitwarden/common/abstractions/device-crypto.service.abstraction";
|
import { DeviceCryptoServiceAbstraction } from "@bitwarden/common/abstractions/device-crypto.service.abstraction";
|
||||||
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
import { DevicesApiServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices-api.service.abstraction";
|
||||||
|
import { DevicesServiceAbstraction } from "@bitwarden/common/abstractions/devices/devices.service.abstraction";
|
||||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||||
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
|
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
|
||||||
@ -97,6 +98,7 @@ import { ApiService } from "@bitwarden/common/services/api.service";
|
|||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
import { DeviceCryptoService } from "@bitwarden/common/services/device-crypto.service.implementation";
|
import { DeviceCryptoService } from "@bitwarden/common/services/device-crypto.service.implementation";
|
||||||
import { DevicesApiServiceImplementation } from "@bitwarden/common/services/devices/devices-api.service.implementation";
|
import { DevicesApiServiceImplementation } from "@bitwarden/common/services/devices/devices-api.service.implementation";
|
||||||
|
import { DevicesServiceImplementation } from "@bitwarden/common/services/devices/devices.service.implementation";
|
||||||
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
import { EventCollectionService } from "@bitwarden/common/services/event/event-collection.service";
|
||||||
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
|
||||||
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
||||||
@ -676,6 +678,11 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
|||||||
useClass: DevicesApiServiceImplementation,
|
useClass: DevicesApiServiceImplementation,
|
||||||
deps: [ApiServiceAbstraction],
|
deps: [ApiServiceAbstraction],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: DevicesServiceAbstraction,
|
||||||
|
useClass: DevicesServiceImplementation,
|
||||||
|
deps: [DevicesApiServiceAbstraction],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: DeviceCryptoServiceAbstraction,
|
provide: DeviceCryptoServiceAbstraction,
|
||||||
useClass: DeviceCryptoService,
|
useClass: DeviceCryptoService,
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { DeviceKey } from "../platform/models/domain/symmetric-crypto-key";
|
import { DeviceKey, UserKey } from "../platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { DeviceResponse } from "./devices/responses/device.response";
|
import { DeviceResponse } from "./devices/responses/device.response";
|
||||||
|
|
||||||
export abstract class DeviceCryptoServiceAbstraction {
|
export abstract class DeviceCryptoServiceAbstraction {
|
||||||
trustDevice: () => Promise<DeviceResponse>;
|
trustDevice: () => Promise<DeviceResponse>;
|
||||||
getDeviceKey: () => Promise<DeviceKey>;
|
getDeviceKey: () => Promise<DeviceKey>;
|
||||||
|
// TODO: update param types when available
|
||||||
|
decryptUserKey: (encryptedDevicePrivateKey: any, encryptedUserKey: any) => Promise<UserKey>;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { DeviceType } from "../../enums";
|
||||||
import { ListResponse } from "../../models/response/list.response";
|
import { ListResponse } from "../../models/response/list.response";
|
||||||
|
|
||||||
import { DeviceResponse } from "./responses/device.response";
|
import { DeviceResponse } from "./responses/device.response";
|
||||||
@ -8,6 +9,7 @@ export abstract class DevicesApiServiceAbstraction {
|
|||||||
getDeviceByIdentifier: (deviceIdentifier: string) => Promise<DeviceResponse>;
|
getDeviceByIdentifier: (deviceIdentifier: string) => Promise<DeviceResponse>;
|
||||||
|
|
||||||
getDevices: () => Promise<ListResponse<DeviceResponse>>;
|
getDevices: () => Promise<ListResponse<DeviceResponse>>;
|
||||||
|
getDevicesExistenceByTypes: (deviceTypes: DeviceType[]) => Promise<boolean>;
|
||||||
|
|
||||||
updateTrustedDeviceKeys: (
|
updateTrustedDeviceKeys: (
|
||||||
deviceIdentifier: string,
|
deviceIdentifier: string,
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
import { DeviceType } from "../../enums";
|
||||||
|
|
||||||
|
import { DeviceView } from "./views/device.view";
|
||||||
|
|
||||||
|
export abstract class DevicesServiceAbstraction {
|
||||||
|
getDevices$: () => Observable<Array<DeviceView>>;
|
||||||
|
getDevicesExistenceByTypes$: (deviceTypes: DeviceType[]) => Observable<boolean>;
|
||||||
|
getDeviceByIdentifier$: (deviceIdentifier: string) => Observable<DeviceView>;
|
||||||
|
isDeviceKnownForUser$: (email: string, deviceIdentifier: string) => Observable<boolean>;
|
||||||
|
updateTrustedDeviceKeys$: (
|
||||||
|
deviceIdentifier: string,
|
||||||
|
devicePublicKeyEncryptedUserKey: string,
|
||||||
|
userKeyEncryptedDevicePublicKey: string,
|
||||||
|
deviceKeyEncryptedDevicePrivateKey: string
|
||||||
|
) => Observable<DeviceView>;
|
||||||
|
}
|
@ -4,15 +4,11 @@ import { BaseResponse } from "../../../models/response/base.response";
|
|||||||
export class DeviceResponse extends BaseResponse {
|
export class DeviceResponse extends BaseResponse {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
name: number;
|
name: string;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
type: DeviceType;
|
type: DeviceType;
|
||||||
creationDate: string;
|
creationDate: string;
|
||||||
revisionDate: string;
|
revisionDate: string;
|
||||||
encryptedUserKey: string;
|
|
||||||
encryptedPublicKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
|
|
||||||
constructor(response: any) {
|
constructor(response: any) {
|
||||||
super(response);
|
super(response);
|
||||||
this.id = this.getResponseProperty("Id");
|
this.id = this.getResponseProperty("Id");
|
||||||
@ -22,8 +18,5 @@ export class DeviceResponse extends BaseResponse {
|
|||||||
this.type = this.getResponseProperty("Type");
|
this.type = this.getResponseProperty("Type");
|
||||||
this.creationDate = this.getResponseProperty("CreationDate");
|
this.creationDate = this.getResponseProperty("CreationDate");
|
||||||
this.revisionDate = this.getResponseProperty("RevisionDate");
|
this.revisionDate = this.getResponseProperty("RevisionDate");
|
||||||
this.encryptedUserKey = this.getResponseProperty("EncryptedUserKey");
|
|
||||||
this.encryptedPublicKey = this.getResponseProperty("EncryptedPublicKey");
|
|
||||||
this.encryptedPrivateKey = this.getResponseProperty("EncryptedPrivateKey");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
libs/common/src/abstractions/devices/views/device.view.ts
Normal file
17
libs/common/src/abstractions/devices/views/device.view.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { DeviceType } from "../../../enums";
|
||||||
|
import { View } from "../../../models/view/view";
|
||||||
|
import { DeviceResponse } from "../responses/device.response";
|
||||||
|
|
||||||
|
export class DeviceView implements View {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
name: string;
|
||||||
|
identifier: string;
|
||||||
|
type: DeviceType;
|
||||||
|
creationDate: string;
|
||||||
|
revisionDate: string;
|
||||||
|
|
||||||
|
constructor(deviceResponse: DeviceResponse) {
|
||||||
|
Object.assign(this, deviceResponse);
|
||||||
|
}
|
||||||
|
}
|
@ -83,6 +83,23 @@ export class SsoLogInStrategy extends LogInStrategy {
|
|||||||
const newSsoUser = tokenResponse.key == null;
|
const newSsoUser = tokenResponse.key == null;
|
||||||
|
|
||||||
if (!newSsoUser) {
|
if (!newSsoUser) {
|
||||||
|
// TODO: check if TDE feature flag enabled and if token response account decryption options has TDE
|
||||||
|
// and then if id token response has required device keys
|
||||||
|
// DevicePublicKey(UserKey)
|
||||||
|
// UserKey(DevicePublicKey)
|
||||||
|
// DeviceKey(DevicePrivateKey)
|
||||||
|
|
||||||
|
// Once we have device keys coming back on id token response we can use this code
|
||||||
|
// const userKey = await this.deviceCryptoService.decryptUserKey(
|
||||||
|
// encryptedDevicePrivateKey,
|
||||||
|
// encryptedUserKey
|
||||||
|
// );
|
||||||
|
// await this.cryptoService.setUserKey(userKey);
|
||||||
|
|
||||||
|
// TODO: also admin approval request existence check should go here b/c that can give us a decrypted user key to set
|
||||||
|
// TODO: future passkey login strategy will need to support setting user key (decrypting via TDE or admin approval request)
|
||||||
|
// so might be worth moving this logic to a common place (base login strategy or a separate service?)
|
||||||
|
|
||||||
await this.cryptoService.setUserKeyMasterKey(tokenResponse.key);
|
await this.cryptoService.setUserKeyMasterKey(tokenResponse.key);
|
||||||
|
|
||||||
if (tokenResponse.keyConnectorUrl != null) {
|
if (tokenResponse.keyConnectorUrl != null) {
|
||||||
|
@ -23,3 +23,16 @@ export enum DeviceType {
|
|||||||
SDK = 21,
|
SDK = 21,
|
||||||
Server = 22,
|
Server = 22,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MobileDeviceTypes: Set<DeviceType> = new Set([
|
||||||
|
DeviceType.Android,
|
||||||
|
DeviceType.iOS,
|
||||||
|
DeviceType.AndroidAmazon,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const DesktopDeviceTypes: Set<DeviceType> = new Set([
|
||||||
|
DeviceType.WindowsDesktop,
|
||||||
|
DeviceType.MacOsDesktop,
|
||||||
|
DeviceType.LinuxDesktop,
|
||||||
|
DeviceType.UWP,
|
||||||
|
]);
|
||||||
|
@ -263,8 +263,10 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>;
|
setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getDeviceKey: (options?: StorageOptions) => Promise<DeviceKey | null>;
|
getDeviceKey: (options?: StorageOptions) => Promise<DeviceKey | null>;
|
||||||
setDeviceKey: (value: DeviceKey, options?: StorageOptions) => Promise<void>;
|
setDeviceKey: (value: DeviceKey, options?: StorageOptions) => Promise<void>;
|
||||||
getAcctDecryptionOptions: (options?: StorageOptions) => Promise<AccountDecryptionOptions | null>;
|
getAccountDecryptionOptions: (
|
||||||
setAcctDecryptionOptions: (
|
options?: StorageOptions
|
||||||
|
) => Promise<AccountDecryptionOptions | null>;
|
||||||
|
setAccountDecryptionOptions: (
|
||||||
value: AccountDecryptionOptions,
|
value: AccountDecryptionOptions,
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
@ -294,6 +294,17 @@ export class AccountDecryptionOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: these nice getters don't work because the Account object is not properly being deserialized out of
|
||||||
|
// JSON (the Account static fromJSON method is not running) so these getters don't exist on the
|
||||||
|
// account decryptions options object when pulled out of state. This is a bug that needs to be fixed later on
|
||||||
|
// get hasTrustedDeviceOption(): boolean {
|
||||||
|
// return this.trustedDeviceOption !== null && this.trustedDeviceOption !== undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// get hasKeyConnectorOption(): boolean {
|
||||||
|
// return this.keyConnectorOption !== null && this.keyConnectorOption !== undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
static fromResponse(response: UserDecryptionOptionsResponse): AccountDecryptionOptions {
|
static fromResponse(response: UserDecryptionOptionsResponse): AccountDecryptionOptions {
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -322,7 +333,21 @@ export class AccountDecryptionOptions {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign(new AccountDecryptionOptions(), obj);
|
const accountDecryptionOptions = Object.assign(new AccountDecryptionOptions(), obj);
|
||||||
|
|
||||||
|
if (obj.trustedDeviceOption) {
|
||||||
|
accountDecryptionOptions.trustedDeviceOption = new TrustedDeviceUserDecryptionOption(
|
||||||
|
obj.trustedDeviceOption.hasAdminApproval
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.keyConnectorOption) {
|
||||||
|
accountDecryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(
|
||||||
|
obj.keyConnectorOption.keyConnectorUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountDecryptionOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1321,7 +1321,7 @@ export class StateService<
|
|||||||
await this.saveAccount(account, options);
|
await this.saveAccount(account, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAcctDecryptionOptions(
|
async getAccountDecryptionOptions(
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
): Promise<AccountDecryptionOptions | null> {
|
): Promise<AccountDecryptionOptions | null> {
|
||||||
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
options = this.reconcileOptions(options, await this.defaultOnDiskLocalOptions());
|
||||||
@ -1335,7 +1335,7 @@ export class StateService<
|
|||||||
return account?.decryptionOptions as AccountDecryptionOptions;
|
return account?.decryptionOptions as AccountDecryptionOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAcctDecryptionOptions(
|
async setAccountDecryptionOptions(
|
||||||
value: AccountDecryptionOptions,
|
value: AccountDecryptionOptions,
|
||||||
options?: StorageOptions
|
options?: StorageOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -86,4 +86,25 @@ export class DeviceCryptoService implements DeviceCryptoServiceAbstraction {
|
|||||||
|
|
||||||
return deviceKey;
|
return deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add proper types to parameters once we have them coming down from server
|
||||||
|
async decryptUserKey(encryptedDevicePrivateKey: any, encryptedUserKey: any): Promise<UserKey> {
|
||||||
|
// get device key
|
||||||
|
const existingDeviceKey = await this.stateService.getDeviceKey();
|
||||||
|
|
||||||
|
if (!existingDeviceKey) {
|
||||||
|
// TODO: not sure what to do here
|
||||||
|
// User doesn't have a device key anymore so device is untrusted
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// attempt to decrypt encryptedDevicePrivateKey with device key
|
||||||
|
const devicePrivateKey = await this.encryptService.decryptToBytes(
|
||||||
|
encryptedDevicePrivateKey,
|
||||||
|
existingDeviceKey
|
||||||
|
);
|
||||||
|
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
|
||||||
|
const userKey = await this.cryptoService.rsaDecrypt(encryptedUserKey, devicePrivateKey);
|
||||||
|
return new SymmetricCryptoKey(userKey) as UserKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { DevicesApiServiceAbstraction } from "../../abstractions/devices/devices-api.service.abstraction";
|
import { DevicesApiServiceAbstraction } from "../../abstractions/devices/devices-api.service.abstraction";
|
||||||
import { DeviceResponse } from "../../abstractions/devices/responses/device.response";
|
import { DeviceResponse } from "../../abstractions/devices/responses/device.response";
|
||||||
|
import { DeviceType } from "../../enums";
|
||||||
import { ListResponse } from "../../models/response/list.response";
|
import { ListResponse } from "../../models/response/list.response";
|
||||||
import { Utils } from "../../platform/misc/utils";
|
import { Utils } from "../../platform/misc/utils";
|
||||||
import { ApiService } from "../api.service";
|
import { ApiService } from "../api.service";
|
||||||
@ -45,6 +46,18 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac
|
|||||||
return new ListResponse(r, DeviceResponse);
|
return new ListResponse(r, DeviceResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDevicesExistenceByTypes(deviceTypes: DeviceType[]): Promise<boolean> {
|
||||||
|
const r = await this.apiService.send(
|
||||||
|
"POST",
|
||||||
|
"/devices/exist-by-types",
|
||||||
|
deviceTypes,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
return Boolean(r);
|
||||||
|
}
|
||||||
|
|
||||||
async updateTrustedDeviceKeys(
|
async updateTrustedDeviceKeys(
|
||||||
deviceIdentifier: string,
|
deviceIdentifier: string,
|
||||||
devicePublicKeyEncryptedUserKey: string,
|
devicePublicKeyEncryptedUserKey: string,
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Observable, defer, map } from "rxjs";
|
||||||
|
|
||||||
|
import { DevicesApiServiceAbstraction } from "../../abstractions/devices/devices-api.service.abstraction";
|
||||||
|
import { DevicesServiceAbstraction } from "../../abstractions/devices/devices.service.abstraction";
|
||||||
|
import { DeviceResponse } from "../../abstractions/devices/responses/device.response";
|
||||||
|
import { DeviceView } from "../../abstractions/devices/views/device.view";
|
||||||
|
import { DeviceType } from "../../enums";
|
||||||
|
import { ListResponse } from "../../models/response/list.response";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class DevicesServiceImplementation
|
||||||
|
* @implements {DevicesServiceAbstraction}
|
||||||
|
* @description Observable based data store service for Devices.
|
||||||
|
* note: defer is used to convert the promises to observables and to ensure
|
||||||
|
* that observables are created for each subscription
|
||||||
|
* (i.e., promsise --> observables are cold until subscribed to)
|
||||||
|
*/
|
||||||
|
export class DevicesServiceImplementation implements DevicesServiceAbstraction {
|
||||||
|
constructor(private devicesApiService: DevicesApiServiceAbstraction) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Gets the list of all devices.
|
||||||
|
*/
|
||||||
|
getDevices$(): Observable<Array<DeviceView>> {
|
||||||
|
return defer(() => this.devicesApiService.getDevices()).pipe(
|
||||||
|
map((deviceResponses: ListResponse<DeviceResponse>) => {
|
||||||
|
return deviceResponses.data.map((deviceResponse: DeviceResponse) => {
|
||||||
|
return new DeviceView(deviceResponse);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Returns whether the user has any devices of the specified types.
|
||||||
|
*/
|
||||||
|
getDevicesExistenceByTypes$(deviceTypes: DeviceType[]): Observable<boolean> {
|
||||||
|
return defer(() => this.devicesApiService.getDevicesExistenceByTypes(deviceTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Gets the device with the specified identifier.
|
||||||
|
*/
|
||||||
|
getDeviceByIdentifier$(deviceIdentifier: string): Observable<DeviceView> {
|
||||||
|
return defer(() => this.devicesApiService.getDeviceByIdentifier(deviceIdentifier)).pipe(
|
||||||
|
map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Checks if a device is known for a user by user's email and device's identifier.
|
||||||
|
*/
|
||||||
|
isDeviceKnownForUser$(email: string, deviceIdentifier: string): Observable<boolean> {
|
||||||
|
return defer(() => this.devicesApiService.getKnownDevice(email, deviceIdentifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Updates the keys for the specified device.
|
||||||
|
*/
|
||||||
|
|
||||||
|
updateTrustedDeviceKeys$(
|
||||||
|
deviceIdentifier: string,
|
||||||
|
devicePublicKeyEncryptedUserKey: string,
|
||||||
|
userKeyEncryptedDevicePublicKey: string,
|
||||||
|
deviceKeyEncryptedDevicePrivateKey: string
|
||||||
|
): Observable<DeviceView> {
|
||||||
|
return defer(() =>
|
||||||
|
this.devicesApiService.updateTrustedDeviceKeys(
|
||||||
|
deviceIdentifier,
|
||||||
|
devicePublicKeyEncryptedUserKey,
|
||||||
|
userKeyEncryptedDevicePublicKey,
|
||||||
|
deviceKeyEncryptedDevicePrivateKey
|
||||||
|
)
|
||||||
|
).pipe(map((deviceResponse: DeviceResponse) => new DeviceView(deviceResponse)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user