diff --git a/angular/src/components/add-edit-custom-fields.component.ts b/angular/src/components/add-edit-custom-fields.component.ts index e1e4d149..71040ac4 100644 --- a/angular/src/components/add-edit-custom-fields.component.ts +++ b/angular/src/components/add-edit-custom-fields.component.ts @@ -118,7 +118,7 @@ export class AddEditCustomFieldsComponent implements OnChanges { } this.cipher.fields - .filter(f => f.type = FieldType.Linked) + .filter(f => f.type === FieldType.Linked) .forEach(f => f.linkedId = this.linkedFieldOptions[0].value); } } diff --git a/angular/src/components/add-edit.component.ts b/angular/src/components/add-edit.component.ts index 0bd4001b..edb0b4ae 100644 --- a/angular/src/components/add-edit.component.ts +++ b/angular/src/components/add-edit.component.ts @@ -154,6 +154,9 @@ export class AddEditComponent implements OnInit { } async init() { + if (this.ownershipOptions.length) { + this.ownershipOptions = []; + } if (await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership)) { this.allowPersonal = false; } else { diff --git a/angular/src/components/export.component.ts b/angular/src/components/export.component.ts index 8b9372c3..716f9ef0 100644 --- a/angular/src/components/export.component.ts +++ b/angular/src/components/export.component.ts @@ -69,7 +69,10 @@ export class ExportComponent implements OnInit { } const secret = this.exportForm.get('secret').value; - if (!await this.userVerificationService.verifyUser(secret)) { + try { + await this.userVerificationService.verifyUser(secret); + } catch (e) { + this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), e.message); return; } diff --git a/angular/src/components/lock.component.ts b/angular/src/components/lock.component.ts index 34f34f11..b192ce6c 100644 --- a/angular/src/components/lock.component.ts +++ b/angular/src/components/lock.component.ts @@ -1,5 +1,6 @@ -import { Directive, OnInit } from '@angular/core'; +import { Directive, NgZone, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { take } from 'rxjs/operators'; import { ApiService } from 'jslib-common/abstractions/api.service'; import { CryptoService } from 'jslib-common/abstractions/crypto.service'; @@ -37,6 +38,7 @@ export class LockComponent implements OnInit { supportsBiometric: boolean; biometricLock: boolean; biometricText: string; + hideInput: boolean; protected successRoute: string = 'vault'; protected onSuccessfulSubmit: () => void; @@ -50,7 +52,7 @@ export class LockComponent implements OnInit { protected storageService: StorageService, protected vaultTimeoutService: VaultTimeoutService, protected environmentService: EnvironmentService, protected stateService: StateService, protected apiService: ApiService, private logService: LogService, - private keyConnectorService: KeyConnectorService) { } + private keyConnectorService: KeyConnectorService, protected ngZone: NgZone) { } async ngOnInit() { this.pinSet = await this.vaultTimeoutService.isPinLockSet(); @@ -60,9 +62,11 @@ export class LockComponent implements OnInit { (await this.cryptoService.hasKeyStored('biometric') || !this.platformUtilsService.supportsSecureStorage()); this.biometricText = await this.storageService.get(ConstantsService.biometricText); this.email = await this.userService.getEmail(); + const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + this.hideInput = usesKeyConnector && !this.pinLock; // Users with key connector and without biometric or pin has no MP to unlock using - if (await this.keyConnectorService.getUsesKeyConnector() && !(this.biometricLock || this.pinLock)) { + if (usesKeyConnector && !(this.biometricLock || this.pinLock)) { await this.vaultTimeoutService.logOut(); } @@ -182,7 +186,12 @@ export class LockComponent implements OnInit { togglePassword() { this.showPassword = !this.showPassword; - document.getElementById(this.pinLock ? 'pin' : 'masterPassword').focus(); + const input = document.getElementById(this.pinLock ? 'pin' : 'masterPassword'); + if (this.ngZone.isStable) { + input.focus(); + } else { + this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus()); + } } private async setKeyAndContinue(key: SymmetricCryptoKey) { diff --git a/angular/src/components/login.component.ts b/angular/src/components/login.component.ts index bfac5f53..34c0b8fd 100644 --- a/angular/src/components/login.component.ts +++ b/angular/src/components/login.component.ts @@ -1,11 +1,14 @@ import { Directive, Input, + NgZone, OnInit, } from '@angular/core'; import { Router } from '@angular/router'; +import { take } from 'rxjs/operators'; + import { AuthResult } from 'jslib-common/models/domain/authResult'; import { AuthService } from 'jslib-common/abstractions/auth.service'; @@ -51,7 +54,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit protected stateService: StateService, environmentService: EnvironmentService, protected passwordGenerationService: PasswordGenerationService, protected cryptoFunctionService: CryptoFunctionService, private storageService: StorageService, - protected logService: LogService) { + protected logService: LogService, protected ngZone: NgZone) { super(environmentService, i18nService, platformUtilsService); } @@ -132,7 +135,11 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit togglePassword() { this.showPassword = !this.showPassword; - document.getElementById('masterPassword').focus(); + if (this.ngZone.isStable) { + document.getElementById('masterPassword').focus(); + } else { + this.ngZone.onStable.pipe(take(1)).subscribe(() => document.getElementById('masterPassword').focus()); + } } async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { diff --git a/angular/src/components/set-pin.component.ts b/angular/src/components/set-pin.component.ts index 41a2383c..d2cfae3a 100644 --- a/angular/src/components/set-pin.component.ts +++ b/angular/src/components/set-pin.component.ts @@ -1,6 +1,10 @@ -import { Directive } from '@angular/core'; +import { + Directive, + OnInit +} from '@angular/core'; import { CryptoService } from 'jslib-common/abstractions/crypto.service'; +import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; import { StorageService } from 'jslib-common/abstractions/storage.service'; import { UserService } from 'jslib-common/abstractions/user.service'; import { VaultTimeoutService } from 'jslib-common/abstractions/vaultTimeout.service'; @@ -12,14 +16,20 @@ import { Utils } from 'jslib-common/misc/utils'; import { ModalRef } from './modal/modal.ref'; @Directive() -export class SetPinComponent { +export class SetPinComponent implements OnInit { pin = ''; showPin = false; masterPassOnRestart = true; + showMasterPassOnRestart = true; constructor(private modalRef: ModalRef, private cryptoService: CryptoService, private userService: UserService, - private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService) { } + private storageService: StorageService, private vaultTimeoutService: VaultTimeoutService, + private keyConnectorService: KeyConnectorService) { } + + async ngOnInit() { + this.showMasterPassOnRestart = this.masterPassOnRestart = !await this.keyConnectorService.getUsesKeyConnector(); + } toggleVisibility() { this.showPin = !this.showPin; diff --git a/angular/src/components/verify-master-password.component.html b/angular/src/components/verify-master-password.component.html index ff34926a..c4160e97 100644 --- a/angular/src/components/verify-master-password.component.html +++ b/angular/src/components/verify-master-password.component.html @@ -2,17 +2,24 @@ + {{'confirmIdentity' | i18n}}
- + + + {{'codeSent' | i18n}} +
+ {{'confirmIdentity' | i18n}}
diff --git a/angular/src/components/verify-master-password.component.ts b/angular/src/components/verify-master-password.component.ts index 01c511c9..1975b4e2 100644 --- a/angular/src/components/verify-master-password.component.ts +++ b/angular/src/components/verify-master-password.component.ts @@ -1,3 +1,9 @@ +import { + animate, + style, + transition, + trigger, +} from '@angular/animations'; import { Component, OnInit, @@ -8,8 +14,8 @@ import { NG_VALUE_ACCESSOR, } from '@angular/forms'; -import { ApiService } from 'jslib-common/abstractions/api.service'; import { KeyConnectorService } from 'jslib-common/abstractions/keyConnector.service'; +import { UserVerificationService } from 'jslib-common/abstractions/userVerification.service'; import { VerificationType } from 'jslib-common/enums/verificationType'; @@ -25,36 +31,43 @@ import { Verification } from 'jslib-common/types/verification'; useExisting: VerifyMasterPasswordComponent, }, ], + animations: [ + trigger('sent', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('100ms', style({ opacity: 1 })), + ]), + ]), + ], }) export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnInit { usesKeyConnector: boolean = false; disableRequestOTP: boolean = false; + sentCode: boolean = false; secret = new FormControl(''); private onChange: (value: Verification) => void; - constructor(private keyConnectorService: KeyConnectorService, private apiService: ApiService) { } + constructor(private keyConnectorService: KeyConnectorService, + private userVerificationService: UserVerificationService) { } async ngOnInit() { this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector(); + this.processChanges(this.secret.value); - this.secret.valueChanges.subscribe(secret => { - if (this.onChange == null) { - return; - } - - this.onChange({ - type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword, - secret: secret, - }); - }); + this.secret.valueChanges.subscribe(secret => this.processChanges(secret)); } async requestOTP() { if (this.usesKeyConnector) { this.disableRequestOTP = true; - await this.apiService.postAccountRequestOTP(); + try { + await this.userVerificationService.requestOTP(); + this.sentCode = true; + } finally { + this.disableRequestOTP = false; + } } } @@ -78,4 +91,15 @@ export class VerifyMasterPasswordComponent implements ControlValueAccessor, OnIn this.secret.enable(); } } + + private processChanges(secret: string) { + if (this.onChange == null) { + return; + } + + this.onChange({ + type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword, + secret: secret, + }); + } } diff --git a/angular/src/services/jslib-services.module.ts b/angular/src/services/jslib-services.module.ts new file mode 100644 index 00000000..d398755f --- /dev/null +++ b/angular/src/services/jslib-services.module.ts @@ -0,0 +1,395 @@ +import { + Injector, + LOCALE_ID, + NgModule, +} from '@angular/core'; +import { ToasterModule } from 'angular2-toaster'; + +import { ApiService } from 'jslib-common/services/api.service'; +import { AppIdService } from 'jslib-common/services/appId.service'; +import { AuditService } from 'jslib-common/services/audit.service'; +import { AuthService } from 'jslib-common/services/auth.service'; +import { CipherService } from 'jslib-common/services/cipher.service'; +import { CollectionService } from 'jslib-common/services/collection.service'; +import { ConsoleLogService } from 'jslib-common/services/consoleLog.service'; +import { CryptoService } from 'jslib-common/services/crypto.service'; +import { EnvironmentService } from 'jslib-common/services/environment.service'; +import { EventService } from 'jslib-common/services/event.service'; +import { ExportService } from 'jslib-common/services/export.service'; +import { FileUploadService } from 'jslib-common/services/fileUpload.service'; +import { FolderService } from 'jslib-common/services/folder.service'; +import { KeyConnectorService } from 'jslib-common/services/keyConnector.service'; +import { NotificationsService } from 'jslib-common/services/notifications.service'; +import { PasswordGenerationService } from 'jslib-common/services/passwordGeneration.service'; +import { PolicyService } from 'jslib-common/services/policy.service'; +import { SearchService } from 'jslib-common/services/search.service'; +import { SendService } from 'jslib-common/services/send.service'; +import { SettingsService } from 'jslib-common/services/settings.service'; +import { StateService } from 'jslib-common/services/state.service'; +import { SyncService } from 'jslib-common/services/sync.service'; +import { TokenService } from 'jslib-common/services/token.service'; +import { TotpService } from 'jslib-common/services/totp.service'; +import { UserService } from 'jslib-common/services/user.service'; +import { UserVerificationService } from 'jslib-common/services/userVerification.service'; +import { VaultTimeoutService } from 'jslib-common/services/vaultTimeout.service'; +import { WebCryptoFunctionService } from 'jslib-common/services/webCryptoFunction.service'; + +import { ApiService as ApiServiceAbstraction } from 'jslib-common/abstractions/api.service'; +import { AppIdService as AppIdServiceAbstraction } from 'jslib-common/abstractions/appId.service'; +import { AuditService as AuditServiceAbstraction } from 'jslib-common/abstractions/audit.service'; +import { AuthService as AuthServiceAbstraction } from 'jslib-common/abstractions/auth.service'; +import { BroadcasterService as BroadcasterServiceAbstraction } from 'jslib-common/abstractions/broadcaster.service'; +import { CipherService as CipherServiceAbstraction } from 'jslib-common/abstractions/cipher.service'; +import { CollectionService as CollectionServiceAbstraction } from 'jslib-common/abstractions/collection.service'; +import { CryptoService as CryptoServiceAbstraction } from 'jslib-common/abstractions/crypto.service'; +import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from 'jslib-common/abstractions/cryptoFunction.service'; +import { EnvironmentService as EnvironmentServiceAbstraction, Urls } from 'jslib-common/abstractions/environment.service'; +import { EventService as EventServiceAbstraction } from 'jslib-common/abstractions/event.service'; +import { ExportService as ExportServiceAbstraction } from 'jslib-common/abstractions/export.service'; +import { FileUploadService as FileUploadServiceAbstraction } from 'jslib-common/abstractions/fileUpload.service'; +import { FolderService as FolderServiceAbstraction } from 'jslib-common/abstractions/folder.service'; +import { I18nService as I18nServiceAbstraction } from 'jslib-common/abstractions/i18n.service'; +import { KeyConnectorService as KeyConnectorServiceAbstraction } from 'jslib-common/abstractions/keyConnector.service'; +import { LogService } from 'jslib-common/abstractions/log.service'; +import { MessagingService as MessagingServiceAbstraction } from 'jslib-common/abstractions/messaging.service'; +import { NotificationsService as NotificationsServiceAbstraction } from 'jslib-common/abstractions/notifications.service'; +import { + PasswordGenerationService as PasswordGenerationServiceAbstraction, +} from 'jslib-common/abstractions/passwordGeneration.service'; +import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from 'jslib-common/abstractions/passwordReprompt.service'; +import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from 'jslib-common/abstractions/platformUtils.service'; +import { PolicyService as PolicyServiceAbstraction } from 'jslib-common/abstractions/policy.service'; +import { SearchService as SearchServiceAbstraction } from 'jslib-common/abstractions/search.service'; +import { SendService as SendServiceAbstraction } from 'jslib-common/abstractions/send.service'; +import { SettingsService as SettingsServiceAbstraction } from 'jslib-common/abstractions/settings.service'; +import { StateService as StateServiceAbstraction } from 'jslib-common/abstractions/state.service'; +import { StorageService as StorageServiceAbstraction } from 'jslib-common/abstractions/storage.service'; +import { SyncService as SyncServiceAbstraction } from 'jslib-common/abstractions/sync.service'; +import { TokenService as TokenServiceAbstraction } from 'jslib-common/abstractions/token.service'; +import { TotpService as TotpServiceAbstraction } from 'jslib-common/abstractions/totp.service'; +import { UserService as UserServiceAbstraction } from 'jslib-common/abstractions/user.service'; +import { UserVerificationService as UserVerificationServiceAbstraction } from 'jslib-common/abstractions/userVerification.service'; +import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from 'jslib-common/abstractions/vaultTimeout.service'; + +import { AuthGuardService } from './auth-guard.service'; +import { BroadcasterService } from './broadcaster.service'; +import { LockGuardService } from './lock-guard.service'; +import { ModalService } from './modal.service'; +import { PasswordRepromptService } from './passwordReprompt.service'; +import { UnauthGuardService } from './unauth-guard.service'; +import { ValidationService } from './validation.service'; + +@NgModule({ + imports: [ + ToasterModule, + ], + declarations: [], + providers: [ + { provide: 'WINDOW', useValue: window }, + { + provide: LOCALE_ID, + useFactory: (i18nService: I18nServiceAbstraction) => i18nService.translationLocale, + deps: [I18nServiceAbstraction], + }, + ValidationService, + AuthGuardService, + UnauthGuardService, + LockGuardService, + ModalService, + { + provide: AppIdServiceAbstraction, + useClass: AppIdService, + deps: [StorageServiceAbstraction], + }, + { + provide: AuditServiceAbstraction, + useClass: AuditService, + deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction], + }, + { + provide: AuthServiceAbstraction, + useClass: AuthService, + deps: [ + CryptoServiceAbstraction, + ApiServiceAbstraction, + UserServiceAbstraction, + TokenServiceAbstraction, + AppIdServiceAbstraction, + I18nServiceAbstraction, + PlatformUtilsServiceAbstraction, + MessagingServiceAbstraction, + VaultTimeoutServiceAbstraction, + LogService, + CryptoFunctionServiceAbstraction, + EnvironmentServiceAbstraction, + KeyConnectorServiceAbstraction, + ], + }, + { + provide: CipherServiceAbstraction, + useFactory: (cryptoService: CryptoServiceAbstraction, userService: UserServiceAbstraction, + settingsService: SettingsServiceAbstraction, apiService: ApiServiceAbstraction, + fileUploadService: FileUploadServiceAbstraction, storageService: StorageServiceAbstraction, + i18nService: I18nServiceAbstraction, injector: Injector, logService: LogService) => + new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService, + storageService, i18nService, () => injector.get(SearchServiceAbstraction), logService), + deps: [ + CryptoServiceAbstraction, + UserServiceAbstraction, + SettingsServiceAbstraction, + ApiServiceAbstraction, + FileUploadServiceAbstraction, + StorageServiceAbstraction, + I18nServiceAbstraction, + Injector, // TODO: Get rid of this circular dependency! + LogService, + ], + }, + { + provide: FolderServiceAbstraction, + useClass: FolderService, + deps: [ + CryptoServiceAbstraction, + UserServiceAbstraction, + ApiServiceAbstraction, + StorageServiceAbstraction, + I18nServiceAbstraction, + CipherServiceAbstraction, + ], + }, + { provide: LogService, useFactory: () => new ConsoleLogService(false) }, + { + provide: CollectionServiceAbstraction, + useClass: CollectionService, + deps: [ + CryptoServiceAbstraction, + UserServiceAbstraction, + StorageServiceAbstraction, + I18nServiceAbstraction, + ], + }, + { + provide: EnvironmentServiceAbstraction, + useClass: EnvironmentService, + deps: [StorageServiceAbstraction], + }, + { + provide: TotpServiceAbstraction, + useClass: TotpService, + deps: [ + StorageServiceAbstraction, + CryptoFunctionServiceAbstraction, + LogService, + ], + }, + { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StorageServiceAbstraction] }, + { + provide: CryptoServiceAbstraction, + useClass: CryptoService, + deps: [ + StorageServiceAbstraction, + 'SECURE_STORAGE', + CryptoFunctionServiceAbstraction, + PlatformUtilsServiceAbstraction, + LogService, + ], + }, + { + provide: PasswordGenerationServiceAbstraction, + useClass: PasswordGenerationService, + deps: [ + CryptoServiceAbstraction, + StorageServiceAbstraction, + PolicyServiceAbstraction, + ], + }, + { + provide: ApiServiceAbstraction, + useFactory: (tokenService: TokenServiceAbstraction, platformUtilsService: PlatformUtilsServiceAbstraction, + environmentService: EnvironmentServiceAbstraction, messagingService: MessagingServiceAbstraction) => + new ApiService(tokenService, platformUtilsService, environmentService, + async (expired: boolean) => messagingService.send('logout', { expired: expired })), + deps: [ + TokenServiceAbstraction, + PlatformUtilsServiceAbstraction, + EnvironmentServiceAbstraction, + MessagingServiceAbstraction, + ], + }, + { + provide: FileUploadServiceAbstraction, + useClass: FileUploadService, + deps: [ + LogService, + ApiServiceAbstraction, + ], + }, + { + provide: SyncServiceAbstraction, + useFactory: (userService: UserServiceAbstraction, apiService: ApiServiceAbstraction, + settingsService: SettingsServiceAbstraction, folderService: FolderServiceAbstraction, + cipherService: CipherServiceAbstraction, cryptoService: CryptoServiceAbstraction, + collectionService: CollectionServiceAbstraction, storageService: StorageServiceAbstraction, + messagingService: MessagingServiceAbstraction, policyService: PolicyServiceAbstraction, + sendService: SendServiceAbstraction, logService: LogService, tokenService: TokenService, + keyConnectorService: KeyConnectorServiceAbstraction) => new SyncService(userService, apiService, + settingsService, folderService, cipherService, cryptoService, collectionService, storageService, + messagingService, policyService, sendService, logService, tokenService, keyConnectorService, + async (expired: boolean) => messagingService.send('logout', { expired: expired })), + deps: [ + UserServiceAbstraction, + ApiServiceAbstraction, + SettingsServiceAbstraction, + FolderServiceAbstraction, + CipherServiceAbstraction, + CryptoServiceAbstraction, + CollectionServiceAbstraction, + StorageServiceAbstraction, + MessagingServiceAbstraction, + PolicyServiceAbstraction, + SendServiceAbstraction, + LogService, + TokenServiceAbstraction, + KeyConnectorServiceAbstraction, + ], + }, + { + provide: UserServiceAbstraction, + useClass: UserService, + deps: [TokenServiceAbstraction, StorageServiceAbstraction], + }, + { provide: BroadcasterServiceAbstraction, useClass: BroadcasterService }, + { + provide: SettingsServiceAbstraction, + useClass: SettingsService, + deps: [UserServiceAbstraction, StorageServiceAbstraction], + }, + { + provide: VaultTimeoutServiceAbstraction, + useFactory: (cipherService: CipherServiceAbstraction, folderService: FolderServiceAbstraction, + collectionService: CollectionServiceAbstraction, cryptoService: CryptoServiceAbstraction, + platformUtilsService: PlatformUtilsServiceAbstraction, storageService: StorageServiceAbstraction, + messagingService: MessagingServiceAbstraction, searchService: SearchServiceAbstraction, + userService: UserServiceAbstraction, tokenService: TokenServiceAbstraction, + policyService: PolicyServiceAbstraction, keyConnectorService: KeyConnectorServiceAbstraction) => + new VaultTimeoutService(cipherService, folderService, collectionService, cryptoService, + platformUtilsService, storageService, messagingService, searchService, userService, tokenService, + policyService, keyConnectorService, null, + async () => messagingService.send('logout', { expired: false })), + deps: [ + CipherServiceAbstraction, + FolderServiceAbstraction, + CollectionServiceAbstraction, + CryptoServiceAbstraction, + PlatformUtilsServiceAbstraction, + StorageServiceAbstraction, + MessagingServiceAbstraction, + SearchServiceAbstraction, + UserServiceAbstraction, + TokenServiceAbstraction, + PolicyServiceAbstraction, + ], + }, + { provide: StateServiceAbstraction, useClass: StateService }, + { + provide: ExportServiceAbstraction, + useClass: ExportService, + deps: [ + FolderServiceAbstraction, + CipherServiceAbstraction, + ApiServiceAbstraction, + CryptoServiceAbstraction, + ], + }, + { + provide: SearchServiceAbstraction, + useClass: SearchService, + deps: [ + CipherServiceAbstraction, + LogService, + I18nServiceAbstraction, + ], + }, + { + provide: NotificationsServiceAbstraction, + useFactory: (userService: UserServiceAbstraction, syncService: SyncServiceAbstraction, + appIdService: AppIdServiceAbstraction, apiService: ApiServiceAbstraction, + vaultTimeoutService: VaultTimeoutServiceAbstraction, environmentService: EnvironmentServiceAbstraction, + messagingService: MessagingServiceAbstraction, logService: LogService) => + new NotificationsService(userService, syncService, appIdService, apiService, vaultTimeoutService, + environmentService, async () => messagingService.send('logout', { expired: true }), logService), + deps: [ + UserServiceAbstraction, + SyncServiceAbstraction, + AppIdServiceAbstraction, + ApiServiceAbstraction, + VaultTimeoutServiceAbstraction, + EnvironmentServiceAbstraction, + MessagingServiceAbstraction, + LogService, + ], + }, + { + provide: CryptoFunctionServiceAbstraction, + useClass: WebCryptoFunctionService, + deps: ['WINDOW', PlatformUtilsServiceAbstraction], + }, + { + provide: EventServiceAbstraction, + useClass: EventService, + deps: [ + StorageServiceAbstraction, + ApiServiceAbstraction, + UserServiceAbstraction, + CipherServiceAbstraction, + LogService, + ], + }, + { + provide: PolicyServiceAbstraction, + useClass: PolicyService, + deps: [ + UserServiceAbstraction, + StorageServiceAbstraction, + ApiServiceAbstraction, + ], + }, + { + provide: SendServiceAbstraction, + useClass: SendService, + deps: [ + CryptoServiceAbstraction, + UserServiceAbstraction, + ApiServiceAbstraction, + FileUploadServiceAbstraction, + StorageServiceAbstraction, + I18nServiceAbstraction, + CryptoFunctionServiceAbstraction, + ], + }, + { + provide: KeyConnectorServiceAbstraction, + useClass: KeyConnectorService, + deps: [ + StorageServiceAbstraction, + UserServiceAbstraction, + CryptoServiceAbstraction, + ApiServiceAbstraction, + TokenServiceAbstraction, + LogService, + ], + }, + { + provide: UserVerificationServiceAbstraction, + useClass: UserVerificationService, + deps: [ + CryptoServiceAbstraction, + I18nServiceAbstraction, + ApiServiceAbstraction, + ], + }, + { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, + ], +}) +export class JslibServicesModule { +} diff --git a/common/src/abstractions/api.service.ts b/common/src/abstractions/api.service.ts index e146a4f5..1c6aa0ef 100644 --- a/common/src/abstractions/api.service.ts +++ b/common/src/abstractions/api.service.ts @@ -32,6 +32,8 @@ import { ImportOrganizationCiphersRequest } from '../models/request/importOrgani import { KdfRequest } from '../models/request/kdfRequest'; import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKeyRequest'; import { KeysRequest } from '../models/request/keysRequest'; +import { OrganizationSponsorshipCreateRequest } from '../models/request/organization/organizationSponsorshipCreateRequest'; +import { OrganizationSponsorshipRedeemRequest } from '../models/request/organization/organizationSponsorshipRedeemRequest'; import { OrganizationSsoRequest } from '../models/request/organization/organizationSsoRequest'; import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; @@ -453,6 +455,14 @@ export abstract class ApiService { preValidateSso: (identifier: string) => Promise; + postCreateSponsorship: (sponsorshipOrgId: string, request: OrganizationSponsorshipCreateRequest) => Promise; + deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; + deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; + postPreValidateSponsorshipToken: (sponsorshipToken: string) => Promise; + postRedeemSponsorship: (sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest) => Promise; + postResendSponsorshipOffer: (sponsoringOrgId: string) => Promise; + getUserKeyFromKeyConnector: (keyConnectorUrl: string) => Promise; postUserKeyToKeyConnector: (keyConnectorUrl: string, request: KeyConnectorUserKeyRequest) => Promise; + getKeyConnectorAlive: (keyConnectorUrl: string) => Promise; } diff --git a/common/src/abstractions/user.service.ts b/common/src/abstractions/user.service.ts index 81c3da87..8170ae73 100644 --- a/common/src/abstractions/user.service.ts +++ b/common/src/abstractions/user.service.ts @@ -21,6 +21,7 @@ export abstract class UserService { clear: () => Promise; isAuthenticated: () => Promise; canAccessPremium: () => Promise; + canManageSponsorships: () => Promise; getOrganization: (id: string) => Promise; getOrganizationByIdentifier: (identifier: string) => Promise; getAllOrganizations: () => Promise; diff --git a/common/src/abstractions/userVerification.service.ts b/common/src/abstractions/userVerification.service.ts index 05c7a090..7dcf1d10 100644 --- a/common/src/abstractions/userVerification.service.ts +++ b/common/src/abstractions/userVerification.service.ts @@ -6,4 +6,5 @@ export abstract class UserVerificationService { buildRequest: (verification: Verification, requestClass?: new () => T, alreadyHashed?: boolean) => Promise; verifyUser: (verification: Verification) => Promise; + requestOTP: () => Promise; } diff --git a/common/src/enums/eventType.ts b/common/src/enums/eventType.ts index 68fbc6b1..b45f45f9 100644 --- a/common/src/enums/eventType.ts +++ b/common/src/enums/eventType.ts @@ -47,6 +47,7 @@ export enum EventType { OrganizationUser_ResetPassword_Withdraw = 1507, OrganizationUser_AdminResetPassword = 1508, OrganizationUser_ResetSsoLink = 1509, + OrganizationUser_FirstSsoLogin = 1510, Organization_Updated = 1600, Organization_PurgedVault = 1601, diff --git a/common/src/enums/planSponsorshipType.ts b/common/src/enums/planSponsorshipType.ts new file mode 100644 index 00000000..330b7ec0 --- /dev/null +++ b/common/src/enums/planSponsorshipType.ts @@ -0,0 +1,3 @@ +export enum PlanSponsorshipType { + FamiliesForEnterprise = 0, +} diff --git a/common/src/models/api/ssoConfigApi.ts b/common/src/models/api/ssoConfigApi.ts index 9c260116..c846524a 100644 --- a/common/src/models/api/ssoConfigApi.ts +++ b/common/src/models/api/ssoConfigApi.ts @@ -37,7 +37,7 @@ enum Saml2SigningBehavior { export class SsoConfigApi extends BaseResponse { configType: SsoType; - useKeyConnector: boolean; + keyConnectorEnabled: boolean; keyConnectorUrl: string; // OpenId @@ -81,7 +81,7 @@ export class SsoConfigApi extends BaseResponse { this.configType = this.getResponseProperty('ConfigType'); - this.useKeyConnector = this.getResponseProperty('UseKeyConnector'); + this.keyConnectorEnabled = this.getResponseProperty('KeyConnectorEnabled'); this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); this.authority = this.getResponseProperty('Authority'); diff --git a/common/src/models/data/organizationData.ts b/common/src/models/data/organizationData.ts index ed44822e..c4061057 100644 --- a/common/src/models/data/organizationData.ts +++ b/common/src/models/data/organizationData.ts @@ -2,6 +2,8 @@ import { ProfileOrganizationResponse } from '../response/profileOrganizationResp import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; import { OrganizationUserType } from '../../enums/organizationUserType'; +import { ProductType } from '../../enums/productType'; + import { PermissionsApi } from '../api/permissionsApi'; export class OrganizationData { @@ -18,6 +20,7 @@ export class OrganizationData { use2fa: boolean; useApi: boolean; useSso: boolean; + useKeyConnector: boolean; useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; @@ -33,7 +36,10 @@ export class OrganizationData { providerId: string; providerName: string; isProviderUser: boolean; - usesKeyConnector: boolean; + familySponsorshipFriendlyName: string; + familySponsorshipAvailable: boolean; + planProductType: ProductType; + keyConnectorEnabled: boolean; keyConnectorUrl: string; constructor(response: ProfileOrganizationResponse) { @@ -50,6 +56,7 @@ export class OrganizationData { this.use2fa = response.use2fa; this.useApi = response.useApi; this.useSso = response.useSso; + this.useKeyConnector = response.useKeyConnector; this.useResetPassword = response.useResetPassword; this.selfHost = response.selfHost; this.usersGetPremium = response.usersGetPremium; @@ -64,7 +71,10 @@ export class OrganizationData { this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys; this.providerId = response.providerId; this.providerName = response.providerName; - this.usesKeyConnector = response.usesKeyConnector; + this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName; + this.familySponsorshipAvailable = response.familySponsorshipAvailable; + this.planProductType = response.planProductType; + this.keyConnectorEnabled = response.keyConnectorEnabled; this.keyConnectorUrl = response.keyConnectorUrl; } } diff --git a/common/src/models/domain/organization.ts b/common/src/models/domain/organization.ts index 1ad1d6f7..492afda5 100644 --- a/common/src/models/domain/organization.ts +++ b/common/src/models/domain/organization.ts @@ -2,6 +2,7 @@ import { OrganizationData } from '../data/organizationData'; import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; import { OrganizationUserType } from '../../enums/organizationUserType'; +import { ProductType } from '../../enums/productType'; import { PermissionsApi } from '../api/permissionsApi'; @@ -19,6 +20,7 @@ export class Organization { use2fa: boolean; useApi: boolean; useSso: boolean; + useKeyConnector: boolean; useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; @@ -34,7 +36,10 @@ export class Organization { providerId: string; providerName: string; isProviderUser: boolean; - usesKeyConnector: boolean; + familySponsorshipFriendlyName: string; + familySponsorshipAvailable: boolean; + planProductType: ProductType; + keyConnectorEnabled: boolean; keyConnectorUrl: string; constructor(obj?: OrganizationData) { @@ -55,6 +60,7 @@ export class Organization { this.use2fa = obj.use2fa; this.useApi = obj.useApi; this.useSso = obj.useSso; + this.useKeyConnector = obj.useKeyConnector; this.useResetPassword = obj.useResetPassword; this.selfHost = obj.selfHost; this.usersGetPremium = obj.usersGetPremium; @@ -70,7 +76,10 @@ export class Organization { this.providerId = obj.providerId; this.providerName = obj.providerName; this.isProviderUser = obj.isProviderUser; - this.usesKeyConnector = obj.usesKeyConnector; + this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName; + this.familySponsorshipAvailable = obj.familySponsorshipAvailable; + this.planProductType = obj.planProductType; + this.keyConnectorEnabled = obj.keyConnectorEnabled; this.keyConnectorUrl = obj.keyConnectorUrl; } diff --git a/common/src/models/export/field.ts b/common/src/models/export/field.ts index 2e98c1bd..0804c123 100644 --- a/common/src/models/export/field.ts +++ b/common/src/models/export/field.ts @@ -1,4 +1,5 @@ import { FieldType } from '../../enums/fieldType'; +import { LinkedIdType } from '../../enums/linkedIdType'; import { FieldView } from '../view/fieldView'; @@ -18,6 +19,7 @@ export class Field { view.type = req.type; view.value = req.value; view.name = req.name; + view.linkedId = req.linkedId; return view; } @@ -25,12 +27,14 @@ export class Field { domain.type = req.type; domain.value = req.value != null ? new EncString(req.value) : null; domain.name = req.name != null ? new EncString(req.name) : null; + domain.linkedId = req.linkedId; return domain; } name: string; value: string; type: FieldType; + linkedId: LinkedIdType; constructor(o?: FieldView | FieldDomain) { if (o == null) { @@ -45,5 +49,6 @@ export class Field { this.value = o.value?.encryptedString; } this.type = o.type; + this.linkedId = o.linkedId; } } diff --git a/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts b/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts new file mode 100644 index 00000000..6dd8c267 --- /dev/null +++ b/common/src/models/request/organization/organizationSponsorshipCreateRequest.ts @@ -0,0 +1,7 @@ +import { PlanSponsorshipType } from '../../../enums/planSponsorshipType'; + +export class OrganizationSponsorshipCreateRequest { + sponsoredEmail: string; + planSponsorshipType: PlanSponsorshipType; + friendlyName: string; +} diff --git a/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts b/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts new file mode 100644 index 00000000..7f325ea1 --- /dev/null +++ b/common/src/models/request/organization/organizationSponsorshipRedeemRequest.ts @@ -0,0 +1,6 @@ +import { PlanSponsorshipType } from '../../../enums/planSponsorshipType'; + +export class OrganizationSponsorshipRedeemRequest { + planSponsorshipType: PlanSponsorshipType; + sponsoredOrganizationId: string; +} diff --git a/common/src/models/response/identityTokenResponse.ts b/common/src/models/response/identityTokenResponse.ts index 7b128f44..c2283956 100644 --- a/common/src/models/response/identityTokenResponse.ts +++ b/common/src/models/response/identityTokenResponse.ts @@ -15,6 +15,7 @@ export class IdentityTokenResponse extends BaseResponse { kdf: KdfType; kdfIterations: number; forcePasswordReset: boolean; + apiUseKeyConnector: boolean; keyConnectorUrl: string; constructor(response: any) { @@ -31,6 +32,7 @@ export class IdentityTokenResponse extends BaseResponse { this.kdf = this.getResponseProperty('Kdf'); this.kdfIterations = this.getResponseProperty('KdfIterations'); this.forcePasswordReset = this.getResponseProperty('ForcePasswordReset'); + this.apiUseKeyConnector = this.getResponseProperty('ApiUseKeyConnector'); this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); } } diff --git a/common/src/models/response/organizationUserResponse.ts b/common/src/models/response/organizationUserResponse.ts index 558f9db0..3b155f01 100644 --- a/common/src/models/response/organizationUserResponse.ts +++ b/common/src/models/response/organizationUserResponse.ts @@ -32,12 +32,14 @@ export class OrganizationUserUserDetailsResponse extends OrganizationUserRespons name: string; email: string; twoFactorEnabled: boolean; + usesKeyConnector: boolean; constructor(response: any) { super(response); this.name = this.getResponseProperty('Name'); this.email = this.getResponseProperty('Email'); this.twoFactorEnabled = this.getResponseProperty('TwoFactorEnabled'); + this.usesKeyConnector = this.getResponseProperty('UsesKeyConnector') ?? false; } } diff --git a/common/src/models/response/profileOrganizationResponse.ts b/common/src/models/response/profileOrganizationResponse.ts index d4c77792..fdd873ba 100644 --- a/common/src/models/response/profileOrganizationResponse.ts +++ b/common/src/models/response/profileOrganizationResponse.ts @@ -2,6 +2,7 @@ import { BaseResponse } from './baseResponse'; import { OrganizationUserStatusType } from '../../enums/organizationUserStatusType'; import { OrganizationUserType } from '../../enums/organizationUserType'; +import { ProductType } from '../../enums/productType'; import { PermissionsApi } from '../api/permissionsApi'; export class ProfileOrganizationResponse extends BaseResponse { @@ -15,6 +16,7 @@ export class ProfileOrganizationResponse extends BaseResponse { use2fa: boolean; useApi: boolean; useSso: boolean; + useKeyConnector: boolean; useResetPassword: boolean; selfHost: boolean; usersGetPremium: boolean; @@ -33,7 +35,10 @@ export class ProfileOrganizationResponse extends BaseResponse { userId: string; providerId: string; providerName: string; - usesKeyConnector: boolean; + familySponsorshipFriendlyName: string; + familySponsorshipAvailable: boolean; + planProductType: ProductType; + keyConnectorEnabled: boolean; keyConnectorUrl: string; constructor(response: any) { @@ -48,6 +53,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.use2fa = this.getResponseProperty('Use2fa'); this.useApi = this.getResponseProperty('UseApi'); this.useSso = this.getResponseProperty('UseSso'); + this.useKeyConnector = this.getResponseProperty('UseKeyConnector') ?? false; this.useResetPassword = this.getResponseProperty('UseResetPassword'); this.selfHost = this.getResponseProperty('SelfHost'); this.usersGetPremium = this.getResponseProperty('UsersGetPremium'); @@ -66,7 +72,10 @@ export class ProfileOrganizationResponse extends BaseResponse { this.userId = this.getResponseProperty('UserId'); this.providerId = this.getResponseProperty('ProviderId'); this.providerName = this.getResponseProperty('ProviderName'); - this.usesKeyConnector = this.getResponseProperty('UsesKeyConnector') ?? false; + this.familySponsorshipFriendlyName = this.getResponseProperty('FamilySponsorshipFriendlyName'); + this.familySponsorshipAvailable = this.getResponseProperty('FamilySponsorshipAvailable'); + this.planProductType = this.getResponseProperty('PlanProductType'); + this.keyConnectorEnabled = this.getResponseProperty('KeyConnectorEnabled') ?? false; this.keyConnectorUrl = this.getResponseProperty('KeyConnectorUrl'); } } diff --git a/common/src/models/response/profileProviderOrganizationResponse.ts b/common/src/models/response/profileProviderOrganizationResponse.ts index dcf24561..d34b78d0 100644 --- a/common/src/models/response/profileProviderOrganizationResponse.ts +++ b/common/src/models/response/profileProviderOrganizationResponse.ts @@ -3,6 +3,6 @@ import { ProfileOrganizationResponse } from './profileOrganizationResponse'; export class ProfileProviderOrganizationResponse extends ProfileOrganizationResponse { constructor(response: any) { super(response); - this.usesKeyConnector = false; + this.keyConnectorEnabled = false; } } diff --git a/common/src/models/response/subscriptionResponse.ts b/common/src/models/response/subscriptionResponse.ts index df2581ce..616d5a8b 100644 --- a/common/src/models/response/subscriptionResponse.ts +++ b/common/src/models/response/subscriptionResponse.ts @@ -59,6 +59,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse { amount: number; quantity: number; interval: string; + sponsoredSubscriptionItem: boolean; constructor(response: any) { super(response); @@ -66,6 +67,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse { this.amount = this.getResponseProperty('Amount'); this.quantity = this.getResponseProperty('Quantity'); this.interval = this.getResponseProperty('Interval'); + this.sponsoredSubscriptionItem = this.getResponseProperty('SponsoredSubscriptionItem'); } } diff --git a/common/src/services/api.service.ts b/common/src/services/api.service.ts index a2c8ee4a..46fdc139 100644 --- a/common/src/services/api.service.ts +++ b/common/src/services/api.service.ts @@ -33,6 +33,8 @@ import { ImportDirectoryRequest } from '../models/request/importDirectoryRequest import { ImportOrganizationCiphersRequest } from '../models/request/importOrganizationCiphersRequest'; import { KdfRequest } from '../models/request/kdfRequest'; import { KeysRequest } from '../models/request/keysRequest'; +import { OrganizationSponsorshipCreateRequest } from '../models/request/organization/organizationSponsorshipCreateRequest'; +import { OrganizationSponsorshipRedeemRequest } from '../models/request/organization/organizationSponsorshipRedeemRequest'; import { OrganizationSsoRequest } from '../models/request/organization/organizationSsoRequest'; import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; @@ -172,6 +174,8 @@ import { KeyConnectorUserKeyRequest } from '../models/request/keyConnectorUserKe import { KeyConnectorUserKeyResponse } from '../models/response/keyConnectorUserKeyResponse'; import { SendAccessView } from '../models/view/sendAccessView'; + + export class ApiService implements ApiServiceAbstraction { protected apiKeyRefresh: (clientId: string, clientSecret: string) => Promise; private device: DeviceType; @@ -1491,6 +1495,21 @@ export class ApiService implements ApiServiceAbstraction { } } + async getKeyConnectorAlive(keyConnectorUrl: string) { + const response = await this.fetch(new Request(keyConnectorUrl + '/alive', { + cache: 'no-store', + method: 'GET', + headers: new Headers({ + 'Accept': 'application/json', + 'Content-Type': 'application/json; charset=utf-8', + }), + })); + + if (response.status !== 200) { + const error = await this.handleError(response, false, true); + return Promise.reject(error); + } + } // Helpers @@ -1543,6 +1562,42 @@ export class ApiService implements ApiServiceAbstraction { } } + async postCreateSponsorship(sponsoredOrgId: string, request: OrganizationSponsorshipCreateRequest): Promise { + return await this.send('POST', + '/organization/sponsorship/' + sponsoredOrgId + '/families-for-enterprise', + request, true, false); + } + + async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { + return await this.send('DELETE', + '/organization/sponsorship/' + sponsoringOrganizationId, + null, true, false); + } + + async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { + return await this.send('DELETE', + '/organization/sponsorship/sponsored/' + sponsoringOrgId, + null, true, false); + } + + async postPreValidateSponsorshipToken(sponsorshipToken: string): Promise { + const r = await this.send('POST', '/organization/sponsorship/validate-token?sponsorshipToken=' + encodeURIComponent(sponsorshipToken), + null, true, true); + return r as boolean; + } + + async postRedeemSponsorship(sponsorshipToken: string, request: OrganizationSponsorshipRedeemRequest): Promise { + return await this.send('POST', '/organization/sponsorship/redeem?sponsorshipToken=' + encodeURIComponent(sponsorshipToken), + request, true, false); + } + + async postResendSponsorshipOffer(sponsoringOrgId: string): Promise { + return await this.send('POST', + '/organization/sponsorship/' + sponsoringOrgId + '/families-for-enterprise/resend', + null, true, false); + } + + protected async doAuthRefresh(): Promise { const refreshToken = await this.tokenService.getRefreshToken(); if (refreshToken != null && refreshToken !== '') { diff --git a/common/src/services/auth.service.ts b/common/src/services/auth.service.ts index 5c5ca225..e4f670d7 100644 --- a/common/src/services/auth.service.ts +++ b/common/src/services/auth.service.ts @@ -370,8 +370,9 @@ export class AuthService implements AuthServiceAbstraction { if (tokenResponse.keyConnectorUrl != null) { await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl); - } else if (this.environmentService.getKeyConnectorUrl() != null) { - await this.keyConnectorService.getAndSetKey(); + } else if (tokenResponse.apiUseKeyConnector) { + const keyConnectorUrl = this.environmentService.getKeyConnectorUrl(); + await this.keyConnectorService.getAndSetKey(keyConnectorUrl); } await this.cryptoService.setEncKey(tokenResponse.key); diff --git a/common/src/services/import.service.ts b/common/src/services/import.service.ts index 3e797031..d04ede50 100644 --- a/common/src/services/import.service.ts +++ b/common/src/services/import.service.ts @@ -85,13 +85,13 @@ export class ImportService implements ImportServiceAbstraction { featuredImportOptions = [ { id: 'bitwardenjson', name: 'Bitwarden (json)' }, { id: 'bitwardencsv', name: 'Bitwarden (csv)' }, - { id: 'lastpasscsv', name: 'LastPass (csv)' }, { id: 'chromecsv', name: 'Chrome (csv)' }, - { id: 'firefoxcsv', name: 'Firefox (csv)' }, - { id: 'safaricsv', name: 'Safari (csv)' }, - { id: 'keepass2xml', name: 'KeePass 2 (xml)' }, - { id: '1password1pif', name: '1Password (1pif)' }, { id: 'dashlanejson', name: 'Dashlane (json)' }, + { id: 'firefoxcsv', name: 'Firefox (csv)' }, + { id: 'keepass2xml', name: 'KeePass 2 (xml)' }, + { id: 'lastpasscsv', name: 'LastPass (csv)' }, + { id: 'safaricsv', name: 'Safari and macOS (csv)' }, + { id: '1password1pif', name: '1Password (1pif)' }, ]; regularImportOptions: ImportOption[] = [ diff --git a/common/src/services/keyConnector.service.ts b/common/src/services/keyConnector.service.ts index 68aac9a2..d597469f 100644 --- a/common/src/services/keyConnector.service.ts +++ b/common/src/services/keyConnector.service.ts @@ -1,6 +1,5 @@ import { ApiService } from '../abstractions/api.service'; import { CryptoService } from '../abstractions/crypto.service'; -import { EnvironmentService } from '../abstractions/environment.service'; import { KeyConnectorService as KeyConnectorServiceAbstraction } from '../abstractions/keyConnector.service'; import { LogService } from '../abstractions/log.service'; import { StorageService } from '../abstractions/storage.service'; @@ -25,8 +24,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { constructor(private storageService: StorageService, private userService: UserService, private cryptoService: CryptoService, private apiService: ApiService, - private environmentService: EnvironmentService, private tokenService: TokenService, - private logService: LogService) { } + private tokenService: TokenService, private logService: LogService) { } setUsesKeyConnector(usesKeyConnector: boolean) { this.usesKeyConnector = usesKeyConnector; @@ -59,15 +57,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { await this.apiService.postConvertToKeyConnector(); } - async getAndSetKey(url?: string) { - if (url == null) { - url = this.environmentService.getKeyConnectorUrl(); - } - - if (url == null) { - throw new Error('No Key Connector URL found.'); - } - + async getAndSetKey(url: string) { try { const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url); const keyArr = Utils.fromB64ToArray(userKeyResponse.key); @@ -82,7 +72,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { async getManagingOrganization() { const orgs = await this.userService.getAllOrganizations(); return orgs.find(o => - o.usesKeyConnector && + o.keyConnectorEnabled && o.type !== OrganizationUserType.Admin && o.type !== OrganizationUserType.Owner && !o.isProviderUser); diff --git a/common/src/services/sync.service.ts b/common/src/services/sync.service.ts index 4b7ee9c4..8395ddcc 100644 --- a/common/src/services/sync.service.ts +++ b/common/src/services/sync.service.ts @@ -321,16 +321,16 @@ export class SyncService implements SyncServiceAbstraction { } }); + await Promise.all([ + this.userService.replaceOrganizations(organizations), + this.userService.replaceProviders(providers), + ]); + if (await this.keyConnectorService.userNeedsMigration()) { this.messagingService.send('convertAccountToKeyConnector'); } else { this.keyConnectorService.removeConvertAccountRequired(); } - - return Promise.all([ - this.userService.replaceOrganizations(organizations), - this.userService.replaceProviders(providers), - ]); } private async syncFolders(userId: string, response: FolderResponse[]) { diff --git a/common/src/services/user.service.ts b/common/src/services/user.service.ts index 658179f9..a572691a 100644 --- a/common/src/services/user.service.ts +++ b/common/src/services/user.service.ts @@ -156,6 +156,11 @@ export class UserService implements UserServiceAbstraction { return false; } + async canManageSponsorships(): Promise { + const orgs = await this.getAllOrganizations(); + return orgs.some(o => o.familySponsorshipAvailable || o.familySponsorshipFriendlyName !== null); + } + async getOrganization(id: string): Promise { const userId = await this.getUserId(); const organizations = await this.storageService.get<{ [id: string]: OrganizationData; }>( diff --git a/common/src/services/userVerification.service.ts b/common/src/services/userVerification.service.ts index 7910f44a..2b94ffae 100644 --- a/common/src/services/userVerification.service.ts +++ b/common/src/services/userVerification.service.ts @@ -1,12 +1,8 @@ -import { Injectable } from '@angular/core'; - import { UserVerificationService as UserVerificationServiceAbstraction } from '../abstractions/userVerification.service'; import { ApiService } from '../abstractions/api.service'; import { CryptoService } from '../abstractions/crypto.service'; import { I18nService } from '../abstractions/i18n.service'; -import { LogService } from '../abstractions/log.service'; -import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { VerificationType } from '../enums/verificationType'; @@ -15,17 +11,13 @@ import { SecretVerificationRequest } from '../models/request/secretVerificationR import { Verification } from '../types/verification'; -@Injectable() export class UserVerificationService implements UserVerificationServiceAbstraction { constructor(private cryptoService: CryptoService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private apiService: ApiService, - private logService: LogService) { } + private apiService: ApiService) { } async buildRequest(verification: Verification, requestClass?: new () => T, alreadyHashed?: boolean) { - if (verification?.secret == null || verification.secret === '') { - throw new Error('No secret provided for verification.'); - } + this.validateInput(verification); const request = requestClass != null ? new requestClass() @@ -43,28 +35,35 @@ export class UserVerificationService implements UserVerificationServiceAbstracti } async verifyUser(verification: Verification): Promise { - if (verification?.secret == null || verification.secret === '') { - throw new Error('No secret provided for verification.'); - } + this.validateInput(verification); if (verification.type === VerificationType.OTP) { const request = new VerifyOTPRequest(verification.secret); try { await this.apiService.postAccountVerifyOTP(request); } catch (e) { - this.logService.error(e); - this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('invalidVerificationCode')); - return false; + throw new Error(this.i18nService.t('invalidVerificationCode')); } } else { const passwordValid = await this.cryptoService.compareAndUpdateKeyHash(verification.secret, null); if (!passwordValid) { - this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), - this.i18nService.t('invalidMasterPassword')); - return false; + throw new Error(this.i18nService.t('invalidMasterPassword')); } } return true; } + + async requestOTP() { + await this.apiService.postAccountRequestOTP(); + } + + private validateInput(verification: Verification) { + if (verification?.secret == null || verification.secret === '') { + if (verification.type === VerificationType.OTP) { + throw new Error(this.i18nService.t('verificationCodeRequired')); + } else { + throw new Error(this.i18nService.t('masterPassRequired')); + } + } + } } diff --git a/node/src/cli/services/cliPlatformUtils.service.ts b/node/src/cli/services/cliPlatformUtils.service.ts index dc2d7e1b..78afaee9 100644 --- a/node/src/cli/services/cliPlatformUtils.service.ts +++ b/node/src/cli/services/cliPlatformUtils.service.ts @@ -93,6 +93,10 @@ export class CliPlatformUtilsService implements PlatformUtilsService { return Promise.resolve(this.packageJson.version); } + getApplicationVersionSync(): string { + return this.packageJson.version; + } + supportsWebAuthn(win: Window) { return false; }