diff --git a/jslib/angular/src/services/auth-guard.service.ts b/jslib/angular/src/services/auth-guard.service.ts deleted file mode 100644 index 12ccc5934..000000000 --- a/jslib/angular/src/services/auth-guard.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from "@angular/core"; -import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from "@angular/router"; - -import { KeyConnectorService } from "@/jslib/common/src/abstractions/keyConnector.service"; -import { MessagingService } from "@/jslib/common/src/abstractions/messaging.service"; -import { StateService } from "@/jslib/common/src/abstractions/state.service"; -import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service"; - -@Injectable() -export class AuthGuardService { - constructor( - private vaultTimeoutService: VaultTimeoutService, - private router: Router, - private messagingService: MessagingService, - private keyConnectorService: KeyConnectorService, - private stateService: StateService, - ) {} - - async canActivate(route: ActivatedRouteSnapshot, routerState: RouterStateSnapshot) { - const isAuthed = await this.stateService.getIsAuthenticated(); - if (!isAuthed) { - this.messagingService.send("authBlocked"); - return false; - } - - const locked = await this.vaultTimeoutService.isLocked(); - if (locked) { - if (routerState != null) { - this.messagingService.send("lockedUrl", { url: routerState.url }); - } - this.router.navigate(["lock"], { queryParams: { promptBiometric: true } }); - return false; - } - - if ( - !routerState.url.includes("remove-password") && - (await this.keyConnectorService.getConvertAccountRequired()) - ) { - this.router.navigate(["/remove-password"]); - return false; - } - - return true; - } -} diff --git a/jslib/angular/src/services/jslib-services.module.ts b/jslib/angular/src/services/jslib-services.module.ts index 65e91c7c9..d6bc0c602 100644 --- a/jslib/angular/src/services/jslib-services.module.ts +++ b/jslib/angular/src/services/jslib-services.module.ts @@ -1,82 +1,45 @@ -import { Injector, LOCALE_ID, NgModule } from "@angular/core"; +import { LOCALE_ID, NgModule } from "@angular/core"; import { ApiService as ApiServiceAbstraction } from "@/jslib/common/src/abstractions/api.service"; import { AppIdService as AppIdServiceAbstraction } from "@/jslib/common/src/abstractions/appId.service"; -import { AuditService as AuditServiceAbstraction } from "@/jslib/common/src/abstractions/audit.service"; import { AuthService as AuthServiceAbstraction } from "@/jslib/common/src/abstractions/auth.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@/jslib/common/src/abstractions/broadcaster.service"; -import { CipherService as CipherServiceAbstraction } from "@/jslib/common/src/abstractions/cipher.service"; -import { CollectionService as CollectionServiceAbstraction } from "@/jslib/common/src/abstractions/collection.service"; import { CryptoService as CryptoServiceAbstraction } from "@/jslib/common/src/abstractions/crypto.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@/jslib/common/src/abstractions/cryptoFunction.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@/jslib/common/src/abstractions/environment.service"; -import { EventService as EventServiceAbstraction } from "@/jslib/common/src/abstractions/event.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "@/jslib/common/src/abstractions/fileUpload.service"; -import { FolderService as FolderServiceAbstraction } from "@/jslib/common/src/abstractions/folder.service"; import { I18nService as I18nServiceAbstraction } from "@/jslib/common/src/abstractions/i18n.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@/jslib/common/src/abstractions/keyConnector.service"; import { LogService } from "@/jslib/common/src/abstractions/log.service"; import { MessagingService as MessagingServiceAbstraction } from "@/jslib/common/src/abstractions/messaging.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "@/jslib/common/src/abstractions/notifications.service"; import { OrganizationService as OrganizationServiceAbstraction } from "@/jslib/common/src/abstractions/organization.service"; import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@/jslib/common/src/abstractions/passwordGeneration.service"; -import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@/jslib/common/src/abstractions/passwordReprompt.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@/jslib/common/src/abstractions/platformUtils.service"; import { PolicyService as PolicyServiceAbstraction } from "@/jslib/common/src/abstractions/policy.service"; -import { ProviderService as ProviderServiceAbstraction } from "@/jslib/common/src/abstractions/provider.service"; -import { SearchService as SearchServiceAbstraction } from "@/jslib/common/src/abstractions/search.service"; -import { SendService as SendServiceAbstraction } from "@/jslib/common/src/abstractions/send.service"; -import { SettingsService as SettingsServiceAbstraction } from "@/jslib/common/src/abstractions/settings.service"; import { StateService as StateServiceAbstraction } from "@/jslib/common/src/abstractions/state.service"; import { StateMigrationService as StateMigrationServiceAbstraction } from "@/jslib/common/src/abstractions/stateMigration.service"; import { StorageService as StorageServiceAbstraction } from "@/jslib/common/src/abstractions/storage.service"; -import { SyncService as SyncServiceAbstraction } from "@/jslib/common/src/abstractions/sync.service"; import { TokenService as TokenServiceAbstraction } from "@/jslib/common/src/abstractions/token.service"; -import { TotpService as TotpServiceAbstraction } from "@/jslib/common/src/abstractions/totp.service"; import { TwoFactorService as TwoFactorServiceAbstraction } from "@/jslib/common/src/abstractions/twoFactor.service"; -import { UserVerificationService as UserVerificationServiceAbstraction } from "@/jslib/common/src/abstractions/userVerification.service"; -import { UsernameGenerationService as UsernameGenerationServiceAbstraction } from "@/jslib/common/src/abstractions/usernameGeneration.service"; -import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@/jslib/common/src/abstractions/vaultTimeout.service"; import { StateFactory } from "@/jslib/common/src/factories/stateFactory"; import { Account } from "@/jslib/common/src/models/domain/account"; import { GlobalState } from "@/jslib/common/src/models/domain/globalState"; import { ApiService } from "@/jslib/common/src/services/api.service"; import { AppIdService } from "@/jslib/common/src/services/appId.service"; -import { AuditService } from "@/jslib/common/src/services/audit.service"; import { AuthService } from "@/jslib/common/src/services/auth.service"; -import { CipherService } from "@/jslib/common/src/services/cipher.service"; -import { CollectionService } from "@/jslib/common/src/services/collection.service"; import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.service"; import { CryptoService } from "@/jslib/common/src/services/crypto.service"; import { EnvironmentService } from "@/jslib/common/src/services/environment.service"; -import { EventService } from "@/jslib/common/src/services/event.service"; -import { FileUploadService } from "@/jslib/common/src/services/fileUpload.service"; -import { FolderService } from "@/jslib/common/src/services/folder.service"; import { KeyConnectorService } from "@/jslib/common/src/services/keyConnector.service"; -import { NotificationsService } from "@/jslib/common/src/services/notifications.service"; import { OrganizationService } from "@/jslib/common/src/services/organization.service"; import { PasswordGenerationService } from "@/jslib/common/src/services/passwordGeneration.service"; import { PolicyService } from "@/jslib/common/src/services/policy.service"; -import { ProviderService } from "@/jslib/common/src/services/provider.service"; -import { SearchService } from "@/jslib/common/src/services/search.service"; -import { SendService } from "@/jslib/common/src/services/send.service"; -import { SettingsService } from "@/jslib/common/src/services/settings.service"; import { StateService } from "@/jslib/common/src/services/state.service"; import { StateMigrationService } from "@/jslib/common/src/services/stateMigration.service"; -import { SyncService } from "@/jslib/common/src/services/sync.service"; import { TokenService } from "@/jslib/common/src/services/token.service"; -import { TotpService } from "@/jslib/common/src/services/totp.service"; import { TwoFactorService } from "@/jslib/common/src/services/twoFactor.service"; -import { UserVerificationService } from "@/jslib/common/src/services/userVerification.service"; -import { UsernameGenerationService } from "@/jslib/common/src/services/usernameGeneration.service"; -import { VaultTimeoutService } from "@/jslib/common/src/services/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({ @@ -89,20 +52,12 @@ import { ValidationService } from "./validation.service"; deps: [I18nServiceAbstraction], }, ValidationService, - AuthGuardService, - UnauthGuardService, - LockGuardService, ModalService, { provide: AppIdServiceAbstraction, useClass: AppIdService, deps: [StorageServiceAbstraction], }, - { - provide: AuditServiceAbstraction, - useClass: AuditService, - deps: [CryptoFunctionServiceAbstraction, ApiServiceAbstraction], - }, { provide: AuthServiceAbstraction, useClass: AuthService, @@ -121,66 +76,12 @@ import { ValidationService } from "./validation.service"; I18nServiceAbstraction, ], }, - { - provide: CipherServiceAbstraction, - useFactory: ( - cryptoService: CryptoServiceAbstraction, - settingsService: SettingsServiceAbstraction, - apiService: ApiServiceAbstraction, - fileUploadService: FileUploadServiceAbstraction, - i18nService: I18nServiceAbstraction, - injector: Injector, - logService: LogService, - stateService: StateServiceAbstraction, - ) => - new CipherService( - cryptoService, - settingsService, - apiService, - fileUploadService, - i18nService, - () => injector.get(SearchServiceAbstraction), - logService, - stateService, - ), - deps: [ - CryptoServiceAbstraction, - SettingsServiceAbstraction, - ApiServiceAbstraction, - FileUploadServiceAbstraction, - I18nServiceAbstraction, - Injector, // TODO: Get rid of this circular dependency! - LogService, - StateServiceAbstraction, - ], - }, - { - provide: FolderServiceAbstraction, - useClass: FolderService, - deps: [ - CryptoServiceAbstraction, - ApiServiceAbstraction, - I18nServiceAbstraction, - CipherServiceAbstraction, - StateServiceAbstraction, - ], - }, { provide: LogService, useFactory: () => new ConsoleLogService(false) }, - { - provide: CollectionServiceAbstraction, - useClass: CollectionService, - deps: [CryptoServiceAbstraction, I18nServiceAbstraction, StateServiceAbstraction], - }, { provide: EnvironmentServiceAbstraction, useClass: EnvironmentService, deps: [StateServiceAbstraction], }, - { - provide: TotpServiceAbstraction, - useClass: TotpService, - deps: [CryptoFunctionServiceAbstraction, LogService, StateServiceAbstraction], - }, { provide: TokenServiceAbstraction, useClass: TokenService, deps: [StateServiceAbstraction] }, { provide: CryptoServiceAbstraction, @@ -197,11 +98,6 @@ import { ValidationService } from "./validation.service"; useClass: PasswordGenerationService, deps: [CryptoServiceAbstraction, PolicyServiceAbstraction, StateServiceAbstraction], }, - { - provide: UsernameGenerationServiceAbstraction, - useClass: UsernameGenerationService, - deps: [CryptoServiceAbstraction, StateServiceAbstraction], - }, { provide: ApiServiceAbstraction, useFactory: ( @@ -226,114 +122,7 @@ import { ValidationService } from "./validation.service"; AppIdServiceAbstraction, ], }, - { - provide: FileUploadServiceAbstraction, - useClass: FileUploadService, - deps: [LogService, ApiServiceAbstraction], - }, - { - provide: SyncServiceAbstraction, - useFactory: ( - apiService: ApiServiceAbstraction, - settingsService: SettingsServiceAbstraction, - folderService: FolderServiceAbstraction, - cipherService: CipherServiceAbstraction, - cryptoService: CryptoServiceAbstraction, - collectionService: CollectionServiceAbstraction, - messagingService: MessagingServiceAbstraction, - policyService: PolicyServiceAbstraction, - sendService: SendServiceAbstraction, - logService: LogService, - keyConnectorService: KeyConnectorServiceAbstraction, - stateService: StateServiceAbstraction, - organizationService: OrganizationServiceAbstraction, - providerService: ProviderServiceAbstraction, - ) => - new SyncService( - apiService, - settingsService, - folderService, - cipherService, - cryptoService, - collectionService, - messagingService, - policyService, - sendService, - logService, - keyConnectorService, - stateService, - organizationService, - providerService, - async (expired: boolean) => messagingService.send("logout", { expired: expired }), - ), - deps: [ - ApiServiceAbstraction, - SettingsServiceAbstraction, - FolderServiceAbstraction, - CipherServiceAbstraction, - CryptoServiceAbstraction, - CollectionServiceAbstraction, - MessagingServiceAbstraction, - PolicyServiceAbstraction, - SendServiceAbstraction, - LogService, - KeyConnectorServiceAbstraction, - StateServiceAbstraction, - OrganizationServiceAbstraction, - ProviderServiceAbstraction, - ], - }, { provide: BroadcasterServiceAbstraction, useClass: BroadcasterService }, - { - provide: SettingsServiceAbstraction, - useClass: SettingsService, - deps: [StateServiceAbstraction], - }, - { - provide: VaultTimeoutServiceAbstraction, - useFactory: ( - cipherService: CipherServiceAbstraction, - folderService: FolderServiceAbstraction, - collectionService: CollectionServiceAbstraction, - cryptoService: CryptoServiceAbstraction, - platformUtilsService: PlatformUtilsServiceAbstraction, - messagingService: MessagingServiceAbstraction, - searchService: SearchServiceAbstraction, - tokenService: TokenServiceAbstraction, - policyService: PolicyServiceAbstraction, - keyConnectorService: KeyConnectorServiceAbstraction, - stateService: StateServiceAbstraction, - ) => - new VaultTimeoutService( - cipherService, - folderService, - collectionService, - cryptoService, - platformUtilsService, - messagingService, - searchService, - tokenService, - policyService, - keyConnectorService, - stateService, - null, - async (userId?: string) => - messagingService.send("logout", { expired: false, userId: userId }), - ), - deps: [ - CipherServiceAbstraction, - FolderServiceAbstraction, - CollectionServiceAbstraction, - CryptoServiceAbstraction, - PlatformUtilsServiceAbstraction, - MessagingServiceAbstraction, - SearchServiceAbstraction, - TokenServiceAbstraction, - PolicyServiceAbstraction, - KeyConnectorServiceAbstraction, - StateServiceAbstraction, - ], - }, { provide: StateServiceAbstraction, useFactory: ( @@ -369,72 +158,11 @@ import { ValidationService } from "./validation.service"; ), deps: [StorageServiceAbstraction, "SECURE_STORAGE"], }, - { - provide: SearchServiceAbstraction, - useClass: SearchService, - deps: [CipherServiceAbstraction, LogService, I18nServiceAbstraction], - }, - { - provide: NotificationsServiceAbstraction, - useFactory: ( - syncService: SyncServiceAbstraction, - appIdService: AppIdServiceAbstraction, - apiService: ApiServiceAbstraction, - vaultTimeoutService: VaultTimeoutServiceAbstraction, - environmentService: EnvironmentServiceAbstraction, - messagingService: MessagingServiceAbstraction, - logService: LogService, - stateService: StateServiceAbstraction, - ) => - new NotificationsService( - syncService, - appIdService, - apiService, - vaultTimeoutService, - environmentService, - async () => messagingService.send("logout", { expired: true }), - logService, - stateService, - ), - deps: [ - SyncServiceAbstraction, - AppIdServiceAbstraction, - ApiServiceAbstraction, - VaultTimeoutServiceAbstraction, - EnvironmentServiceAbstraction, - MessagingServiceAbstraction, - LogService, - StateServiceAbstraction, - ], - }, - { - provide: EventServiceAbstraction, - useClass: EventService, - deps: [ - ApiServiceAbstraction, - CipherServiceAbstraction, - StateServiceAbstraction, - LogService, - OrganizationServiceAbstraction, - ], - }, { provide: PolicyServiceAbstraction, useClass: PolicyService, deps: [StateServiceAbstraction, OrganizationServiceAbstraction, ApiServiceAbstraction], }, - { - provide: SendServiceAbstraction, - useClass: SendService, - deps: [ - CryptoServiceAbstraction, - ApiServiceAbstraction, - FileUploadServiceAbstraction, - I18nServiceAbstraction, - CryptoFunctionServiceAbstraction, - StateServiceAbstraction, - ], - }, { provide: KeyConnectorServiceAbstraction, useClass: KeyConnectorService, @@ -448,22 +176,11 @@ import { ValidationService } from "./validation.service"; CryptoFunctionServiceAbstraction, ], }, - { - provide: UserVerificationServiceAbstraction, - useClass: UserVerificationService, - deps: [CryptoServiceAbstraction, I18nServiceAbstraction, ApiServiceAbstraction], - }, - { provide: PasswordRepromptServiceAbstraction, useClass: PasswordRepromptService }, { provide: OrganizationServiceAbstraction, useClass: OrganizationService, deps: [StateServiceAbstraction], }, - { - provide: ProviderServiceAbstraction, - useClass: ProviderService, - deps: [StateServiceAbstraction], - }, { provide: TwoFactorServiceAbstraction, useClass: TwoFactorService, diff --git a/jslib/angular/src/services/lock-guard.service.ts b/jslib/angular/src/services/lock-guard.service.ts deleted file mode 100644 index f04f7a6d1..000000000 --- a/jslib/angular/src/services/lock-guard.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Router } from "@angular/router"; - -import { StateService } from "@/jslib/common/src/abstractions/state.service"; -import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service"; - -@Injectable() -export class LockGuardService { - protected homepage = "vault"; - protected loginpage = "login"; - constructor( - private vaultTimeoutService: VaultTimeoutService, - private router: Router, - private stateService: StateService, - ) {} - - async canActivate() { - if (await this.vaultTimeoutService.isLocked()) { - return true; - } - - const redirectUrl = (await this.stateService.getIsAuthenticated()) - ? [this.homepage] - : [this.loginpage]; - - this.router.navigate(redirectUrl); - return false; - } -} diff --git a/jslib/angular/src/services/passwordReprompt.service.ts b/jslib/angular/src/services/passwordReprompt.service.ts deleted file mode 100644 index 3051780cf..000000000 --- a/jslib/angular/src/services/passwordReprompt.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from "@angular/core"; - -import { KeyConnectorService } from "@/jslib/common/src/abstractions/keyConnector.service"; -import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@/jslib/common/src/abstractions/passwordReprompt.service"; - -import { PasswordRepromptComponent } from "../components/password-reprompt.component"; - -import { ModalService } from "./modal.service"; - -/** - * Used to verify the user's Master Password for the "Master Password Re-prompt" feature only. - * See UserVerificationService for any other situation where you need to verify the user's identity. - */ -@Injectable() -export class PasswordRepromptService implements PasswordRepromptServiceAbstraction { - protected component = PasswordRepromptComponent; - - constructor( - private modalService: ModalService, - private keyConnectorService: KeyConnectorService, - ) {} - - protectedFields() { - return ["TOTP", "Password", "H_Field", "Card Number", "Security Code"]; - } - - async showPasswordPrompt() { - if (!(await this.enabled())) { - return true; - } - - const ref = this.modalService.open(this.component, { allowMultipleModals: true }); - - if (ref == null) { - return false; - } - - const result = await ref.onClosedPromise(); - return result === true; - } - - async enabled() { - return !(await this.keyConnectorService.getUsesKeyConnector()); - } -} diff --git a/jslib/angular/src/services/unauth-guard.service.ts b/jslib/angular/src/services/unauth-guard.service.ts deleted file mode 100644 index ffb2c5ca6..000000000 --- a/jslib/angular/src/services/unauth-guard.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Router } from "@angular/router"; - -import { StateService } from "@/jslib/common/src/abstractions/state.service"; -import { VaultTimeoutService } from "@/jslib/common/src/abstractions/vaultTimeout.service"; - -@Injectable() -export class UnauthGuardService { - protected homepage = "vault"; - constructor( - private vaultTimeoutService: VaultTimeoutService, - private router: Router, - private stateService: StateService, - ) {} - - async canActivate() { - const isAuthed = await this.stateService.getIsAuthenticated(); - if (isAuthed) { - const locked = await this.vaultTimeoutService.isLocked(); - if (locked) { - this.router.navigate(["lock"]); - } else { - this.router.navigate([this.homepage]); - } - return false; - } - return true; - } -} diff --git a/jslib/common/spec/services/cipher.service.spec.ts b/jslib/common/spec/services/cipher.service.spec.ts deleted file mode 100644 index 5ab62d4f6..000000000 --- a/jslib/common/spec/services/cipher.service.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Arg, Substitute, SubstituteOf } from "@fluffy-spoon/substitute"; - -import { ApiService } from "@/jslib/common/src/abstractions/api.service"; -import { CryptoService } from "@/jslib/common/src/abstractions/crypto.service"; -import { FileUploadService } from "@/jslib/common/src/abstractions/fileUpload.service"; -import { I18nService } from "@/jslib/common/src/abstractions/i18n.service"; -import { LogService } from "@/jslib/common/src/abstractions/log.service"; -import { SearchService } from "@/jslib/common/src/abstractions/search.service"; -import { SettingsService } from "@/jslib/common/src/abstractions/settings.service"; -import { StateService } from "@/jslib/common/src/abstractions/state.service"; -import { Utils } from "@/jslib/common/src/misc/utils"; -import { Cipher } from "@/jslib/common/src/models/domain/cipher"; -import { EncArrayBuffer } from "@/jslib/common/src/models/domain/encArrayBuffer"; -import { EncString } from "@/jslib/common/src/models/domain/encString"; -import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey"; -import { CipherService } from "@/jslib/common/src/services/cipher.service"; - -const ENCRYPTED_TEXT = "This data has been encrypted"; -const ENCRYPTED_BYTES = new EncArrayBuffer(Utils.fromUtf8ToArray(ENCRYPTED_TEXT).buffer); - -describe("Cipher Service", () => { - let cryptoService: SubstituteOf; - let stateService: SubstituteOf; - let settingsService: SubstituteOf; - let apiService: SubstituteOf; - let fileUploadService: SubstituteOf; - let i18nService: SubstituteOf; - let searchService: SubstituteOf; - let logService: SubstituteOf; - - let cipherService: CipherService; - - beforeEach(() => { - cryptoService = Substitute.for(); - stateService = Substitute.for(); - settingsService = Substitute.for(); - apiService = Substitute.for(); - fileUploadService = Substitute.for(); - i18nService = Substitute.for(); - searchService = Substitute.for(); - logService = Substitute.for(); - - cryptoService.encryptToBytes(Arg.any(), Arg.any()).resolves(ENCRYPTED_BYTES); - cryptoService.encrypt(Arg.any(), Arg.any()).resolves(new EncString(ENCRYPTED_TEXT)); - - cipherService = new CipherService( - cryptoService, - settingsService, - apiService, - fileUploadService, - i18nService, - () => searchService, - logService, - stateService, - ); - }); - - it("attachments upload encrypted file contents", async () => { - const fileName = "filename"; - const fileData = new Uint8Array(10).buffer; - cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer)); - - await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData); - - fileUploadService - .received(1) - .uploadCipherAttachment(Arg.any(), Arg.any(), new EncString(ENCRYPTED_TEXT), ENCRYPTED_BYTES); - }); -}); diff --git a/jslib/common/src/abstractions/audit.service.ts b/jslib/common/src/abstractions/audit.service.ts deleted file mode 100644 index 6b6b81f69..000000000 --- a/jslib/common/src/abstractions/audit.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { BreachAccountResponse } from "../models/response/breachAccountResponse"; - -export abstract class AuditService { - passwordLeaked: (password: string) => Promise; - breachedAccounts: (username: string) => Promise; -} diff --git a/jslib/common/src/abstractions/cipher.service.ts b/jslib/common/src/abstractions/cipher.service.ts deleted file mode 100644 index 5ae5a006a..000000000 --- a/jslib/common/src/abstractions/cipher.service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { CipherType } from "../enums/cipherType"; -import { UriMatchType } from "../enums/uriMatchType"; -import { CipherData } from "../models/data/cipherData"; -import { Cipher } from "../models/domain/cipher"; -import { Field } from "../models/domain/field"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { CipherView } from "../models/view/cipherView"; -import { FieldView } from "../models/view/fieldView"; - -export abstract class CipherService { - clearCache: (userId?: string) => Promise; - encrypt: ( - model: CipherView, - key?: SymmetricCryptoKey, - originalCipher?: Cipher, - ) => Promise; - encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; - encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise; - getAllDecryptedForUrl: ( - url: string, - includeOtherTypes?: CipherType[], - defaultMatch?: UriMatchType, - ) => Promise; - getAllFromApiForOrganization: (organizationId: string) => Promise; - getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getNextCipherForUrl: (url: string) => Promise; - updateLastUsedIndexForUrl: (url: string) => void; - updateLastUsedDate: (id: string) => Promise; - updateLastLaunchedDate: (id: string) => Promise; - saveNeverDomain: (domain: string) => Promise; - saveWithServer: (cipher: Cipher) => Promise; - shareWithServer: ( - cipher: CipherView, - organizationId: string, - collectionIds: string[], - ) => Promise; - shareManyWithServer: ( - ciphers: CipherView[], - organizationId: string, - collectionIds: string[], - ) => Promise; - saveAttachmentWithServer: ( - cipher: Cipher, - unencryptedFile: any, - admin?: boolean, - ) => Promise; - saveAttachmentRawWithServer: ( - cipher: Cipher, - filename: string, - data: ArrayBuffer, - admin?: boolean, - ) => Promise; - saveCollectionsWithServer: (cipher: Cipher) => Promise; - upsert: (cipher: CipherData | CipherData[]) => Promise; - replace: (ciphers: { [id: string]: CipherData }) => Promise; - clear: (userId: string) => Promise; - moveManyWithServer: (ids: string[], folderId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string) => Promise; - deleteManyWithServer: (ids: string[]) => Promise; - deleteAttachment: (id: string, attachmentId: string) => Promise; - deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; - sortCiphersByLastUsed: (a: any, b: any) => number; - sortCiphersByLastUsedThenName: (a: any, b: any) => number; - getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; - softDelete: (id: string | string[]) => Promise; - softDeleteWithServer: (id: string) => Promise; - softDeleteManyWithServer: (ids: string[]) => Promise; - restore: ( - cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], - ) => Promise; - restoreWithServer: (id: string) => Promise; - restoreManyWithServer: (ids: string[]) => Promise; -} diff --git a/jslib/common/src/abstractions/collection.service.ts b/jslib/common/src/abstractions/collection.service.ts deleted file mode 100644 index 0673e9066..000000000 --- a/jslib/common/src/abstractions/collection.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { CollectionData } from "../models/data/collectionData"; -import { Collection } from "../models/domain/collection"; -import { TreeNode } from "../models/domain/treeNode"; -import { CollectionView } from "../models/view/collectionView"; - -export abstract class CollectionService { - clearCache: (userId?: string) => Promise; - encrypt: (model: CollectionView) => Promise; - decryptMany: (collections: Collection[]) => Promise; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - getAllNested: (collections?: CollectionView[]) => Promise[]>; - getNested: (id: string) => Promise>; - upsert: (collection: CollectionData | CollectionData[]) => Promise; - replace: (collections: { [id: string]: CollectionData }) => Promise; - clear: (userId: string) => Promise; - delete: (id: string | string[]) => Promise; -} diff --git a/jslib/common/src/abstractions/event.service.ts b/jslib/common/src/abstractions/event.service.ts deleted file mode 100644 index 2f7660fb5..000000000 --- a/jslib/common/src/abstractions/event.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { EventType } from "../enums/eventType"; - -export abstract class EventService { - collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise; - uploadEvents: (userId?: string) => Promise; - clearEvents: (userId?: string) => Promise; -} diff --git a/jslib/common/src/abstractions/fileUpload.service.ts b/jslib/common/src/abstractions/fileUpload.service.ts deleted file mode 100644 index 6918347ec..000000000 --- a/jslib/common/src/abstractions/fileUpload.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - -export abstract class FileUploadService { - uploadSendFile: ( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) => Promise; - uploadCipherAttachment: ( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) => Promise; -} diff --git a/jslib/common/src/abstractions/folder.service.ts b/jslib/common/src/abstractions/folder.service.ts deleted file mode 100644 index 574cebfcd..000000000 --- a/jslib/common/src/abstractions/folder.service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FolderData } from "../models/data/folderData"; -import { Folder } from "../models/domain/folder"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { TreeNode } from "../models/domain/treeNode"; -import { FolderView } from "../models/view/folderView"; - -export abstract class FolderService { - clearCache: (userId?: string) => Promise; - encrypt: (model: FolderView, key?: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - getAllNested: () => Promise[]>; - getNested: (id: string) => Promise>; - saveWithServer: (folder: Folder) => Promise; - upsert: (folder: FolderData | FolderData[]) => Promise; - replace: (folders: { [id: string]: FolderData }) => Promise; - clear: (userId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string) => Promise; -} diff --git a/jslib/common/src/abstractions/notifications.service.ts b/jslib/common/src/abstractions/notifications.service.ts deleted file mode 100644 index 921e8e62d..000000000 --- a/jslib/common/src/abstractions/notifications.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -export abstract class NotificationsService { - init: () => Promise; - updateConnection: (sync?: boolean) => Promise; - reconnectFromActivity: () => Promise; - disconnectFromInactivity: () => Promise; -} diff --git a/jslib/common/src/abstractions/passwordReprompt.service.ts b/jslib/common/src/abstractions/passwordReprompt.service.ts deleted file mode 100644 index 6253425b3..000000000 --- a/jslib/common/src/abstractions/passwordReprompt.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -export abstract class PasswordRepromptService { - protectedFields: () => string[]; - showPasswordPrompt: () => Promise; - enabled: () => Promise; -} diff --git a/jslib/common/src/abstractions/provider.service.ts b/jslib/common/src/abstractions/provider.service.ts deleted file mode 100644 index e47463692..000000000 --- a/jslib/common/src/abstractions/provider.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ProviderData } from "../models/data/providerData"; -import { Provider } from "../models/domain/provider"; - -export abstract class ProviderService { - get: (id: string) => Promise; - getAll: () => Promise; - save: (providers: { [id: string]: ProviderData }) => Promise; -} diff --git a/jslib/common/src/abstractions/search.service.ts b/jslib/common/src/abstractions/search.service.ts deleted file mode 100644 index cfe569a24..000000000 --- a/jslib/common/src/abstractions/search.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { CipherView } from "../models/view/cipherView"; -import { SendView } from "../models/view/sendView"; - -export abstract class SearchService { - indexedEntityId?: string = null; - clearIndex: () => void; - isSearchable: (query: string) => boolean; - indexCiphers: (indexedEntityGuid?: string, ciphersToIndex?: CipherView[]) => Promise; - searchCiphers: ( - query: string, - filter?: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[], - ciphers?: CipherView[], - ) => Promise; - searchCiphersBasic: (ciphers: CipherView[], query: string, deleted?: boolean) => CipherView[]; - searchSends: (sends: SendView[], query: string) => SendView[]; -} diff --git a/jslib/common/src/abstractions/send.service.ts b/jslib/common/src/abstractions/send.service.ts deleted file mode 100644 index 0bf5d485e..000000000 --- a/jslib/common/src/abstractions/send.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SendData } from "../models/data/sendData"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { Send } from "../models/domain/send"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { SendView } from "../models/view/sendView"; - -export abstract class SendService { - clearCache: () => Promise; - encrypt: ( - model: SendView, - file: File | ArrayBuffer, - password: string, - key?: SymmetricCryptoKey, - ) => Promise<[Send, EncArrayBuffer]>; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - saveWithServer: (sendData: [Send, EncArrayBuffer]) => Promise; - upsert: (send: SendData | SendData[]) => Promise; - replace: (sends: { [id: string]: SendData }) => Promise; - clear: (userId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string) => Promise; - removePasswordWithServer: (id: string) => Promise; -} diff --git a/jslib/common/src/abstractions/settings.service.ts b/jslib/common/src/abstractions/settings.service.ts deleted file mode 100644 index e7886585b..000000000 --- a/jslib/common/src/abstractions/settings.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -export abstract class SettingsService { - clearCache: () => Promise; - getEquivalentDomains: () => Promise; - setEquivalentDomains: (equivalentDomains: string[][]) => Promise; - clear: (userId?: string) => Promise; -} diff --git a/jslib/common/src/abstractions/sync.service.ts b/jslib/common/src/abstractions/sync.service.ts deleted file mode 100644 index 936d027fd..000000000 --- a/jslib/common/src/abstractions/sync.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { - SyncCipherNotification, - SyncFolderNotification, - SyncSendNotification, -} from "../models/response/notificationResponse"; - -export abstract class SyncService { - syncInProgress: boolean; - - getLastSync: () => Promise; - setLastSync: (date: Date, userId?: string) => Promise; - fullSync: (forceSync: boolean, allowThrowOnError?: boolean) => Promise; - syncUpsertFolder: (notification: SyncFolderNotification, isEdit: boolean) => Promise; - syncDeleteFolder: (notification: SyncFolderNotification) => Promise; - syncUpsertCipher: (notification: SyncCipherNotification, isEdit: boolean) => Promise; - syncDeleteCipher: (notification: SyncFolderNotification) => Promise; - syncUpsertSend: (notification: SyncSendNotification, isEdit: boolean) => Promise; - syncDeleteSend: (notification: SyncSendNotification) => Promise; -} diff --git a/jslib/common/src/abstractions/system.service.ts b/jslib/common/src/abstractions/system.service.ts deleted file mode 100644 index b5c1e5959..000000000 --- a/jslib/common/src/abstractions/system.service.ts +++ /dev/null @@ -1,6 +0,0 @@ -export abstract class SystemService { - startProcessReload: () => Promise; - cancelProcessReload: () => void; - clearClipboard: (clipboardValue: string, timeoutMs?: number) => Promise; - clearPendingClipboard: () => Promise; -} diff --git a/jslib/common/src/abstractions/totp.service.ts b/jslib/common/src/abstractions/totp.service.ts deleted file mode 100644 index bf143e269..000000000 --- a/jslib/common/src/abstractions/totp.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -export abstract class TotpService { - getCode: (key: string) => Promise; - getTimeInterval: (key: string) => number; - isAutoCopyEnabled: () => Promise; -} diff --git a/jslib/common/src/abstractions/userVerification.service.ts b/jslib/common/src/abstractions/userVerification.service.ts deleted file mode 100644 index 56a0a08fb..000000000 --- a/jslib/common/src/abstractions/userVerification.service.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -import { Verification } from "../types/verification"; - -export abstract class UserVerificationService { - buildRequest: ( - verification: Verification, - requestClass?: new () => T, - alreadyHashed?: boolean, - ) => Promise; - verifyUser: (verification: Verification) => Promise; - requestOTP: () => Promise; -} diff --git a/jslib/common/src/abstractions/usernameGeneration.service.ts b/jslib/common/src/abstractions/usernameGeneration.service.ts deleted file mode 100644 index baba74d8e..000000000 --- a/jslib/common/src/abstractions/usernameGeneration.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -export abstract class UsernameGenerationService { - generateUsername: (options: any) => Promise; - generateWord: (options: any) => Promise; - generateSubaddress: (options: any) => Promise; - generateCatchall: (options: any) => Promise; - getOptions: () => Promise; - saveOptions: (options: any) => Promise; -} diff --git a/jslib/common/src/abstractions/vaultTimeout.service.ts b/jslib/common/src/abstractions/vaultTimeout.service.ts deleted file mode 100644 index 2c2b2521d..000000000 --- a/jslib/common/src/abstractions/vaultTimeout.service.ts +++ /dev/null @@ -1,11 +0,0 @@ -export abstract class VaultTimeoutService { - isLocked: (userId?: string) => Promise; - checkVaultTimeout: () => Promise; - lock: (allowSoftLock?: boolean, userId?: string) => Promise; - logOut: (userId?: string) => Promise; - setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise; - getVaultTimeout: () => Promise; - isPinLockSet: () => Promise<[boolean, boolean]>; - isBiometricLockSet: () => Promise; - clear: (userId?: string) => Promise; -} diff --git a/jslib/common/src/services/audit.service.ts b/jslib/common/src/services/audit.service.ts deleted file mode 100644 index f854389a7..000000000 --- a/jslib/common/src/services/audit.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { AuditService as AuditServiceAbstraction } from "../abstractions/audit.service"; -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { throttle } from "../misc/throttle"; -import { Utils } from "../misc/utils"; -import { BreachAccountResponse } from "../models/response/breachAccountResponse"; -import { ErrorResponse } from "../models/response/errorResponse"; - -const PwnedPasswordsApi = "https://api.pwnedpasswords.com/range/"; - -export class AuditService implements AuditServiceAbstraction { - constructor( - private cryptoFunctionService: CryptoFunctionService, - private apiService: ApiService, - ) {} - - @throttle(100, () => "passwordLeaked") - async passwordLeaked(password: string): Promise { - const hashBytes = await this.cryptoFunctionService.hash(password, "sha1"); - const hash = Utils.fromBufferToHex(hashBytes).toUpperCase(); - const hashStart = hash.substr(0, 5); - const hashEnding = hash.substr(5); - - const response = await this.apiService.nativeFetch(new Request(PwnedPasswordsApi + hashStart)); - const leakedHashes = await response.text(); - const match = leakedHashes.split(/\r?\n/).find((v) => { - return v.split(":")[0] === hashEnding; - }); - - return match != null ? parseInt(match.split(":")[1], 10) : 0; - } - - async breachedAccounts(username: string): Promise { - try { - return await this.apiService.getHibpBreach(username); - } catch (e) { - const error = e as ErrorResponse; - if (error.statusCode === 404) { - return []; - } - throw new Error(); - } - } -} diff --git a/jslib/common/src/services/azureFileUpload.service.ts b/jslib/common/src/services/azureFileUpload.service.ts deleted file mode 100644 index bcfcda17f..000000000 --- a/jslib/common/src/services/azureFileUpload.service.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { LogService } from "../abstractions/log.service"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - -const MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB -const MAX_BLOCKS_PER_BLOB = 50000; - -export class AzureFileUploadService { - constructor(private logService: LogService) {} - - async upload(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { - if (data.buffer.byteLength <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { - return await this.azureUploadBlob(url, data); - } else { - return await this.azureUploadBlocks(url, data, renewalCallback); - } - } - private async azureUploadBlob(url: string, data: EncArrayBuffer) { - const urlObject = Utils.getUrl(url); - const headers = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": urlObject.searchParams.get("sv"), - "Content-Length": data.buffer.byteLength.toString(), - "x-ms-blob-type": "BlockBlob", - }); - - const request = new Request(url, { - body: data.buffer, - cache: "no-store", - method: "PUT", - headers: headers, - }); - - const blobResponse = await fetch(request); - - if (blobResponse.status !== 201) { - throw new Error(`Failed to create Azure blob: ${blobResponse.status}`); - } - } - private async azureUploadBlocks( - url: string, - data: EncArrayBuffer, - renewalCallback: () => Promise, - ) { - const baseUrl = Utils.getUrl(url); - const blockSize = this.getMaxBlockSize(baseUrl.searchParams.get("sv")); - let blockIndex = 0; - const numBlocks = Math.ceil(data.buffer.byteLength / blockSize); - const blocksStaged: string[] = []; - - if (numBlocks > MAX_BLOCKS_PER_BLOB) { - throw new Error( - `Cannot upload file, exceeds maximum size of ${blockSize * MAX_BLOCKS_PER_BLOB}`, - ); - } - - // eslint-disable-next-line - try { - while (blockIndex < numBlocks) { - url = await this.renewUrlIfNecessary(url, renewalCallback); - const blockUrl = Utils.getUrl(url); - const blockId = this.encodedBlockId(blockIndex); - blockUrl.searchParams.append("comp", "block"); - blockUrl.searchParams.append("blockid", blockId); - const start = blockIndex * blockSize; - const blockData = data.buffer.slice(start, start + blockSize); - const blockHeaders = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": blockUrl.searchParams.get("sv"), - "Content-Length": blockData.byteLength.toString(), - }); - - const blockRequest = new Request(blockUrl.toString(), { - body: blockData, - cache: "no-store", - method: "PUT", - headers: blockHeaders, - }); - - const blockResponse = await fetch(blockRequest); - - if (blockResponse.status !== 201) { - const message = `Unsuccessful block PUT. Received status ${blockResponse.status}`; - this.logService.error(message + "\n" + (await blockResponse.json())); - throw new Error(message); - } - - blocksStaged.push(blockId); - blockIndex++; - } - - url = await this.renewUrlIfNecessary(url, renewalCallback); - const blockListUrl = Utils.getUrl(url); - const blockListXml = this.blockListXml(blocksStaged); - blockListUrl.searchParams.append("comp", "blocklist"); - const headers = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": blockListUrl.searchParams.get("sv"), - "Content-Length": blockListXml.length.toString(), - }); - - const request = new Request(blockListUrl.toString(), { - body: blockListXml, - cache: "no-store", - method: "PUT", - headers: headers, - }); - - const response = await fetch(request); - - if (response.status !== 201) { - const message = `Unsuccessful block list PUT. Received status ${response.status}`; - this.logService.error(message + "\n" + (await response.json())); - throw new Error(message); - } - } catch (e) { - throw e; - } - } - - private async renewUrlIfNecessary( - url: string, - renewalCallback: () => Promise, - ): Promise { - const urlObject = Utils.getUrl(url); - const expiry = new Date(urlObject.searchParams.get("se") ?? ""); - - if (isNaN(expiry.getTime())) { - expiry.setTime(Date.now() + 3600000); - } - - if (expiry.getTime() < Date.now() + 1000) { - return await renewalCallback(); - } - return url; - } - - private encodedBlockId(blockIndex: number) { - // Encoded blockId max size is 64, so pre-encoding max size is 48 - const utfBlockId = ( - "000000000000000000000000000000000000000000000000" + blockIndex.toString() - ).slice(-48); - return Utils.fromUtf8ToB64(utfBlockId); - } - - private blockListXml(blockIdList: string[]) { - let xml = ''; - blockIdList.forEach((blockId) => { - xml += `${blockId}`; - }); - xml += ""; - return xml; - } - - private getMaxBlockSize(version: string) { - if (Version.compare(version, "2019-12-12") >= 0) { - return 4000 * 1024 * 1024; // 4000 MiB - } else if (Version.compare(version, "2016-05-31") >= 0) { - return 100 * 1024 * 1024; // 100 MiB - } else { - return 4 * 1024 * 1024; // 4 MiB - } - } -} - -class Version { - /** - * Compares two Azure Versions against each other - * @param a Version to compare - * @param b Version to compare - * @returns a number less than zero if b is newer than a, 0 if equal, - * and greater than zero if a is newer than b - */ - static compare(a: Required | string, b: Required | string) { - if (typeof a === "string") { - a = new Version(a); - } - - if (typeof b === "string") { - b = new Version(b); - } - - return a.year !== b.year - ? a.year - b.year - : a.month !== b.month - ? a.month - b.month - : a.day !== b.day - ? a.day - b.day - : 0; - } - year = 0; - month = 0; - day = 0; - - constructor(version: string) { - try { - const parts = version.split("-").map((v) => Number.parseInt(v, 10)); - this.year = parts[0]; - this.month = parts[1]; - this.day = parts[2]; - } catch { - // Ignore error - } - } - /** - * Compares two Azure Versions against each other - * @param compareTo Version to compare against - * @returns a number less than zero if compareTo is newer, 0 if equal, - * and greater than zero if this is greater than compareTo - */ - compare(compareTo: Required | string) { - return Version.compare(this, compareTo); - } -} diff --git a/jslib/common/src/services/bitwardenFileUpload.service.ts b/jslib/common/src/services/bitwardenFileUpload.service.ts deleted file mode 100644 index 0347a9025..000000000 --- a/jslib/common/src/services/bitwardenFileUpload.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - -export class BitwardenFileUploadService { - constructor(private apiService: ApiService) {} - - async upload( - encryptedFileName: string, - encryptedFileData: EncArrayBuffer, - apiCall: (fd: FormData) => Promise, - ) { - const fd = new FormData(); - try { - const blob = new Blob([encryptedFileData.buffer], { type: "application/octet-stream" }); - fd.append("data", blob, encryptedFileName); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append( - "data", - Buffer.from(encryptedFileData.buffer) as any, - { - filepath: encryptedFileName, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - - await apiCall(fd); - } -} diff --git a/jslib/common/src/services/cipher.service.ts b/jslib/common/src/services/cipher.service.ts deleted file mode 100644 index 876d2d353..000000000 --- a/jslib/common/src/services/cipher.service.ts +++ /dev/null @@ -1,1282 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { FileUploadService } from "../abstractions/fileUpload.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { LogService } from "../abstractions/log.service"; -import { SearchService } from "../abstractions/search.service"; -import { SettingsService } from "../abstractions/settings.service"; -import { StateService } from "../abstractions/state.service"; -import { CipherType } from "../enums/cipherType"; -import { FieldType } from "../enums/fieldType"; -import { UriMatchType } from "../enums/uriMatchType"; -import { sequentialize } from "../misc/sequentialize"; -import { Utils } from "../misc/utils"; -import { CipherData } from "../models/data/cipherData"; -import { Attachment } from "../models/domain/attachment"; -import { Card } from "../models/domain/card"; -import { Cipher } from "../models/domain/cipher"; -import Domain from "../models/domain/domainBase"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { Field } from "../models/domain/field"; -import { Identity } from "../models/domain/identity"; -import { Login } from "../models/domain/login"; -import { LoginUri } from "../models/domain/loginUri"; -import { Password } from "../models/domain/password"; -import { SecureNote } from "../models/domain/secureNote"; -import { SortedCiphersCache } from "../models/domain/sortedCiphersCache"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { AttachmentRequest } from "../models/request/attachmentRequest"; -import { CipherBulkDeleteRequest } from "../models/request/cipherBulkDeleteRequest"; -import { CipherBulkMoveRequest } from "../models/request/cipherBulkMoveRequest"; -import { CipherBulkRestoreRequest } from "../models/request/cipherBulkRestoreRequest"; -import { CipherBulkShareRequest } from "../models/request/cipherBulkShareRequest"; -import { CipherCollectionsRequest } from "../models/request/cipherCollectionsRequest"; -import { CipherCreateRequest } from "../models/request/cipherCreateRequest"; -import { CipherRequest } from "../models/request/cipherRequest"; -import { CipherShareRequest } from "../models/request/cipherShareRequest"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { ErrorResponse } from "../models/response/errorResponse"; -import { AttachmentView } from "../models/view/attachmentView"; -import { CipherView } from "../models/view/cipherView"; -import { FieldView } from "../models/view/fieldView"; -import { PasswordHistoryView } from "../models/view/passwordHistoryView"; -import { View } from "../models/view/view"; - -const DomainMatchBlacklist = new Map>([ - ["google.com", new Set(["script.google.com"])], -]); - -export class CipherService implements CipherServiceAbstraction { - private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache( - this.sortCiphersByLastUsed, - ); - - constructor( - private cryptoService: CryptoService, - private settingsService: SettingsService, - private apiService: ApiService, - private fileUploadService: FileUploadService, - private i18nService: I18nService, - private searchService: () => SearchService, - private logService: LogService, - private stateService: StateService, - ) {} - - async getDecryptedCipherCache(): Promise { - const decryptedCiphers = await this.stateService.getDecryptedCiphers(); - return decryptedCiphers; - } - - async setDecryptedCipherCache(value: CipherView[]) { - await this.stateService.setDecryptedCiphers(value); - if (this.searchService != null) { - if (value == null) { - this.searchService().clearIndex(); - } else { - this.searchService().indexCiphers(); - } - } - } - - async clearCache(userId?: string): Promise { - await this.clearDecryptedCiphersState(userId); - } - - async encrypt( - model: CipherView, - key?: SymmetricCryptoKey, - originalCipher: Cipher = null, - ): Promise { - // Adjust password history - if (model.id != null) { - if (originalCipher == null) { - originalCipher = await this.get(model.id); - } - if (originalCipher != null) { - const existingCipher = await originalCipher.decrypt(); - model.passwordHistory = existingCipher.passwordHistory || []; - if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { - if ( - existingCipher.login.password != null && - existingCipher.login.password !== "" && - existingCipher.login.password !== model.login.password - ) { - const ph = new PasswordHistoryView(); - ph.password = existingCipher.login.password; - ph.lastUsedDate = model.login.passwordRevisionDate = new Date(); - model.passwordHistory.splice(0, 0, ph); - } else { - model.login.passwordRevisionDate = existingCipher.login.passwordRevisionDate; - } - } - if (existingCipher.hasFields) { - const existingHiddenFields = existingCipher.fields.filter( - (f) => - f.type === FieldType.Hidden && - f.name != null && - f.name !== "" && - f.value != null && - f.value !== "", - ); - const hiddenFields = - model.fields == null - ? [] - : model.fields.filter( - (f) => f.type === FieldType.Hidden && f.name != null && f.name !== "", - ); - existingHiddenFields.forEach((ef) => { - const matchedField = hiddenFields.find((f) => f.name === ef.name); - if (matchedField == null || matchedField.value !== ef.value) { - const ph = new PasswordHistoryView(); - ph.password = ef.name + ": " + ef.value; - ph.lastUsedDate = new Date(); - model.passwordHistory.splice(0, 0, ph); - } - }); - } - } - if (model.passwordHistory != null && model.passwordHistory.length === 0) { - model.passwordHistory = null; - } else if (model.passwordHistory != null && model.passwordHistory.length > 5) { - // only save last 5 history - model.passwordHistory = model.passwordHistory.slice(0, 5); - } - } - - const cipher = new Cipher(); - cipher.id = model.id; - cipher.folderId = model.folderId; - cipher.favorite = model.favorite; - cipher.organizationId = model.organizationId; - cipher.type = model.type; - cipher.collectionIds = model.collectionIds; - cipher.revisionDate = model.revisionDate; - cipher.reprompt = model.reprompt; - - if (key == null && cipher.organizationId != null) { - key = await this.cryptoService.getOrgKey(cipher.organizationId); - if (key == null) { - throw new Error("Cannot encrypt cipher for organization. No key."); - } - } - await Promise.all([ - this.encryptObjProperty( - model, - cipher, - { - name: null, - notes: null, - }, - key, - ), - this.encryptCipherData(cipher, model, key), - this.encryptFields(model.fields, key).then((fields) => { - cipher.fields = fields; - }), - this.encryptPasswordHistories(model.passwordHistory, key).then((ph) => { - cipher.passwordHistory = ph; - }), - this.encryptAttachments(model.attachments, key).then((attachments) => { - cipher.attachments = attachments; - }), - ]); - - return cipher; - } - - async encryptAttachments( - attachmentsModel: AttachmentView[], - key: SymmetricCryptoKey, - ): Promise { - if (attachmentsModel == null || attachmentsModel.length === 0) { - return null; - } - - const promises: Promise[] = []; - const encAttachments: Attachment[] = []; - attachmentsModel.forEach(async (model) => { - const attachment = new Attachment(); - attachment.id = model.id; - attachment.size = model.size; - attachment.sizeName = model.sizeName; - attachment.url = model.url; - const promise = this.encryptObjProperty( - model, - attachment, - { - fileName: null, - }, - key, - ).then(async () => { - if (model.key != null) { - attachment.key = await this.cryptoService.encrypt(model.key.key, key); - } - encAttachments.push(attachment); - }); - promises.push(promise); - }); - - await Promise.all(promises); - return encAttachments; - } - - async encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise { - if (!fieldsModel || !fieldsModel.length) { - return null; - } - - const self = this; - const encFields: Field[] = []; - await fieldsModel.reduce(async (promise, field) => { - await promise; - const encField = await self.encryptField(field, key); - encFields.push(encField); - }, Promise.resolve()); - - return encFields; - } - - async encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise { - const field = new Field(); - field.type = fieldModel.type; - field.linkedId = fieldModel.linkedId; - // normalize boolean type field values - if (fieldModel.type === FieldType.Boolean && fieldModel.value !== "true") { - fieldModel.value = "false"; - } - - await this.encryptObjProperty( - fieldModel, - field, - { - name: null, - value: null, - }, - key, - ); - - return field; - } - - async encryptPasswordHistories( - phModels: PasswordHistoryView[], - key: SymmetricCryptoKey, - ): Promise { - if (!phModels || !phModels.length) { - return null; - } - - const self = this; - const encPhs: Password[] = []; - await phModels.reduce(async (promise, ph) => { - await promise; - const encPh = await self.encryptPasswordHistory(ph, key); - encPhs.push(encPh); - }, Promise.resolve()); - - return encPhs; - } - - async encryptPasswordHistory( - phModel: PasswordHistoryView, - key: SymmetricCryptoKey, - ): Promise { - const ph = new Password(); - ph.lastUsedDate = phModel.lastUsedDate; - - await this.encryptObjProperty( - phModel, - ph, - { - password: null, - }, - key, - ); - - return ph; - } - - async get(id: string): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); - // eslint-disable-next-line - if (ciphers == null || !ciphers.hasOwnProperty(id)) { - return null; - } - - const localData = await this.stateService.getLocalData(); - return new Cipher(ciphers[id], localData ? localData[id] : null); - } - - async getAll(): Promise { - const localData = await this.stateService.getLocalData(); - const ciphers = await this.stateService.getEncryptedCiphers(); - const response: Cipher[] = []; - for (const id in ciphers) { - // eslint-disable-next-line - if (ciphers.hasOwnProperty(id)) { - response.push(new Cipher(ciphers[id], localData ? localData[id] : null)); - } - } - return response; - } - - @sequentialize(() => "getAllDecrypted") - async getAllDecrypted(): Promise { - const userId = await this.stateService.getUserId(); - if ((await this.getDecryptedCipherCache()) != null) { - if ( - this.searchService != null && - (this.searchService().indexedEntityId ?? userId) !== userId - ) { - await this.searchService().indexCiphers(userId, await this.getDecryptedCipherCache()); - } - return await this.getDecryptedCipherCache(); - } - - const decCiphers: CipherView[] = []; - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { - throw new Error("No key."); - } - - const promises: any[] = []; - const ciphers = await this.getAll(); - ciphers.forEach(async (cipher) => { - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); - - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); - await this.setDecryptedCipherCache(decCiphers); - return decCiphers; - } - - async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise { - const ciphers = await this.getAllDecrypted(); - - return ciphers.filter((cipher) => { - if (cipher.isDeleted) { - return false; - } - if (folder && cipher.folderId === groupingId) { - return true; - } else if ( - !folder && - cipher.collectionIds != null && - cipher.collectionIds.indexOf(groupingId) > -1 - ) { - return true; - } - - return false; - }); - } - - async getAllDecryptedForUrl( - url: string, - includeOtherTypes?: CipherType[], - defaultMatch: UriMatchType = null, - ): Promise { - if (url == null && includeOtherTypes == null) { - return Promise.resolve([]); - } - - const domain = Utils.getDomain(url); - const eqDomainsPromise = - domain == null - ? Promise.resolve([]) - : this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => { - let matches: any[] = []; - eqDomains.forEach((eqDomain) => { - if (eqDomain.length && eqDomain.indexOf(domain) >= 0) { - matches = matches.concat(eqDomain); - } - }); - - if (!matches.length) { - matches.push(domain); - } - - return matches; - }); - - const result = await Promise.all([eqDomainsPromise, this.getAllDecrypted()]); - const matchingDomains = result[0]; - const ciphers = result[1]; - - if (defaultMatch == null) { - defaultMatch = await this.stateService.getDefaultUriMatch(); - if (defaultMatch == null) { - defaultMatch = UriMatchType.Domain; - } - } - - return ciphers.filter((cipher) => { - if (cipher.deletedDate != null) { - return false; - } - if (includeOtherTypes != null && includeOtherTypes.indexOf(cipher.type) > -1) { - return true; - } - - if (url != null && cipher.type === CipherType.Login && cipher.login.uris != null) { - for (let i = 0; i < cipher.login.uris.length; i++) { - const u = cipher.login.uris[i]; - if (u.uri == null) { - continue; - } - - const match = u.match == null ? defaultMatch : u.match; - switch (match) { - case UriMatchType.Domain: - if (domain != null && u.domain != null && matchingDomains.indexOf(u.domain) > -1) { - if (DomainMatchBlacklist.has(u.domain)) { - const domainUrlHost = Utils.getHost(url); - if (!DomainMatchBlacklist.get(u.domain).has(domainUrlHost)) { - return true; - } - } else { - return true; - } - } - break; - case UriMatchType.Host: { - const urlHost = Utils.getHost(url); - if (urlHost != null && urlHost === Utils.getHost(u.uri)) { - return true; - } - break; - } - case UriMatchType.Exact: - if (url === u.uri) { - return true; - } - break; - case UriMatchType.StartsWith: - if (url.startsWith(u.uri)) { - return true; - } - break; - case UriMatchType.RegularExpression: - try { - const regex = new RegExp(u.uri, "i"); - if (regex.test(url)) { - return true; - } - } catch (e) { - this.logService.error(e); - } - break; - case UriMatchType.Never: - default: - break; - } - } - } - - return false; - }); - } - - async getAllFromApiForOrganization(organizationId: string): Promise { - const ciphers = await this.apiService.getCiphersOrganization(organizationId); - if (ciphers != null && ciphers.data != null && ciphers.data.length) { - const decCiphers: CipherView[] = []; - const promises: any[] = []; - ciphers.data.forEach((r) => { - const data = new CipherData(r); - const cipher = new Cipher(data); - promises.push(cipher.decrypt().then((c) => decCiphers.push(c))); - }); - await Promise.all(promises); - decCiphers.sort(this.getLocaleSortingFunction()); - return decCiphers; - } else { - return []; - } - } - - async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, true, false, autofillOnPageLoad); - } - - async getLastLaunchedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, false, true, autofillOnPageLoad); - } - - async getNextCipherForUrl(url: string): Promise { - return this.getCipherForUrl(url, false, false, false); - } - - updateLastUsedIndexForUrl(url: string) { - this.sortedCiphersCache.updateLastUsedIndex(url); - } - - async updateLastUsedDate(id: string): Promise { - let ciphersLocalData = await this.stateService.getLocalData(); - if (!ciphersLocalData) { - ciphersLocalData = {}; - } - - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastUsedDate = new Date().getTime(); - } else { - ciphersLocalData[id] = { - lastUsedDate: new Date().getTime(), - }; - } - - await this.stateService.setLocalData(ciphersLocalData); - - const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); - if (!decryptedCipherCache) { - return; - } - - for (let i = 0; i < decryptedCipherCache.length; i++) { - const cached = decryptedCipherCache[i]; - if (cached.id === id) { - cached.localData = ciphersLocalData[id]; - break; - } - } - await this.stateService.setDecryptedCiphers(decryptedCipherCache); - } - - async updateLastLaunchedDate(id: string): Promise { - let ciphersLocalData = await this.stateService.getLocalData(); - if (!ciphersLocalData) { - ciphersLocalData = {}; - } - - if (ciphersLocalData[id]) { - ciphersLocalData[id].lastLaunched = new Date().getTime(); - } else { - ciphersLocalData[id] = { - lastUsedDate: new Date().getTime(), - }; - } - - await this.stateService.setLocalData(ciphersLocalData); - - const decryptedCipherCache = await this.stateService.getDecryptedCiphers(); - if (!decryptedCipherCache) { - return; - } - - for (let i = 0; i < decryptedCipherCache.length; i++) { - const cached = decryptedCipherCache[i]; - if (cached.id === id) { - cached.localData = ciphersLocalData[id]; - break; - } - } - await this.stateService.setDecryptedCiphers(decryptedCipherCache); - } - - async saveNeverDomain(domain: string): Promise { - if (domain == null) { - return; - } - - let domains = await this.stateService.getNeverDomains(); - if (!domains) { - domains = {}; - } - domains[domain] = null; - await this.stateService.setNeverDomains(domains); - } - - async saveWithServer(cipher: Cipher): Promise { - let response: CipherResponse; - if (cipher.id == null) { - if (cipher.collectionIds != null) { - const request = new CipherCreateRequest(cipher); - response = await this.apiService.postCipherCreate(request); - } else { - const request = new CipherRequest(cipher); - response = await this.apiService.postCipher(request); - } - cipher.id = response.id; - } else { - const request = new CipherRequest(cipher); - response = await this.apiService.putCipher(cipher.id, request); - } - - const data = new CipherData( - response, - await this.stateService.getUserId(), - cipher.collectionIds, - ); - await this.upsert(data); - } - - async shareWithServer( - cipher: CipherView, - organizationId: string, - collectionIds: string[], - ): Promise { - const attachmentPromises: Promise[] = []; - if (cipher.attachments != null) { - cipher.attachments.forEach((attachment) => { - if (attachment.key == null) { - attachmentPromises.push( - this.shareAttachmentWithServer(attachment, cipher.id, organizationId), - ); - } - }); - } - await Promise.all(attachmentPromises); - - cipher.organizationId = organizationId; - cipher.collectionIds = collectionIds; - const encCipher = await this.encrypt(cipher); - const request = new CipherShareRequest(encCipher); - const response = await this.apiService.putShareCipher(cipher.id, request); - const data = new CipherData(response, await this.stateService.getUserId(), collectionIds); - await this.upsert(data); - } - - async shareManyWithServer( - ciphers: CipherView[], - organizationId: string, - collectionIds: string[], - ): Promise { - const promises: Promise[] = []; - const encCiphers: Cipher[] = []; - for (const cipher of ciphers) { - cipher.organizationId = organizationId; - cipher.collectionIds = collectionIds; - promises.push( - this.encrypt(cipher).then((c) => { - encCiphers.push(c); - }), - ); - } - await Promise.all(promises); - const request = new CipherBulkShareRequest(encCiphers, collectionIds); - try { - await this.apiService.putShareCiphers(request); - } catch (e) { - for (const cipher of ciphers) { - cipher.organizationId = null; - cipher.collectionIds = null; - } - throw e; - } - const userId = await this.stateService.getUserId(); - await this.upsert(encCiphers.map((c) => c.toCipherData(userId))); - } - - saveAttachmentWithServer(cipher: Cipher, unencryptedFile: any, admin = false): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsArrayBuffer(unencryptedFile); - reader.onload = async (evt: any) => { - try { - const cData = await this.saveAttachmentRawWithServer( - cipher, - unencryptedFile.name, - evt.target.result, - admin, - ); - resolve(cData); - } catch (e) { - reject(e); - } - }; - reader.onerror = () => { - reject("Error reading file."); - }; - }); - } - - async saveAttachmentRawWithServer( - cipher: Cipher, - filename: string, - data: ArrayBuffer, - admin = false, - ): Promise { - const key = await this.cryptoService.getOrgKey(cipher.organizationId); - const encFileName = await this.cryptoService.encrypt(filename, key); - - const dataEncKey = await this.cryptoService.makeEncKey(key); - const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]); - - const request: AttachmentRequest = { - key: dataEncKey[1].encryptedString, - fileName: encFileName.encryptedString, - fileSize: encData.buffer.byteLength, - adminRequest: admin, - }; - - let response: CipherResponse; - try { - const uploadDataResponse = await this.apiService.postCipherAttachment(cipher.id, request); - response = admin ? uploadDataResponse.cipherMiniResponse : uploadDataResponse.cipherResponse; - await this.fileUploadService.uploadCipherAttachment( - admin, - uploadDataResponse, - encFileName, - encData, - ); - } catch (e) { - if ( - (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) || - (e as ErrorResponse).statusCode === 405 - ) { - response = await this.legacyServerAttachmentFileUpload( - admin, - cipher.id, - encFileName, - encData, - dataEncKey[1], - ); - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } - - const cData = new CipherData( - response, - await this.stateService.getUserId(), - cipher.collectionIds, - ); - if (!admin) { - await this.upsert(cData); - } - return new Cipher(cData); - } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerAttachmentFileUpload( - admin: boolean, - cipherId: string, - encFileName: EncString, - encData: EncArrayBuffer, - key: EncString, - ) { - const fd = new FormData(); - try { - const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); - fd.append("key", key.encryptedString); - fd.append("data", blob, encFileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("key", key.encryptedString); - fd.append( - "data", - Buffer.from(encData.buffer) as any, - { - filepath: encFileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - - let response: CipherResponse; - try { - if (admin) { - response = await this.apiService.postCipherAttachmentAdminLegacy(cipherId, fd); - } else { - response = await this.apiService.postCipherAttachmentLegacy(cipherId, fd); - } - } catch (e) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } - - return response; - } - - async saveCollectionsWithServer(cipher: Cipher): Promise { - const request = new CipherCollectionsRequest(cipher.collectionIds); - await this.apiService.putCipherCollections(cipher.id, request); - const data = cipher.toCipherData(await this.stateService.getUserId()); - await this.upsert(data); - } - - async upsert(cipher: CipherData | CipherData[]): Promise { - let ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - ciphers = {}; - } - - if (cipher instanceof CipherData) { - const c = cipher as CipherData; - ciphers[c.id] = c; - } else { - (cipher as CipherData[]).forEach((c) => { - ciphers[c.id] = c; - }); - } - - await this.replace(ciphers); - } - - async replace(ciphers: { [id: string]: CipherData }): Promise { - await this.clearDecryptedCiphersState(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async clear(userId?: string): Promise { - await this.clearEncryptedCiphersState(userId); - await this.clearCache(userId); - } - - async moveManyWithServer(ids: string[], folderId: string): Promise { - await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - - let ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - ciphers = {}; - } - - ids.forEach((id) => { - // eslint-disable-next-line - if (ciphers.hasOwnProperty(id)) { - ciphers[id].folderId = folderId; - } - }); - - await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async delete(id: string | string[]): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - return; - } - - if (typeof id === "string") { - if (ciphers[id] == null) { - return; - } - delete ciphers[id]; - } else { - (id as string[]).forEach((i) => { - delete ciphers[i]; - }); - } - - await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async deleteWithServer(id: string): Promise { - await this.apiService.deleteCipher(id); - await this.delete(id); - } - - async deleteManyWithServer(ids: string[]): Promise { - await this.apiService.deleteManyCiphers(new CipherBulkDeleteRequest(ids)); - await this.delete(ids); - } - - async deleteAttachment(id: string, attachmentId: string): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); - - // eslint-disable-next-line - if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[id].attachments == null) { - return; - } - - for (let i = 0; i < ciphers[id].attachments.length; i++) { - if (ciphers[id].attachments[i].id === attachmentId) { - ciphers[id].attachments.splice(i, 1); - } - } - - await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { - try { - await this.apiService.deleteCipherAttachment(id, attachmentId); - } catch (e) { - return Promise.reject((e as ErrorResponse).getSingleMessage()); - } - await this.deleteAttachment(id, attachmentId); - } - - sortCiphersByLastUsed(a: CipherView, b: CipherView): number { - const aLastUsed = - a.localData && a.localData.lastUsedDate ? (a.localData.lastUsedDate as number) : null; - const bLastUsed = - b.localData && b.localData.lastUsedDate ? (b.localData.lastUsedDate as number) : null; - - const bothNotNull = aLastUsed != null && bLastUsed != null; - if (bothNotNull && aLastUsed < bLastUsed) { - return 1; - } - if (aLastUsed != null && bLastUsed == null) { - return -1; - } - - if (bothNotNull && aLastUsed > bLastUsed) { - return -1; - } - if (bLastUsed != null && aLastUsed == null) { - return 1; - } - - return 0; - } - - sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number { - const result = this.sortCiphersByLastUsed(a, b); - if (result !== 0) { - return result; - } - - return this.getLocaleSortingFunction()(a, b); - } - - getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number { - return (a, b) => { - let aName = a.name; - let bName = b.name; - - if (aName == null && bName != null) { - return -1; - } - if (aName != null && bName == null) { - return 1; - } - if (aName == null && bName == null) { - return 0; - } - - const result = this.i18nService.collator - ? this.i18nService.collator.compare(aName, bName) - : aName.localeCompare(bName); - - if (result !== 0 || a.type !== CipherType.Login || b.type !== CipherType.Login) { - return result; - } - - if (a.login.username != null) { - aName += a.login.username; - } - - if (b.login.username != null) { - bName += b.login.username; - } - - return this.i18nService.collator - ? this.i18nService.collator.compare(aName, bName) - : aName.localeCompare(bName); - }; - } - - async softDelete(id: string | string[]): Promise { - const ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - return; - } - - const setDeletedDate = (cipherId: string) => { - if (ciphers[cipherId] == null) { - return; - } - ciphers[cipherId].deletedDate = new Date().toISOString(); - }; - - if (typeof id === "string") { - setDeletedDate(id); - } else { - (id as string[]).forEach(setDeletedDate); - } - - await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async softDeleteWithServer(id: string): Promise { - await this.apiService.putDeleteCipher(id); - await this.softDelete(id); - } - - async softDeleteManyWithServer(ids: string[]): Promise { - await this.apiService.putDeleteManyCiphers(new CipherBulkDeleteRequest(ids)); - await this.softDelete(ids); - } - - async restore( - cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], - ) { - const ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers == null) { - return; - } - - const clearDeletedDate = (c: { id: string; revisionDate: string }) => { - if (ciphers[c.id] == null) { - return; - } - ciphers[c.id].deletedDate = null; - ciphers[c.id].revisionDate = c.revisionDate; - }; - - if (cipher.constructor.name === Array.name) { - (cipher as { id: string; revisionDate: string }[]).forEach(clearDeletedDate); - } else { - clearDeletedDate(cipher as { id: string; revisionDate: string }); - } - - await this.clearCache(); - await this.stateService.setEncryptedCiphers(ciphers); - } - - async restoreWithServer(id: string): Promise { - const response = await this.apiService.putRestoreCipher(id); - await this.restore({ id: id, revisionDate: response.revisionDate }); - } - - async restoreManyWithServer(ids: string[]): Promise { - const response = await this.apiService.putRestoreManyCiphers(new CipherBulkRestoreRequest(ids)); - const restores: { id: string; revisionDate: string }[] = []; - for (const cipher of response.data) { - restores.push({ id: cipher.id, revisionDate: cipher.revisionDate }); - } - await this.restore(restores); - } - - // Helpers - - private async shareAttachmentWithServer( - attachmentView: AttachmentView, - cipherId: string, - organizationId: string, - ): Promise { - const attachmentResponse = await this.apiService.nativeFetch( - new Request(attachmentView.url, { cache: "no-store" }), - ); - if (attachmentResponse.status !== 200) { - throw Error("Failed to download attachment: " + attachmentResponse.status.toString()); - } - - const buf = await attachmentResponse.arrayBuffer(); - const decBuf = await this.cryptoService.decryptFromBytes(buf, null); - const key = await this.cryptoService.getOrgKey(organizationId); - const encFileName = await this.cryptoService.encrypt(attachmentView.fileName, key); - - const dataEncKey = await this.cryptoService.makeEncKey(key); - const encData = await this.cryptoService.encryptToBytes(decBuf, dataEncKey[0]); - - const fd = new FormData(); - try { - const blob = new Blob([encData.buffer], { type: "application/octet-stream" }); - fd.append("key", dataEncKey[1].encryptedString); - fd.append("data", blob, encFileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("key", dataEncKey[1].encryptedString); - fd.append( - "data", - Buffer.from(encData.buffer) as any, - { - filepath: encFileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - - try { - await this.apiService.postShareCipherAttachment( - cipherId, - attachmentView.id, - fd, - organizationId, - ); - } catch (e) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } - } - - private async encryptObjProperty( - model: V, - obj: D, - map: any, - key: SymmetricCryptoKey, - ): Promise { - const promises = []; - const self = this; - - for (const prop in map) { - // eslint-disable-next-line - if (!map.hasOwnProperty(prop)) { - continue; - } - - (function (theProp, theObj) { - const p = Promise.resolve() - .then(() => { - const modelProp = (model as any)[map[theProp] || theProp]; - if (modelProp && modelProp !== "") { - return self.cryptoService.encrypt(modelProp, key); - } - return null; - }) - .then((val: EncString) => { - (theObj as any)[theProp] = val; - }); - promises.push(p); - })(prop, obj); - } - - await Promise.all(promises); - } - - private async encryptCipherData(cipher: Cipher, model: CipherView, key: SymmetricCryptoKey) { - switch (cipher.type) { - case CipherType.Login: - cipher.login = new Login(); - cipher.login.passwordRevisionDate = model.login.passwordRevisionDate; - cipher.login.autofillOnPageLoad = model.login.autofillOnPageLoad; - await this.encryptObjProperty( - model.login, - cipher.login, - { - username: null, - password: null, - totp: null, - }, - key, - ); - - if (model.login.uris != null) { - cipher.login.uris = []; - for (let i = 0; i < model.login.uris.length; i++) { - const loginUri = new LoginUri(); - loginUri.match = model.login.uris[i].match; - await this.encryptObjProperty( - model.login.uris[i], - loginUri, - { - uri: null, - }, - key, - ); - cipher.login.uris.push(loginUri); - } - } - return; - case CipherType.SecureNote: - cipher.secureNote = new SecureNote(); - cipher.secureNote.type = model.secureNote.type; - return; - case CipherType.Card: - cipher.card = new Card(); - await this.encryptObjProperty( - model.card, - cipher.card, - { - cardholderName: null, - brand: null, - number: null, - expMonth: null, - expYear: null, - code: null, - }, - key, - ); - return; - case CipherType.Identity: - cipher.identity = new Identity(); - await this.encryptObjProperty( - model.identity, - cipher.identity, - { - title: null, - firstName: null, - middleName: null, - lastName: null, - address1: null, - address2: null, - address3: null, - city: null, - state: null, - postalCode: null, - country: null, - company: null, - email: null, - phone: null, - ssn: null, - username: null, - passportNumber: null, - licenseNumber: null, - }, - key, - ); - return; - default: - throw new Error("Unknown cipher type."); - } - } - - private async getCipherForUrl( - url: string, - lastUsed: boolean, - lastLaunched: boolean, - autofillOnPageLoad: boolean, - ): Promise { - const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url; - - if (!this.sortedCiphersCache.isCached(cacheKey)) { - let ciphers = await this.getAllDecryptedForUrl(url); - if (!ciphers) { - return null; - } - - if (autofillOnPageLoad) { - const autofillOnPageLoadDefault = await this.stateService.getAutoFillOnPageLoadDefault(); - ciphers = ciphers.filter( - (cipher) => - cipher.login.autofillOnPageLoad || - (cipher.login.autofillOnPageLoad == null && autofillOnPageLoadDefault !== false), - ); - if (ciphers.length === 0) { - return null; - } - } - - this.sortedCiphersCache.addCiphers(cacheKey, ciphers); - } - - if (lastLaunched) { - return this.sortedCiphersCache.getLastLaunched(cacheKey); - } else if (lastUsed) { - return this.sortedCiphersCache.getLastUsed(cacheKey); - } else { - return this.sortedCiphersCache.getNext(cacheKey); - } - } - - private async clearEncryptedCiphersState(userId?: string) { - await this.stateService.setEncryptedCiphers(null, { userId: userId }); - } - - private async clearDecryptedCiphersState(userId?: string) { - await this.stateService.setDecryptedCiphers(null, { userId: userId }); - this.clearSortedCiphers(); - } - - private clearSortedCiphers() { - this.sortedCiphersCache.clear(); - } -} diff --git a/jslib/common/src/services/collection.service.ts b/jslib/common/src/services/collection.service.ts deleted file mode 100644 index be8ceb3d2..000000000 --- a/jslib/common/src/services/collection.service.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { CollectionService as CollectionServiceAbstraction } from "../abstractions/collection.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { StateService } from "../abstractions/state.service"; -import { ServiceUtils } from "../misc/serviceUtils"; -import { Utils } from "../misc/utils"; -import { CollectionData } from "../models/data/collectionData"; -import { Collection } from "../models/domain/collection"; -import { TreeNode } from "../models/domain/treeNode"; -import { CollectionView } from "../models/view/collectionView"; - -const NestingDelimiter = "/"; - -export class CollectionService implements CollectionServiceAbstraction { - constructor( - private cryptoService: CryptoService, - private i18nService: I18nService, - private stateService: StateService, - ) {} - - async clearCache(userId?: string): Promise { - await this.stateService.setDecryptedCollections(null, { userId: userId }); - } - - async encrypt(model: CollectionView): Promise { - if (model.organizationId == null) { - throw new Error("Collection has no organization id."); - } - const key = await this.cryptoService.getOrgKey(model.organizationId); - if (key == null) { - throw new Error("No key for this collection's organization."); - } - const collection = new Collection(); - collection.id = model.id; - collection.organizationId = model.organizationId; - collection.readOnly = model.readOnly; - collection.name = await this.cryptoService.encrypt(model.name, key); - return collection; - } - - async decryptMany(collections: Collection[]): Promise { - if (collections == null) { - return []; - } - const decCollections: CollectionView[] = []; - const promises: Promise[] = []; - collections.forEach((collection) => { - promises.push(collection.decrypt().then((c) => decCollections.push(c))); - }); - await Promise.all(promises); - return decCollections.sort(Utils.getSortFunction(this.i18nService, "name")); - } - - async get(id: string): Promise { - const collections = await this.stateService.getEncryptedCollections(); - // eslint-disable-next-line - if (collections == null || !collections.hasOwnProperty(id)) { - return null; - } - - return new Collection(collections[id]); - } - - async getAll(): Promise { - const collections = await this.stateService.getEncryptedCollections(); - const response: Collection[] = []; - for (const id in collections) { - // eslint-disable-next-line - if (collections.hasOwnProperty(id)) { - response.push(new Collection(collections[id])); - } - } - return response; - } - - async getAllDecrypted(): Promise { - let decryptedCollections = await this.stateService.getDecryptedCollections(); - if (decryptedCollections != null) { - return decryptedCollections; - } - - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { - throw new Error("No key."); - } - - const collections = await this.getAll(); - decryptedCollections = await this.decryptMany(collections); - await this.stateService.setDecryptedCollections(decryptedCollections); - return decryptedCollections; - } - - async getAllNested(collections: CollectionView[] = null): Promise[]> { - if (collections == null) { - collections = await this.getAllDecrypted(); - } - const nodes: TreeNode[] = []; - collections.forEach((c) => { - const collectionCopy = new CollectionView(); - collectionCopy.id = c.id; - collectionCopy.organizationId = c.organizationId; - const parts = c.name != null ? c.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, collectionCopy, null, NestingDelimiter); - }); - return nodes; - } - - async getNested(id: string): Promise> { - const collections = await this.getAllNested(); - return ServiceUtils.getTreeNodeObject(collections, id) as TreeNode; - } - - async upsert(collection: CollectionData | CollectionData[]): Promise { - let collections = await this.stateService.getEncryptedCollections(); - if (collections == null) { - collections = {}; - } - - if (collection instanceof CollectionData) { - const c = collection as CollectionData; - collections[c.id] = c; - } else { - (collection as CollectionData[]).forEach((c) => { - collections[c.id] = c; - }); - } - - await this.replace(collections); - } - - async replace(collections: { [id: string]: CollectionData }): Promise { - await this.clearCache(); - await this.stateService.setEncryptedCollections(collections); - } - - async clear(userId?: string): Promise { - await this.clearCache(userId); - await this.stateService.setEncryptedCollections(null, { userId: userId }); - } - - async delete(id: string | string[]): Promise { - const collections = await this.stateService.getEncryptedCollections(); - if (collections == null) { - return; - } - - if (typeof id === "string") { - delete collections[id]; - } else { - (id as string[]).forEach((i) => { - delete collections[i]; - }); - } - - await this.replace(collections); - } -} diff --git a/jslib/common/src/services/event.service.ts b/jslib/common/src/services/event.service.ts deleted file mode 100644 index 00f928b51..000000000 --- a/jslib/common/src/services/event.service.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CipherService } from "../abstractions/cipher.service"; -import { EventService as EventServiceAbstraction } from "../abstractions/event.service"; -import { LogService } from "../abstractions/log.service"; -import { OrganizationService } from "../abstractions/organization.service"; -import { StateService } from "../abstractions/state.service"; -import { EventType } from "../enums/eventType"; -import { EventData } from "../models/data/eventData"; -import { EventRequest } from "../models/request/eventRequest"; - -export class EventService implements EventServiceAbstraction { - private inited = false; - - constructor( - private apiService: ApiService, - private cipherService: CipherService, - private stateService: StateService, - private logService: LogService, - private organizationService: OrganizationService, - ) {} - - init(checkOnInterval: boolean) { - if (this.inited) { - return; - } - - this.inited = true; - if (checkOnInterval) { - this.uploadEvents(); - setInterval(() => this.uploadEvents(), 60 * 1000); // check every 60 seconds - } - } - - async collect( - eventType: EventType, - cipherId: string = null, - uploadImmediately = false, - ): Promise { - const authed = await this.stateService.getIsAuthenticated(); - if (!authed) { - return; - } - const organizations = await this.organizationService.getAll(); - if (organizations == null) { - return; - } - const orgIds = new Set(organizations.filter((o) => o.useEvents).map((o) => o.id)); - if (orgIds.size === 0) { - return; - } - if (cipherId != null) { - const cipher = await this.cipherService.get(cipherId); - if (cipher == null || cipher.organizationId == null || !orgIds.has(cipher.organizationId)) { - return; - } - } - let eventCollection = await this.stateService.getEventCollection(); - if (eventCollection == null) { - eventCollection = []; - } - const event = new EventData(); - event.type = eventType; - event.cipherId = cipherId; - event.date = new Date().toISOString(); - eventCollection.push(event); - await this.stateService.setEventCollection(eventCollection); - if (uploadImmediately) { - await this.uploadEvents(); - } - } - - async uploadEvents(userId?: string): Promise { - const authed = await this.stateService.getIsAuthenticated({ userId: userId }); - if (!authed) { - return; - } - const eventCollection = await this.stateService.getEventCollection({ userId: userId }); - if (eventCollection == null || eventCollection.length === 0) { - return; - } - const request = eventCollection.map((e) => { - const req = new EventRequest(); - req.type = e.type; - req.cipherId = e.cipherId; - req.date = e.date; - return req; - }); - try { - await this.apiService.postEventsCollect(request); - this.clearEvents(userId); - } catch (e) { - this.logService.error(e); - } - } - - async clearEvents(userId?: string): Promise { - await this.stateService.setEventCollection(null, { userId: userId }); - } -} diff --git a/jslib/common/src/services/fileUpload.service.ts b/jslib/common/src/services/fileUpload.service.ts deleted file mode 100644 index aafe41785..000000000 --- a/jslib/common/src/services/fileUpload.service.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "../abstractions/fileUpload.service"; -import { LogService } from "../abstractions/log.service"; -import { FileUploadType } from "../enums/fileUploadType"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - -import { AzureFileUploadService } from "./azureFileUpload.service"; -import { BitwardenFileUploadService } from "./bitwardenFileUpload.service"; - -export class FileUploadService implements FileUploadServiceAbstraction { - private azureFileUploadService: AzureFileUploadService; - private bitwardenFileUploadService: BitwardenFileUploadService; - - constructor( - private logService: LogService, - private apiService: ApiService, - ) { - this.azureFileUploadService = new AzureFileUploadService(logService); - this.bitwardenFileUploadService = new BitwardenFileUploadService(apiService); - } - - async uploadSendFile( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) { - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - fileName.encryptedString, - encryptedFileData, - (fd) => - this.apiService.postSendFile( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id, - fd, - ), - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewSendFileUploadUrl( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id, - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback, - ); - break; - } - default: - throw new Error("Unknown file upload type"); - } - } catch (e) { - await this.apiService.deleteSend(uploadData.sendResponse.id); - throw e; - } - } - - async uploadCipherAttachment( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - encryptedFileName: EncString, - encryptedFileData: EncArrayBuffer, - ) { - const response = admin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - encryptedFileName.encryptedString, - encryptedFileData, - (fd) => this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, fd), - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewAttachmentUploadUrl( - response.id, - uploadData.attachmentId, - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback, - ); - break; - } - default: - throw new Error("Unknown file upload type."); - } - } catch (e) { - if (admin) { - await this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); - } else { - await this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); - } - throw e; - } - } -} diff --git a/jslib/common/src/services/folder.service.ts b/jslib/common/src/services/folder.service.ts deleted file mode 100644 index 7c0b2a08e..000000000 --- a/jslib/common/src/services/folder.service.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CipherService } from "../abstractions/cipher.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { FolderService as FolderServiceAbstraction } from "../abstractions/folder.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { StateService } from "../abstractions/state.service"; -import { ServiceUtils } from "../misc/serviceUtils"; -import { Utils } from "../misc/utils"; -import { CipherData } from "../models/data/cipherData"; -import { FolderData } from "../models/data/folderData"; -import { Folder } from "../models/domain/folder"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { TreeNode } from "../models/domain/treeNode"; -import { FolderRequest } from "../models/request/folderRequest"; -import { FolderResponse } from "../models/response/folderResponse"; -import { FolderView } from "../models/view/folderView"; - -const NestingDelimiter = "/"; - -export class FolderService implements FolderServiceAbstraction { - constructor( - private cryptoService: CryptoService, - private apiService: ApiService, - private i18nService: I18nService, - private cipherService: CipherService, - private stateService: StateService, - ) {} - - async clearCache(userId?: string): Promise { - await this.stateService.setDecryptedFolders(null, { userId: userId }); - } - - async encrypt(model: FolderView, key?: SymmetricCryptoKey): Promise { - const folder = new Folder(); - folder.id = model.id; - folder.name = await this.cryptoService.encrypt(model.name, key); - return folder; - } - - async get(id: string): Promise { - const folders = await this.stateService.getEncryptedFolders(); - // eslint-disable-next-line - if (folders == null || !folders.hasOwnProperty(id)) { - return null; - } - - return new Folder(folders[id]); - } - - async getAll(): Promise { - const folders = await this.stateService.getEncryptedFolders(); - const response: Folder[] = []; - for (const id in folders) { - // eslint-disable-next-line - if (folders.hasOwnProperty(id)) { - response.push(new Folder(folders[id])); - } - } - return response; - } - - async getAllDecrypted(): Promise { - const decryptedFolders = await this.stateService.getDecryptedFolders(); - if (decryptedFolders != null) { - return decryptedFolders; - } - - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { - throw new Error("No key."); - } - - const decFolders: FolderView[] = []; - const promises: Promise[] = []; - const folders = await this.getAll(); - folders.forEach((folder) => { - promises.push(folder.decrypt().then((f) => decFolders.push(f))); - }); - - await Promise.all(promises); - decFolders.sort(Utils.getSortFunction(this.i18nService, "name")); - - const noneFolder = new FolderView(); - noneFolder.name = this.i18nService.t("noneFolder"); - decFolders.push(noneFolder); - - await this.stateService.setDecryptedFolders(decFolders); - return decFolders; - } - - async getAllNested(): Promise[]> { - const folders = await this.getAllDecrypted(); - const nodes: TreeNode[] = []; - folders.forEach((f) => { - const folderCopy = new FolderView(); - folderCopy.id = f.id; - folderCopy.revisionDate = f.revisionDate; - const parts = f.name != null ? f.name.replace(/^\/+|\/+$/g, "").split(NestingDelimiter) : []; - ServiceUtils.nestedTraverse(nodes, 0, parts, folderCopy, null, NestingDelimiter); - }); - return nodes; - } - - async getNested(id: string): Promise> { - const folders = await this.getAllNested(); - return ServiceUtils.getTreeNodeObject(folders, id) as TreeNode; - } - - async saveWithServer(folder: Folder): Promise { - const request = new FolderRequest(folder); - - let response: FolderResponse; - if (folder.id == null) { - response = await this.apiService.postFolder(request); - folder.id = response.id; - } else { - response = await this.apiService.putFolder(folder.id, request); - } - - const userId = await this.stateService.getUserId(); - const data = new FolderData(response, userId); - await this.upsert(data); - } - - async upsert(folder: FolderData | FolderData[]): Promise { - let folders = await this.stateService.getEncryptedFolders(); - if (folders == null) { - folders = {}; - } - - if (folder instanceof FolderData) { - const f = folder as FolderData; - folders[f.id] = f; - } else { - (folder as FolderData[]).forEach((f) => { - folders[f.id] = f; - }); - } - - await this.stateService.setDecryptedFolders(null); - await this.stateService.setEncryptedFolders(folders); - } - - async replace(folders: { [id: string]: FolderData }): Promise { - await this.stateService.setDecryptedFolders(null); - await this.stateService.setEncryptedFolders(folders); - } - - async clear(userId?: string): Promise { - await this.stateService.setDecryptedFolders(null, { userId: userId }); - await this.stateService.setEncryptedFolders(null, { userId: userId }); - } - - async delete(id: string | string[]): Promise { - const folders = await this.stateService.getEncryptedFolders(); - if (folders == null) { - return; - } - - if (typeof id === "string") { - if (folders[id] == null) { - return; - } - delete folders[id]; - } else { - (id as string[]).forEach((i) => { - delete folders[i]; - }); - } - - await this.stateService.setDecryptedFolders(null); - await this.stateService.setEncryptedFolders(folders); - - // Items in a deleted folder are re-assigned to "No Folder" - const ciphers = await this.stateService.getEncryptedCiphers(); - if (ciphers != null) { - const updates: CipherData[] = []; - for (const cId in ciphers) { - if (ciphers[cId].folderId === id) { - ciphers[cId].folderId = null; - updates.push(ciphers[cId]); - } - } - if (updates.length > 0) { - this.cipherService.upsert(updates); - } - } - } - - async deleteWithServer(id: string): Promise { - await this.apiService.deleteFolder(id); - await this.delete(id); - } -} diff --git a/jslib/common/src/services/notifications.service.ts b/jslib/common/src/services/notifications.service.ts deleted file mode 100644 index 9808f8fd1..000000000 --- a/jslib/common/src/services/notifications.service.ts +++ /dev/null @@ -1,231 +0,0 @@ -import * as signalR from "@microsoft/signalr"; -import * as signalRMsgPack from "@microsoft/signalr-protocol-msgpack"; - -import { ApiService } from "../abstractions/api.service"; -import { AppIdService } from "../abstractions/appId.service"; -import { EnvironmentService } from "../abstractions/environment.service"; -import { LogService } from "../abstractions/log.service"; -import { NotificationsService as NotificationsServiceAbstraction } from "../abstractions/notifications.service"; -import { StateService } from "../abstractions/state.service"; -import { SyncService } from "../abstractions/sync.service"; -import { VaultTimeoutService } from "../abstractions/vaultTimeout.service"; -import { NotificationType } from "../enums/notificationType"; -import { - NotificationResponse, - SyncCipherNotification, - SyncFolderNotification, - SyncSendNotification, -} from "../models/response/notificationResponse"; - -export class NotificationsService implements NotificationsServiceAbstraction { - private signalrConnection: signalR.HubConnection; - private url: string; - private connected = false; - private inited = false; - private inactive = false; - private reconnectTimer: any = null; - - constructor( - private syncService: SyncService, - private appIdService: AppIdService, - private apiService: ApiService, - private vaultTimeoutService: VaultTimeoutService, - private environmentService: EnvironmentService, - private logoutCallback: () => Promise, - private logService: LogService, - private stateService: StateService, - ) { - this.environmentService.urls.subscribe(() => { - if (!this.inited) { - return; - } - - this.init(); - }); - } - - async init(): Promise { - this.inited = false; - this.url = this.environmentService.getNotificationsUrl(); - - // Set notifications server URL to `https://-` to effectively disable communication - // with the notifications server from the client app - if (this.url === "https://-") { - return; - } - - if (this.signalrConnection != null) { - this.signalrConnection.off("ReceiveMessage"); - this.signalrConnection.off("Heartbeat"); - await this.signalrConnection.stop(); - this.connected = false; - this.signalrConnection = null; - } - - this.signalrConnection = new signalR.HubConnectionBuilder() - .withUrl(this.url + "/hub", { - accessTokenFactory: () => this.apiService.getActiveBearerToken(), - skipNegotiation: true, - transport: signalR.HttpTransportType.WebSockets, - }) - .withHubProtocol(new signalRMsgPack.MessagePackHubProtocol() as signalR.IHubProtocol) - // .configureLogging(signalR.LogLevel.Trace) - .build(); - - this.signalrConnection.on("ReceiveMessage", (data: any) => - this.processNotification(new NotificationResponse(data)), - ); - // eslint-disable-next-line - this.signalrConnection.on("Heartbeat", (data: any) => { - /*console.log('Heartbeat!');*/ - }); - this.signalrConnection.onclose(() => { - this.connected = false; - this.reconnect(true); - }); - this.inited = true; - if (await this.isAuthedAndUnlocked()) { - await this.reconnect(false); - } - } - - async updateConnection(sync = false): Promise { - if (!this.inited) { - return; - } - try { - if (await this.isAuthedAndUnlocked()) { - await this.reconnect(sync); - } else { - await this.signalrConnection.stop(); - } - } catch (e) { - this.logService.error(e.toString()); - } - } - - async reconnectFromActivity(): Promise { - this.inactive = false; - if (this.inited && !this.connected) { - await this.reconnect(true); - } - } - - async disconnectFromInactivity(): Promise { - this.inactive = true; - if (this.inited && this.connected) { - await this.signalrConnection.stop(); - } - } - - private async processNotification(notification: NotificationResponse) { - const appId = await this.appIdService.getAppId(); - if (notification == null || notification.contextId === appId) { - return; - } - - const isAuthenticated = await this.stateService.getIsAuthenticated(); - const payloadUserId = notification.payload.userId || notification.payload.UserId; - const myUserId = await this.stateService.getUserId(); - if (isAuthenticated && payloadUserId != null && payloadUserId !== myUserId) { - return; - } - - switch (notification.type) { - case NotificationType.SyncCipherCreate: - case NotificationType.SyncCipherUpdate: - await this.syncService.syncUpsertCipher( - notification.payload as SyncCipherNotification, - notification.type === NotificationType.SyncCipherUpdate, - ); - break; - case NotificationType.SyncCipherDelete: - case NotificationType.SyncLoginDelete: - await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification); - break; - case NotificationType.SyncFolderCreate: - case NotificationType.SyncFolderUpdate: - await this.syncService.syncUpsertFolder( - notification.payload as SyncFolderNotification, - notification.type === NotificationType.SyncFolderUpdate, - ); - break; - case NotificationType.SyncFolderDelete: - await this.syncService.syncDeleteFolder(notification.payload as SyncFolderNotification); - break; - case NotificationType.SyncVault: - case NotificationType.SyncCiphers: - case NotificationType.SyncSettings: - if (isAuthenticated) { - await this.syncService.fullSync(false); - } - break; - case NotificationType.SyncOrgKeys: - if (isAuthenticated) { - await this.syncService.fullSync(true); - // Stop so a reconnect can be made - await this.signalrConnection.stop(); - } - break; - case NotificationType.LogOut: - if (isAuthenticated) { - this.logoutCallback(); - } - break; - case NotificationType.SyncSendCreate: - case NotificationType.SyncSendUpdate: - await this.syncService.syncUpsertSend( - notification.payload as SyncSendNotification, - notification.type === NotificationType.SyncSendUpdate, - ); - break; - case NotificationType.SyncSendDelete: - await this.syncService.syncDeleteSend(notification.payload as SyncSendNotification); - break; - default: - break; - } - } - - private async reconnect(sync: boolean) { - if (this.reconnectTimer != null) { - clearTimeout(this.reconnectTimer); - this.reconnectTimer = null; - } - if (this.connected || !this.inited || this.inactive) { - return; - } - const authedAndUnlocked = await this.isAuthedAndUnlocked(); - if (!authedAndUnlocked) { - return; - } - - try { - await this.signalrConnection.start(); - this.connected = true; - if (sync) { - await this.syncService.fullSync(false); - } - } catch (e) { - this.logService.error(e); - } - - if (!this.connected) { - this.reconnectTimer = setTimeout(() => this.reconnect(sync), this.random(120000, 300000)); - } - } - - private async isAuthedAndUnlocked() { - if (await this.stateService.getIsAuthenticated()) { - const locked = await this.vaultTimeoutService.isLocked(); - return !locked; - } - return false; - } - - private random(min: number, max: number) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1)) + min; - } -} diff --git a/jslib/common/src/services/provider.service.ts b/jslib/common/src/services/provider.service.ts deleted file mode 100644 index 53f095424..000000000 --- a/jslib/common/src/services/provider.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ProviderService as ProviderServiceAbstraction } from "../abstractions/provider.service"; -import { StateService } from "../abstractions/state.service"; -import { ProviderData } from "../models/data/providerData"; -import { Provider } from "../models/domain/provider"; - -export class ProviderService implements ProviderServiceAbstraction { - constructor(private stateService: StateService) {} - - async get(id: string): Promise { - const providers = await this.stateService.getProviders(); - // eslint-disable-next-line - if (providers == null || !providers.hasOwnProperty(id)) { - return null; - } - - return new Provider(providers[id]); - } - - async getAll(): Promise { - const providers = await this.stateService.getProviders(); - const response: Provider[] = []; - for (const id in providers) { - // eslint-disable-next-line - if (providers.hasOwnProperty(id)) { - response.push(new Provider(providers[id])); - } - } - return response; - } - - async save(providers: { [id: string]: ProviderData }) { - await this.stateService.setProviders(providers); - } -} diff --git a/jslib/common/src/services/search.service.ts b/jslib/common/src/services/search.service.ts deleted file mode 100644 index a51346143..000000000 --- a/jslib/common/src/services/search.service.ts +++ /dev/null @@ -1,284 +0,0 @@ -import * as lunr from "lunr"; - -import { CipherService } from "../abstractions/cipher.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { LogService } from "../abstractions/log.service"; -import { SearchService as SearchServiceAbstraction } from "../abstractions/search.service"; -import { CipherType } from "../enums/cipherType"; -import { FieldType } from "../enums/fieldType"; -import { UriMatchType } from "../enums/uriMatchType"; -import { CipherView } from "../models/view/cipherView"; -import { SendView } from "../models/view/sendView"; - -export class SearchService implements SearchServiceAbstraction { - indexedEntityId?: string = null; - private indexing = false; - private index: lunr.Index = null; - private searchableMinLength = 2; - - constructor( - private cipherService: CipherService, - private logService: LogService, - private i18nService: I18nService, - ) { - if (["zh-CN", "zh-TW"].indexOf(i18nService.locale) !== -1) { - this.searchableMinLength = 1; - } - } - - clearIndex(): void { - this.indexedEntityId = null; - this.index = null; - } - - isSearchable(query: string): boolean { - const notSearchable = - query == null || - (this.index == null && query.length < this.searchableMinLength) || - (this.index != null && query.length < this.searchableMinLength && query.indexOf(">") !== 0); - return !notSearchable; - } - - async indexCiphers(indexedEntityId?: string, ciphers?: CipherView[]): Promise { - if (this.indexing) { - return; - } - - this.logService.time("search indexing"); - this.indexing = true; - this.indexedEntityId = indexedEntityId; - this.index = null; - const builder = new lunr.Builder(); - builder.ref("id"); - builder.field("shortid", { boost: 100, extractor: (c: CipherView) => c.id.substr(0, 8) }); - builder.field("name", { boost: 10 }); - builder.field("subtitle", { - boost: 5, - extractor: (c: CipherView) => { - if (c.subTitle != null && c.type === CipherType.Card) { - return c.subTitle.replace(/\*/g, ""); - } - return c.subTitle; - }, - }); - builder.field("notes"); - builder.field("login.username", { - extractor: (c: CipherView) => - c.type === CipherType.Login && c.login != null ? c.login.username : null, - }); - builder.field("login.uris", { boost: 2, extractor: (c: CipherView) => this.uriExtractor(c) }); - builder.field("fields", { extractor: (c: CipherView) => this.fieldExtractor(c, false) }); - builder.field("fields_joined", { extractor: (c: CipherView) => this.fieldExtractor(c, true) }); - builder.field("attachments", { - extractor: (c: CipherView) => this.attachmentExtractor(c, false), - }); - builder.field("attachments_joined", { - extractor: (c: CipherView) => this.attachmentExtractor(c, true), - }); - builder.field("organizationid", { extractor: (c: CipherView) => c.organizationId }); - ciphers = ciphers || (await this.cipherService.getAllDecrypted()); - ciphers.forEach((c) => builder.add(c)); - this.index = builder.build(); - - this.indexing = false; - - this.logService.timeEnd("search indexing"); - } - - async searchCiphers( - query: string, - filter: ((cipher: CipherView) => boolean) | ((cipher: CipherView) => boolean)[] = null, - ciphers: CipherView[] = null, - ): Promise { - const results: CipherView[] = []; - if (query != null) { - query = query.trim().toLowerCase(); - } - if (query === "") { - query = null; - } - - if (ciphers == null) { - ciphers = await this.cipherService.getAllDecrypted(); - } - - if (filter != null && Array.isArray(filter) && filter.length > 0) { - ciphers = ciphers.filter((c) => filter.every((f) => f == null || f(c))); - } else if (filter != null) { - ciphers = ciphers.filter(filter as (cipher: CipherView) => boolean); - } - - if (!this.isSearchable(query)) { - return ciphers; - } - - if (this.indexing) { - await new Promise((r) => setTimeout(r, 250)); - if (this.indexing) { - await new Promise((r) => setTimeout(r, 500)); - } - } - - const index = this.getIndexForSearch(); - if (index == null) { - // Fall back to basic search if index is not available - return this.searchCiphersBasic(ciphers, query); - } - - const ciphersMap = new Map(); - ciphers.forEach((c) => ciphersMap.set(c.id, c)); - - let searchResults: lunr.Index.Result[] = null; - const isQueryString = query != null && query.length > 1 && query.indexOf(">") === 0; - if (isQueryString) { - try { - searchResults = index.search(query.substr(1).trim()); - } catch (e) { - this.logService.error(e); - } - } else { - const soWild = lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING; - searchResults = index.query((q) => { - lunr.tokenizer(query).forEach((token) => { - const t = token.toString(); - q.term(t, { fields: ["name"], wildcard: soWild }); - q.term(t, { fields: ["subtitle"], wildcard: soWild }); - q.term(t, { fields: ["login.uris"], wildcard: soWild }); - q.term(t, {}); - }); - }); - } - - if (searchResults != null) { - searchResults.forEach((r) => { - if (ciphersMap.has(r.ref)) { - results.push(ciphersMap.get(r.ref)); - } - }); - } - return results; - } - - searchCiphersBasic(ciphers: CipherView[], query: string, deleted = false) { - query = query.trim().toLowerCase(); - return ciphers.filter((c) => { - if (deleted !== c.isDeleted) { - return false; - } - if (c.name != null && c.name.toLowerCase().indexOf(query) > -1) { - return true; - } - if (query.length >= 8 && c.id.startsWith(query)) { - return true; - } - if (c.subTitle != null && c.subTitle.toLowerCase().indexOf(query) > -1) { - return true; - } - if (c.login && c.login.uri != null && c.login.uri.toLowerCase().indexOf(query) > -1) { - return true; - } - return false; - }); - } - - searchSends(sends: SendView[], query: string) { - query = query.trim().toLocaleLowerCase(); - - return sends.filter((s) => { - if (s.name != null && s.name.toLowerCase().indexOf(query) > -1) { - return true; - } - if ( - query.length >= 8 && - (s.id.startsWith(query) || - s.accessId.toLocaleLowerCase().startsWith(query) || - (s.file?.id != null && s.file.id.startsWith(query))) - ) { - return true; - } - if (s.notes != null && s.notes.toLowerCase().indexOf(query) > -1) { - return true; - } - if (s.text?.text != null && s.text.text.toLowerCase().indexOf(query) > -1) { - return true; - } - if (s.file?.fileName != null && s.file.fileName.toLowerCase().indexOf(query) > -1) { - return true; - } - }); - } - - getIndexForSearch(): lunr.Index { - return this.index; - } - - private fieldExtractor(c: CipherView, joined: boolean) { - if (!c.hasFields) { - return null; - } - let fields: string[] = []; - c.fields.forEach((f) => { - if (f.name != null) { - fields.push(f.name); - } - if (f.type === FieldType.Text && f.value != null) { - fields.push(f.value); - } - }); - fields = fields.filter((f) => f.trim() !== ""); - if (fields.length === 0) { - return null; - } - return joined ? fields.join(" ") : fields; - } - - private attachmentExtractor(c: CipherView, joined: boolean) { - if (!c.hasAttachments) { - return null; - } - let attachments: string[] = []; - c.attachments.forEach((a) => { - if (a != null && a.fileName != null) { - if (joined && a.fileName.indexOf(".") > -1) { - attachments.push(a.fileName.substr(0, a.fileName.lastIndexOf("."))); - } else { - attachments.push(a.fileName); - } - } - }); - attachments = attachments.filter((f) => f.trim() !== ""); - if (attachments.length === 0) { - return null; - } - return joined ? attachments.join(" ") : attachments; - } - - private uriExtractor(c: CipherView) { - if (c.type !== CipherType.Login || c.login == null || !c.login.hasUris) { - return null; - } - const uris: string[] = []; - c.login.uris.forEach((u) => { - if (u.uri == null || u.uri === "") { - return; - } - if (u.hostname != null) { - uris.push(u.hostname); - return; - } - let uri = u.uri; - if (u.match !== UriMatchType.RegularExpression) { - const protocolIndex = uri.indexOf("://"); - if (protocolIndex > -1) { - uri = uri.substr(protocolIndex + 3); - } - const queryIndex = uri.search(/\?|&|#/); - if (queryIndex > -1) { - uri = uri.substring(0, queryIndex); - } - } - uris.push(uri); - }); - return uris.length > 0 ? uris : null; - } -} diff --git a/jslib/common/src/services/send.service.ts b/jslib/common/src/services/send.service.ts deleted file mode 100644 index 73584a9bf..000000000 --- a/jslib/common/src/services/send.service.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { FileUploadService } from "../abstractions/fileUpload.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { SendService as SendServiceAbstraction } from "../abstractions/send.service"; -import { StateService } from "../abstractions/state.service"; -import { SEND_KDF_ITERATIONS } from "../enums/kdfType"; -import { SendType } from "../enums/sendType"; -import { Utils } from "../misc/utils"; -import { SendData } from "../models/data/sendData"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { Send } from "../models/domain/send"; -import { SendFile } from "../models/domain/sendFile"; -import { SendText } from "../models/domain/sendText"; -import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey"; -import { SendRequest } from "../models/request/sendRequest"; -import { ErrorResponse } from "../models/response/errorResponse"; -import { SendResponse } from "../models/response/sendResponse"; -import { SendView } from "../models/view/sendView"; - -export class SendService implements SendServiceAbstraction { - constructor( - private cryptoService: CryptoService, - private apiService: ApiService, - private fileUploadService: FileUploadService, - private i18nService: I18nService, - private cryptoFunctionService: CryptoFunctionService, - private stateService: StateService, - ) {} - - async clearCache(): Promise { - await this.stateService.setDecryptedSends(null); - } - - async encrypt( - model: SendView, - file: File | ArrayBuffer, - password: string, - key?: SymmetricCryptoKey, - ): Promise<[Send, EncArrayBuffer]> { - let fileData: EncArrayBuffer = null; - const send = new Send(); - send.id = model.id; - send.type = model.type; - send.disabled = model.disabled; - send.hideEmail = model.hideEmail; - send.maxAccessCount = model.maxAccessCount; - if (model.key == null) { - model.key = await this.cryptoFunctionService.randomBytes(16); - model.cryptoKey = await this.cryptoService.makeSendKey(model.key); - } - if (password != null) { - const passwordHash = await this.cryptoFunctionService.pbkdf2( - password, - model.key, - "sha256", - SEND_KDF_ITERATIONS, - ); - send.password = Utils.fromBufferToB64(passwordHash); - } - send.key = await this.cryptoService.encrypt(model.key, key); - send.name = await this.cryptoService.encrypt(model.name, model.cryptoKey); - send.notes = await this.cryptoService.encrypt(model.notes, model.cryptoKey); - if (send.type === SendType.Text) { - send.text = new SendText(); - send.text.text = await this.cryptoService.encrypt(model.text.text, model.cryptoKey); - send.text.hidden = model.text.hidden; - } else if (send.type === SendType.File) { - send.file = new SendFile(); - if (file != null) { - if (file instanceof ArrayBuffer) { - const [name, data] = await this.encryptFileData( - model.file.fileName, - file, - model.cryptoKey, - ); - send.file.fileName = name; - fileData = data; - } else { - fileData = await this.parseFile(send, file, model.cryptoKey); - } - } - } - - return [send, fileData]; - } - - async get(id: string): Promise { - const sends = await this.stateService.getEncryptedSends(); - // eslint-disable-next-line - if (sends == null || !sends.hasOwnProperty(id)) { - return null; - } - - return new Send(sends[id]); - } - - async getAll(): Promise { - const sends = await this.stateService.getEncryptedSends(); - const response: Send[] = []; - for (const id in sends) { - // eslint-disable-next-line - if (sends.hasOwnProperty(id)) { - response.push(new Send(sends[id])); - } - } - return response; - } - - async getAllDecrypted(): Promise { - let decSends = await this.stateService.getDecryptedSends(); - if (decSends != null) { - return decSends; - } - - decSends = []; - const hasKey = await this.cryptoService.hasKey(); - if (!hasKey) { - throw new Error("No key."); - } - - const promises: Promise[] = []; - const sends = await this.getAll(); - sends.forEach((send) => { - promises.push(send.decrypt().then((f) => decSends.push(f))); - }); - - await Promise.all(promises); - decSends.sort(Utils.getSortFunction(this.i18nService, "name")); - - await this.stateService.setDecryptedSends(decSends); - return decSends; - } - - async saveWithServer(sendData: [Send, EncArrayBuffer]): Promise { - const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength); - let response: SendResponse; - if (sendData[0].id == null) { - if (sendData[0].type === SendType.Text) { - response = await this.apiService.postSend(request); - } else { - try { - const uploadDataResponse = await this.apiService.postFileTypeSend(request); - response = uploadDataResponse.sendResponse; - - await this.fileUploadService.uploadSendFile( - uploadDataResponse, - sendData[0].file.fileName, - sendData[1], - ); - } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { - throw new Error((e as ErrorResponse).getSingleMessage()); - } else { - throw e; - } - } - } - sendData[0].id = response.id; - sendData[0].accessId = response.accessId; - } else { - response = await this.apiService.putSend(sendData[0].id, request); - } - - const userId = await this.stateService.getUserId(); - const data = new SendData(response, userId); - await this.upsert(data); - } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.apiService.postSendFileLegacy(fd); - } - - async upsert(send: SendData | SendData[]): Promise { - let sends = await this.stateService.getEncryptedSends(); - if (sends == null) { - sends = {}; - } - - if (send instanceof SendData) { - const s = send as SendData; - sends[s.id] = s; - } else { - (send as SendData[]).forEach((s) => { - sends[s.id] = s; - }); - } - - await this.replace(sends); - } - - async replace(sends: { [id: string]: SendData }): Promise { - await this.stateService.setDecryptedSends(null); - await this.stateService.setEncryptedSends(sends); - } - - async clear(): Promise { - await this.stateService.setDecryptedSends(null); - await this.stateService.setEncryptedSends(null); - } - - async delete(id: string | string[]): Promise { - const sends = await this.stateService.getEncryptedSends(); - if (sends == null) { - return; - } - - if (typeof id === "string") { - if (sends[id] == null) { - return; - } - delete sends[id]; - } else { - (id as string[]).forEach((i) => { - delete sends[i]; - }); - } - - await this.replace(sends); - } - - async deleteWithServer(id: string): Promise { - await this.apiService.deleteSend(id); - await this.delete(id); - } - - async removePasswordWithServer(id: string): Promise { - const response = await this.apiService.putSendRemovePassword(id); - const userId = await this.stateService.getUserId(); - const data = new SendData(response, userId); - await this.upsert(data); - } - - private parseFile(send: Send, file: File, key: SymmetricCryptoKey): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsArrayBuffer(file); - reader.onload = async (evt) => { - try { - const [name, data] = await this.encryptFileData( - file.name, - evt.target.result as ArrayBuffer, - key, - ); - send.file.fileName = name; - resolve(data); - } catch (e) { - reject(e); - } - }; - reader.onerror = () => { - reject("Error reading file."); - }; - }); - } - - private async encryptFileData( - fileName: string, - data: ArrayBuffer, - key: SymmetricCryptoKey, - ): Promise<[EncString, EncArrayBuffer]> { - const encFileName = await this.cryptoService.encrypt(fileName, key); - const encFileData = await this.cryptoService.encryptToBytes(data, key); - return [encFileName, encFileData]; - } -} diff --git a/jslib/common/src/services/settings.service.ts b/jslib/common/src/services/settings.service.ts deleted file mode 100644 index 7f5131b0f..000000000 --- a/jslib/common/src/services/settings.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { SettingsService as SettingsServiceAbstraction } from "../abstractions/settings.service"; -import { StateService } from "../abstractions/state.service"; - -const Keys = { - settingsPrefix: "settings_", - equivalentDomains: "equivalentDomains", -}; - -export class SettingsService implements SettingsServiceAbstraction { - constructor(private stateService: StateService) {} - - async clearCache(): Promise { - await this.stateService.setSettings(null); - } - - getEquivalentDomains(): Promise { - return this.getSettingsKey(Keys.equivalentDomains); - } - - async setEquivalentDomains(equivalentDomains: string[][]): Promise { - await this.setSettingsKey(Keys.equivalentDomains, equivalentDomains); - } - - async clear(userId?: string): Promise { - await this.stateService.setSettings(null, { userId: userId }); - } - - // Helpers - - private async getSettings(): Promise { - const settings = await this.stateService.getSettings(); - if (settings == null) { - // eslint-disable-next-line - const userId = await this.stateService.getUserId(); - } - return settings; - } - - private async getSettingsKey(key: string): Promise { - const settings = await this.getSettings(); - if (settings != null && settings[key]) { - return settings[key]; - } - return null; - } - - private async setSettingsKey(key: string, value: any): Promise { - let settings = await this.getSettings(); - if (!settings) { - settings = {}; - } - - settings[key] = value; - await this.stateService.setSettings(settings); - } -} diff --git a/jslib/common/src/services/sync.service.ts b/jslib/common/src/services/sync.service.ts deleted file mode 100644 index f31412663..000000000 --- a/jslib/common/src/services/sync.service.ts +++ /dev/null @@ -1,400 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CipherService } from "../abstractions/cipher.service"; -import { CollectionService } from "../abstractions/collection.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { FolderService } from "../abstractions/folder.service"; -import { KeyConnectorService } from "../abstractions/keyConnector.service"; -import { LogService } from "../abstractions/log.service"; -import { MessagingService } from "../abstractions/messaging.service"; -import { OrganizationService } from "../abstractions/organization.service"; -import { PolicyService } from "../abstractions/policy.service"; -import { ProviderService } from "../abstractions/provider.service"; -import { SendService } from "../abstractions/send.service"; -import { SettingsService } from "../abstractions/settings.service"; -import { StateService } from "../abstractions/state.service"; -import { SyncService as SyncServiceAbstraction } from "../abstractions/sync.service"; -import { sequentialize } from "../misc/sequentialize"; -import { CipherData } from "../models/data/cipherData"; -import { CollectionData } from "../models/data/collectionData"; -import { FolderData } from "../models/data/folderData"; -import { OrganizationData } from "../models/data/organizationData"; -import { PolicyData } from "../models/data/policyData"; -import { ProviderData } from "../models/data/providerData"; -import { SendData } from "../models/data/sendData"; -import { CipherResponse } from "../models/response/cipherResponse"; -import { CollectionDetailsResponse } from "../models/response/collectionResponse"; -import { DomainsResponse } from "../models/response/domainsResponse"; -import { FolderResponse } from "../models/response/folderResponse"; -import { - SyncCipherNotification, - SyncFolderNotification, - SyncSendNotification, -} from "../models/response/notificationResponse"; -import { PolicyResponse } from "../models/response/policyResponse"; -import { ProfileResponse } from "../models/response/profileResponse"; -import { SendResponse } from "../models/response/sendResponse"; - -export class SyncService implements SyncServiceAbstraction { - syncInProgress = false; - - constructor( - private apiService: ApiService, - private settingsService: SettingsService, - private folderService: FolderService, - private cipherService: CipherService, - private cryptoService: CryptoService, - private collectionService: CollectionService, - private messagingService: MessagingService, - private policyService: PolicyService, - private sendService: SendService, - private logService: LogService, - private keyConnectorService: KeyConnectorService, - private stateService: StateService, - private organizationService: OrganizationService, - private providerService: ProviderService, - private logoutCallback: (expired: boolean) => Promise, - ) {} - - async getLastSync(): Promise { - if ((await this.stateService.getUserId()) == null) { - return null; - } - - const lastSync = await this.stateService.getLastSync(); - if (lastSync) { - return new Date(lastSync); - } - - return null; - } - - async setLastSync(date: Date, userId?: string): Promise { - await this.stateService.setLastSync(date.toJSON(), { userId: userId }); - } - - @sequentialize(() => "fullSync") - async fullSync(forceSync: boolean, allowThrowOnError = false): Promise { - this.syncStarted(); - const isAuthenticated = await this.stateService.getIsAuthenticated(); - if (!isAuthenticated) { - return this.syncCompleted(false); - } - - const now = new Date(); - let needsSync = false; - try { - needsSync = await this.needsSyncing(forceSync); - } catch (e) { - if (allowThrowOnError) { - throw e; - } - } - - if (!needsSync) { - await this.setLastSync(now); - return this.syncCompleted(false); - } - - const userId = await this.stateService.getUserId(); - try { - await this.apiService.refreshIdentityToken(); - const response = await this.apiService.getSync(); - - await this.syncProfile(response.profile); - await this.syncFolders(userId, response.folders); - await this.syncCollections(response.collections); - await this.syncCiphers(userId, response.ciphers); - await this.syncSends(userId, response.sends); - await this.syncSettings(response.domains); - await this.syncPolicies(response.policies); - - await this.setLastSync(now); - return this.syncCompleted(true); - } catch (e) { - if (allowThrowOnError) { - throw e; - } else { - return this.syncCompleted(false); - } - } - } - - async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - try { - const localFolder = await this.folderService.get(notification.id); - if ( - (!isEdit && localFolder == null) || - (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate) - ) { - const remoteFolder = await this.apiService.getFolder(notification.id); - if (remoteFolder != null) { - const userId = await this.stateService.getUserId(); - await this.folderService.upsert(new FolderData(remoteFolder, userId)); - this.messagingService.send("syncedUpsertedFolder", { folderId: notification.id }); - return this.syncCompleted(true); - } - } - } catch (e) { - this.logService.error(e); - } - } - return this.syncCompleted(false); - } - - async syncDeleteFolder(notification: SyncFolderNotification): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.folderService.delete(notification.id); - this.messagingService.send("syncedDeletedFolder", { folderId: notification.id }); - this.syncCompleted(true); - return true; - } - return this.syncCompleted(false); - } - - async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - try { - let shouldUpdate = true; - const localCipher = await this.cipherService.get(notification.id); - if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) { - shouldUpdate = false; - } - - let checkCollections = false; - if (shouldUpdate) { - if (isEdit) { - shouldUpdate = localCipher != null; - checkCollections = true; - } else { - if (notification.collectionIds == null || notification.organizationId == null) { - shouldUpdate = localCipher == null; - } else { - shouldUpdate = false; - checkCollections = true; - } - } - } - - if ( - !shouldUpdate && - checkCollections && - notification.organizationId != null && - notification.collectionIds != null && - notification.collectionIds.length > 0 - ) { - const collections = await this.collectionService.getAll(); - if (collections != null) { - for (let i = 0; i < collections.length; i++) { - if (notification.collectionIds.indexOf(collections[i].id) > -1) { - shouldUpdate = true; - break; - } - } - } - } - - if (shouldUpdate) { - const remoteCipher = await this.apiService.getCipher(notification.id); - if (remoteCipher != null) { - const userId = await this.stateService.getUserId(); - await this.cipherService.upsert(new CipherData(remoteCipher, userId)); - this.messagingService.send("syncedUpsertedCipher", { cipherId: notification.id }); - return this.syncCompleted(true); - } - } - } catch (e) { - if (e != null && e.statusCode === 404 && isEdit) { - await this.cipherService.delete(notification.id); - this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id }); - return this.syncCompleted(true); - } - } - } - return this.syncCompleted(false); - } - - async syncDeleteCipher(notification: SyncCipherNotification): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.cipherService.delete(notification.id); - this.messagingService.send("syncedDeletedCipher", { cipherId: notification.id }); - return this.syncCompleted(true); - } - return this.syncCompleted(false); - } - - async syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - try { - const localSend = await this.sendService.get(notification.id); - if ( - (!isEdit && localSend == null) || - (isEdit && localSend != null && localSend.revisionDate < notification.revisionDate) - ) { - const remoteSend = await this.apiService.getSend(notification.id); - if (remoteSend != null) { - const userId = await this.stateService.getUserId(); - await this.sendService.upsert(new SendData(remoteSend, userId)); - this.messagingService.send("syncedUpsertedSend", { sendId: notification.id }); - return this.syncCompleted(true); - } - } - } catch (e) { - this.logService.error(e); - } - } - return this.syncCompleted(false); - } - - async syncDeleteSend(notification: SyncSendNotification): Promise { - this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.sendService.delete(notification.id); - this.messagingService.send("syncedDeletedSend", { sendId: notification.id }); - this.syncCompleted(true); - return true; - } - return this.syncCompleted(false); - } - - // Helpers - - private syncStarted() { - this.syncInProgress = true; - this.messagingService.send("syncStarted"); - } - - private syncCompleted(successfully: boolean): boolean { - this.syncInProgress = false; - this.messagingService.send("syncCompleted", { successfully: successfully }); - return successfully; - } - - private async needsSyncing(forceSync: boolean) { - if (forceSync) { - return true; - } - - const lastSync = await this.getLastSync(); - if (lastSync == null || lastSync.getTime() === 0) { - return true; - } - - const response = await this.apiService.getAccountRevisionDate(); - if (new Date(response) <= lastSync) { - return false; - } - return true; - } - - private async syncProfile(response: ProfileResponse) { - const stamp = await this.stateService.getSecurityStamp(); - if (stamp != null && stamp !== response.securityStamp) { - if (this.logoutCallback != null) { - await this.logoutCallback(true); - } - - throw new Error("Stamp has changed"); - } - - await this.cryptoService.setEncKey(response.key); - await this.cryptoService.setEncPrivateKey(response.privateKey); - await this.cryptoService.setProviderKeys(response.providers); - await this.cryptoService.setOrgKeys(response.organizations, response.providerOrganizations); - await this.stateService.setSecurityStamp(response.securityStamp); - await this.stateService.setEmailVerified(response.emailVerified); - await this.stateService.setForcePasswordReset(response.forcePasswordReset); - await this.keyConnectorService.setUsesKeyConnector(response.usesKeyConnector); - - const organizations: { [id: string]: OrganizationData } = {}; - response.organizations.forEach((o) => { - organizations[o.id] = new OrganizationData(o); - }); - - const providers: { [id: string]: ProviderData } = {}; - response.providers.forEach((p) => { - providers[p.id] = new ProviderData(p); - }); - - response.providerOrganizations.forEach((o) => { - if (organizations[o.id] == null) { - organizations[o.id] = new OrganizationData(o); - organizations[o.id].isProviderUser = true; - } - }); - - await this.organizationService.save(organizations); - await this.providerService.save(providers); - - if (await this.keyConnectorService.userNeedsMigration()) { - await this.keyConnectorService.setConvertAccountRequired(true); - this.messagingService.send("convertAccountToKeyConnector"); - } else { - this.keyConnectorService.removeConvertAccountRequired(); - } - } - - private async syncFolders(userId: string, response: FolderResponse[]) { - const folders: { [id: string]: FolderData } = {}; - response.forEach((f) => { - folders[f.id] = new FolderData(f, userId); - }); - return await this.folderService.replace(folders); - } - - private async syncCollections(response: CollectionDetailsResponse[]) { - const collections: { [id: string]: CollectionData } = {}; - response.forEach((c) => { - collections[c.id] = new CollectionData(c); - }); - return await this.collectionService.replace(collections); - } - - private async syncCiphers(userId: string, response: CipherResponse[]) { - const ciphers: { [id: string]: CipherData } = {}; - response.forEach((c) => { - ciphers[c.id] = new CipherData(c, userId); - }); - return await this.cipherService.replace(ciphers); - } - - private async syncSends(userId: string, response: SendResponse[]) { - const sends: { [id: string]: SendData } = {}; - response.forEach((s) => { - sends[s.id] = new SendData(s, userId); - }); - return await this.sendService.replace(sends); - } - - private async syncSettings(response: DomainsResponse) { - let eqDomains: string[][] = []; - if (response != null && response.equivalentDomains != null) { - eqDomains = eqDomains.concat(response.equivalentDomains); - } - - if (response != null && response.globalEquivalentDomains != null) { - response.globalEquivalentDomains.forEach((global) => { - if (global.domains.length > 0) { - eqDomains.push(global.domains); - } - }); - } - - return this.settingsService.setEquivalentDomains(eqDomains); - } - - private async syncPolicies(response: PolicyResponse[]) { - const policies: { [id: string]: PolicyData } = {}; - if (response != null) { - response.forEach((p) => { - policies[p.id] = new PolicyData(p); - }); - } - return await this.policyService.replace(policies); - } -} diff --git a/jslib/common/src/services/system.service.ts b/jslib/common/src/services/system.service.ts deleted file mode 100644 index 8d9385b23..000000000 --- a/jslib/common/src/services/system.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { MessagingService } from "../abstractions/messaging.service"; -import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -import { StateService } from "../abstractions/state.service"; -import { SystemService as SystemServiceAbstraction } from "../abstractions/system.service"; -import { Utils } from "../misc/utils"; - -export class SystemService implements SystemServiceAbstraction { - private reloadInterval: any = null; - private clearClipboardTimeout: any = null; - private clearClipboardTimeoutFunction: () => Promise = null; - - constructor( - private messagingService: MessagingService, - private platformUtilsService: PlatformUtilsService, - private reloadCallback: () => Promise = null, - private stateService: StateService, - ) {} - - async startProcessReload(): Promise { - if ( - (await this.stateService.getDecryptedPinProtected()) != null || - (await this.stateService.getBiometricLocked()) || - this.reloadInterval != null - ) { - return; - } - this.cancelProcessReload(); - this.reloadInterval = setInterval(async () => { - let doRefresh = false; - const lastActive = await this.stateService.getLastActive(); - if (lastActive != null) { - const diffSeconds = new Date().getTime() - lastActive; - // Don't refresh if they are still active in the window - doRefresh = diffSeconds >= 5000; - } - const biometricLockedFingerprintValidated = - (await this.stateService.getBiometricFingerprintValidated()) && - (await this.stateService.getBiometricLocked()); - if (doRefresh && !biometricLockedFingerprintValidated) { - clearInterval(this.reloadInterval); - this.reloadInterval = null; - this.messagingService.send("reloadProcess"); - if (this.reloadCallback != null) { - await this.reloadCallback(); - } - } - }, 10000); - } - - cancelProcessReload(): void { - if (this.reloadInterval != null) { - clearInterval(this.reloadInterval); - this.reloadInterval = null; - } - } - - async clearClipboard(clipboardValue: string, timeoutMs: number = null): Promise { - if (this.clearClipboardTimeout != null) { - clearTimeout(this.clearClipboardTimeout); - this.clearClipboardTimeout = null; - } - if (Utils.isNullOrWhitespace(clipboardValue)) { - return; - } - await this.stateService.getClearClipboard().then((clearSeconds) => { - if (clearSeconds == null) { - return; - } - if (timeoutMs == null) { - timeoutMs = clearSeconds * 1000; - } - this.clearClipboardTimeoutFunction = async () => { - const clipboardValueNow = await this.platformUtilsService.readFromClipboard(); - if (clipboardValue === clipboardValueNow) { - this.platformUtilsService.copyToClipboard("", { clearing: true }); - } - }; - this.clearClipboardTimeout = setTimeout(async () => { - await this.clearPendingClipboard(); - }, timeoutMs); - }); - } - - async clearPendingClipboard() { - if (this.clearClipboardTimeoutFunction != null) { - await this.clearClipboardTimeoutFunction(); - this.clearClipboardTimeoutFunction = null; - } - } -} diff --git a/jslib/common/src/services/totp.service.ts b/jslib/common/src/services/totp.service.ts deleted file mode 100644 index d666305f6..000000000 --- a/jslib/common/src/services/totp.service.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { CryptoFunctionService } from "../abstractions/cryptoFunction.service"; -import { LogService } from "../abstractions/log.service"; -import { StateService } from "../abstractions/state.service"; -import { TotpService as TotpServiceAbstraction } from "../abstractions/totp.service"; -import { Utils } from "../misc/utils"; - -const B32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; -const SteamChars = "23456789BCDFGHJKMNPQRTVWXY"; - -export class TotpService implements TotpServiceAbstraction { - constructor( - private cryptoFunctionService: CryptoFunctionService, - private logService: LogService, - private stateService: StateService, - ) {} - - async getCode(key: string): Promise { - if (key == null) { - return null; - } - let period = 30; - let alg: "sha1" | "sha256" | "sha512" = "sha1"; - let digits = 6; - let keyB32 = key; - const isOtpAuth = key.toLowerCase().indexOf("otpauth://") === 0; - const isSteamAuth = !isOtpAuth && key.toLowerCase().indexOf("steam://") === 0; - if (isOtpAuth) { - const params = Utils.getQueryParams(key); - if (params.has("digits") && params.get("digits") != null) { - try { - const digitParams = parseInt(params.get("digits").trim(), null); - if (digitParams > 10) { - digits = 10; - } else if (digitParams > 0) { - digits = digitParams; - } - } catch { - this.logService.error("Invalid digits param."); - } - } - if (params.has("period") && params.get("period") != null) { - try { - const periodParam = parseInt(params.get("period").trim(), null); - if (periodParam > 0) { - period = periodParam; - } - } catch { - this.logService.error("Invalid period param."); - } - } - if (params.has("secret") && params.get("secret") != null) { - keyB32 = params.get("secret"); - } - if (params.has("algorithm") && params.get("algorithm") != null) { - const algParam = params.get("algorithm").toLowerCase(); - if (algParam === "sha1" || algParam === "sha256" || algParam === "sha512") { - alg = algParam; - } - } - } else if (isSteamAuth) { - keyB32 = key.substr("steam://".length); - digits = 5; - } - - const epoch = Math.round(new Date().getTime() / 1000.0); - const timeHex = this.leftPad(this.decToHex(Math.floor(epoch / period)), 16, "0"); - const timeBytes = Utils.fromHexToArray(timeHex); - const keyBytes = this.b32ToBytes(keyB32); - - if (!keyBytes.length || !timeBytes.length) { - return null; - } - - const hash = await this.sign(keyBytes, timeBytes, alg); - if (hash.length === 0) { - return null; - } - - const offset = hash[hash.length - 1] & 0xf; - const binary = - ((hash[offset] & 0x7f) << 24) | - ((hash[offset + 1] & 0xff) << 16) | - ((hash[offset + 2] & 0xff) << 8) | - (hash[offset + 3] & 0xff); - - let otp = ""; - if (isSteamAuth) { - let fullCode = binary & 0x7fffffff; - for (let i = 0; i < digits; i++) { - otp += SteamChars[fullCode % SteamChars.length]; - fullCode = Math.trunc(fullCode / SteamChars.length); - } - } else { - otp = (binary % Math.pow(10, digits)).toString(); - otp = this.leftPad(otp, digits, "0"); - } - - return otp; - } - - getTimeInterval(key: string): number { - let period = 30; - if (key != null && key.toLowerCase().indexOf("otpauth://") === 0) { - const params = Utils.getQueryParams(key); - if (params.has("period") && params.get("period") != null) { - try { - period = parseInt(params.get("period").trim(), null); - } catch { - this.logService.error("Invalid period param."); - } - } - } - return period; - } - - async isAutoCopyEnabled(): Promise { - return !(await this.stateService.getDisableAutoTotpCopy()); - } - - // Helpers - - private leftPad(s: string, l: number, p: string): string { - if (l + 1 >= s.length) { - s = Array(l + 1 - s.length).join(p) + s; - } - return s; - } - - private decToHex(d: number): string { - return (d < 15.5 ? "0" : "") + Math.round(d).toString(16); - } - - private b32ToHex(s: string): string { - s = s.toUpperCase(); - let cleanedInput = ""; - - for (let i = 0; i < s.length; i++) { - if (B32Chars.indexOf(s[i]) < 0) { - continue; - } - - cleanedInput += s[i]; - } - s = cleanedInput; - - let bits = ""; - let hex = ""; - for (let i = 0; i < s.length; i++) { - const byteIndex = B32Chars.indexOf(s.charAt(i)); - if (byteIndex < 0) { - continue; - } - bits += this.leftPad(byteIndex.toString(2), 5, "0"); - } - for (let i = 0; i + 4 <= bits.length; i += 4) { - const chunk = bits.substr(i, 4); - hex = hex + parseInt(chunk, 2).toString(16); - } - return hex; - } - - private b32ToBytes(s: string): Uint8Array { - return Utils.fromHexToArray(this.b32ToHex(s)); - } - - private async sign( - keyBytes: Uint8Array, - timeBytes: Uint8Array, - alg: "sha1" | "sha256" | "sha512", - ) { - const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg); - return new Uint8Array(signature); - } -} diff --git a/jslib/common/src/services/userVerification.service.ts b/jslib/common/src/services/userVerification.service.ts deleted file mode 100644 index d95d75b91..000000000 --- a/jslib/common/src/services/userVerification.service.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { I18nService } from "../abstractions/i18n.service"; -import { UserVerificationService as UserVerificationServiceAbstraction } from "../abstractions/userVerification.service"; -import { VerificationType } from "../enums/verificationType"; -import { VerifyOTPRequest } from "../models/request/account/verifyOTPRequest"; -import { SecretVerificationRequest } from "../models/request/secretVerificationRequest"; -import { Verification } from "../types/verification"; - -/** - * Used for general-purpose user verification throughout the app. - * Use it to verify the input collected by UserVerificationComponent. - */ -export class UserVerificationService implements UserVerificationServiceAbstraction { - constructor( - private cryptoService: CryptoService, - private i18nService: I18nService, - private apiService: ApiService, - ) {} - - /** - * Create a new request model to be used for server-side verification - * @param verification User-supplied verification data (Master Password or OTP) - * @param requestClass The request model to create - * @param alreadyHashed Whether the master password is already hashed - */ - async buildRequest( - verification: Verification, - requestClass?: new () => T, - alreadyHashed?: boolean, - ) { - this.validateInput(verification); - - const request = - requestClass != null ? new requestClass() : (new SecretVerificationRequest() as T); - - if (verification.type === VerificationType.OTP) { - request.otp = verification.secret; - } else { - request.masterPasswordHash = alreadyHashed - ? verification.secret - : await this.cryptoService.hashPassword(verification.secret, null); - } - - return request; - } - - /** - * Used to verify the Master Password client-side, or send the OTP to the server for verification (with no other data) - * Generally used for client-side verification only. - * @param verification User-supplied verification data (Master Password or OTP) - */ - async verifyUser(verification: Verification): Promise { - this.validateInput(verification); - - if (verification.type === VerificationType.OTP) { - const request = new VerifyOTPRequest(verification.secret); - try { - await this.apiService.postAccountVerifyOTP(request); - } catch (e) { - throw new Error(this.i18nService.t("invalidVerificationCode")); - } - } else { - const passwordValid = await this.cryptoService.compareAndUpdateKeyHash( - verification.secret, - null, - ); - if (!passwordValid) { - 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/jslib/common/src/services/usernameGeneration.service.ts b/jslib/common/src/services/usernameGeneration.service.ts deleted file mode 100644 index 02d71a27a..000000000 --- a/jslib/common/src/services/usernameGeneration.service.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { CryptoService } from "../abstractions/crypto.service"; -import { StateService } from "../abstractions/state.service"; -import { UsernameGenerationService as BaseUsernameGenerationService } from "../abstractions/usernameGeneration.service"; -import { EEFLongWordList } from "../misc/wordlist"; - -const DefaultOptions = { - type: "word", - wordCapitalize: true, - wordIncludeNumber: true, - subaddressType: "random", - catchallType: "random", -}; - -export class UsernameGenerationService implements BaseUsernameGenerationService { - constructor( - private cryptoService: CryptoService, - private stateService: StateService, - ) {} - - generateUsername(options: any): Promise { - if (options.type === "catchall") { - return this.generateCatchall(options); - } else if (options.type === "subaddress") { - return this.generateSubaddress(options); - } else if (options.type === "forwarded") { - return this.generateSubaddress(options); - } else { - return this.generateWord(options); - } - } - - async generateWord(options: any): Promise { - const o = Object.assign({}, DefaultOptions, options); - - if (o.wordCapitalize == null) { - o.wordCapitalize = true; - } - if (o.wordIncludeNumber == null) { - o.wordIncludeNumber = true; - } - - const wordIndex = await this.cryptoService.randomNumber(0, EEFLongWordList.length - 1); - let word = EEFLongWordList[wordIndex]; - if (o.wordCapitalize) { - word = word.charAt(0).toUpperCase() + word.slice(1); - } - if (o.wordIncludeNumber) { - const num = await this.cryptoService.randomNumber(1, 9999); - word = word + this.zeroPad(num.toString(), 4); - } - return word; - } - - async generateSubaddress(options: any): Promise { - const o = Object.assign({}, DefaultOptions, options); - - const subaddressEmail = o.subaddressEmail; - if (subaddressEmail == null || subaddressEmail.length < 3) { - return o.subaddressEmail; - } - const atIndex = subaddressEmail.indexOf("@"); - if (atIndex < 1 || atIndex >= subaddressEmail.length - 1) { - return subaddressEmail; - } - if (o.subaddressType == null) { - o.subaddressType = "random"; - } - - const emailBeginning = subaddressEmail.substr(0, atIndex); - const emailEnding = subaddressEmail.substr(atIndex + 1, subaddressEmail.length); - - let subaddressString = ""; - if (o.subaddressType === "random") { - subaddressString = await this.randomString(8); - } else if (o.subaddressType === "website-name") { - subaddressString = o.website; - } - return emailBeginning + "+" + subaddressString + "@" + emailEnding; - } - - async generateCatchall(options: any): Promise { - const o = Object.assign({}, DefaultOptions, options); - - if (o.catchallDomain == null || o.catchallDomain === "") { - return null; - } - if (o.catchallType == null) { - o.catchallType = "random"; - } - - let startString = ""; - if (o.catchallType === "random") { - startString = await this.randomString(8); - } else if (o.catchallType === "website-name") { - startString = o.website; - } - return startString + "@" + o.catchallDomain; - } - - async getOptions(): Promise { - let options = await this.stateService.getUsernameGenerationOptions(); - if (options == null) { - options = Object.assign({}, DefaultOptions); - } else { - options = Object.assign({}, DefaultOptions, options); - } - await this.stateService.setUsernameGenerationOptions(options); - return options; - } - - async saveOptions(options: any) { - await this.stateService.setUsernameGenerationOptions(options); - } - - private async randomString(length: number) { - let str = ""; - const charSet = "abcdefghijklmnopqrstuvwxyz1234567890"; - for (let i = 0; i < length; i++) { - const randomCharIndex = await this.cryptoService.randomNumber(0, charSet.length - 1); - str += charSet.charAt(randomCharIndex); - } - return str; - } - - // ref: https://stackoverflow.com/a/10073788 - private zeroPad(number: string, width: number) { - return number.length >= width - ? number - : new Array(width - number.length + 1).join("0") + number; - } -} diff --git a/jslib/common/src/services/vaultTimeout.service.ts b/jslib/common/src/services/vaultTimeout.service.ts deleted file mode 100644 index a4468711e..000000000 --- a/jslib/common/src/services/vaultTimeout.service.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { firstValueFrom } from "rxjs"; - -import { CipherService } from "../abstractions/cipher.service"; -import { CollectionService } from "../abstractions/collection.service"; -import { CryptoService } from "../abstractions/crypto.service"; -import { FolderService } from "../abstractions/folder.service"; -import { KeyConnectorService } from "../abstractions/keyConnector.service"; -import { MessagingService } from "../abstractions/messaging.service"; -import { PlatformUtilsService } from "../abstractions/platformUtils.service"; -import { PolicyService } from "../abstractions/policy.service"; -import { SearchService } from "../abstractions/search.service"; -import { StateService } from "../abstractions/state.service"; -import { TokenService } from "../abstractions/token.service"; -import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "../abstractions/vaultTimeout.service"; -import { KeySuffixOptions } from "../enums/keySuffixOptions"; -import { PolicyType } from "../enums/policyType"; - -export class VaultTimeoutService implements VaultTimeoutServiceAbstraction { - private inited = false; - - constructor( - private cipherService: CipherService, - private folderService: FolderService, - private collectionService: CollectionService, - private cryptoService: CryptoService, - protected platformUtilsService: PlatformUtilsService, - private messagingService: MessagingService, - private searchService: SearchService, - private tokenService: TokenService, - private policyService: PolicyService, - private keyConnectorService: KeyConnectorService, - private stateService: StateService, - private lockedCallback: (userId?: string) => Promise = null, - private loggedOutCallback: (userId?: string) => Promise = null, - ) {} - - init(checkOnInterval: boolean) { - if (this.inited) { - return; - } - - this.inited = true; - if (checkOnInterval) { - this.startCheck(); - } - } - - startCheck() { - this.checkVaultTimeout(); - setInterval(() => this.checkVaultTimeout(), 10 * 1000); // check every 10 seconds - } - - // Keys aren't stored for a device that is locked or logged out. - async isLocked(userId?: string): Promise { - const neverLock = - (await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) && - !(await this.stateService.getEverBeenUnlocked({ userId: userId })); - if (neverLock) { - // TODO: This also _sets_ the key so when we check memory in the next line it finds a key. - // We should refactor here. - await this.cryptoService.getKey(KeySuffixOptions.Auto, userId); - } - - return !(await this.cryptoService.hasKeyInMemory(userId)); - } - - async checkVaultTimeout(): Promise { - if (await this.platformUtilsService.isViewOpen()) { - return; - } - - const accounts = await firstValueFrom(this.stateService.accounts$); - for (const userId in accounts) { - if (userId != null && (await this.shouldLock(userId))) { - await this.executeTimeoutAction(userId); - } - } - } - - async lock(allowSoftLock = false, userId?: string): Promise { - const authed = await this.stateService.getIsAuthenticated({ userId: userId }); - if (!authed) { - return; - } - - if (await this.keyConnectorService.getUsesKeyConnector()) { - const pinSet = await this.isPinLockSet(); - const pinLock = - (pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1]; - - if (!pinLock && !(await this.isBiometricLockSet())) { - await this.logOut(userId); - } - } - - if (userId == null || userId === (await this.stateService.getUserId())) { - this.searchService.clearIndex(); - } - - await this.stateService.setEverBeenUnlocked(true, { userId: userId }); - await this.stateService.setBiometricLocked(true, { userId: userId }); - await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId }); - - await this.cryptoService.clearKey(false, userId); - await this.cryptoService.clearOrgKeys(true, userId); - await this.cryptoService.clearKeyPair(true, userId); - await this.cryptoService.clearEncKey(true, userId); - - await this.folderService.clearCache(userId); - await this.cipherService.clearCache(userId); - await this.collectionService.clearCache(userId); - - this.messagingService.send("locked", { userId: userId }); - - if (this.lockedCallback != null) { - await this.lockedCallback(userId); - } - } - - async logOut(userId?: string): Promise { - if (this.loggedOutCallback != null) { - await this.loggedOutCallback(userId); - } - } - - async setVaultTimeoutOptions(timeout: number, action: string): Promise { - await this.stateService.setVaultTimeout(timeout); - - // We swap these tokens from being on disk for lock actions, and in memory for logout actions - // Get them here to set them to their new location after changing the timeout action and clearing if needed - const token = await this.tokenService.getToken(); - const refreshToken = await this.tokenService.getRefreshToken(); - const clientId = await this.tokenService.getClientId(); - const clientSecret = await this.tokenService.getClientSecret(); - - const currentAction = await this.stateService.getVaultTimeoutAction(); - if ((timeout != null || timeout === 0) && action === "logOut" && action !== currentAction) { - // if we have a vault timeout and the action is log out, reset tokens - await this.tokenService.clearToken(); - } - - await this.stateService.setVaultTimeoutAction(action); - - await this.tokenService.setToken(token); - await this.tokenService.setRefreshToken(refreshToken); - await this.tokenService.setClientId(clientId); - await this.tokenService.setClientSecret(clientSecret); - - await this.cryptoService.toggleKey(); - } - - async isPinLockSet(): Promise<[boolean, boolean]> { - const protectedPin = await this.stateService.getProtectedPin(); - const pinProtectedKey = await this.stateService.getEncryptedPinProtected(); - return [protectedPin != null, pinProtectedKey != null]; - } - - async isBiometricLockSet(): Promise { - return await this.stateService.getBiometricUnlock(); - } - - async getVaultTimeout(userId?: string): Promise { - const vaultTimeout = await this.stateService.getVaultTimeout({ userId: userId }); - - if ( - await this.policyService.policyAppliesToUser(PolicyType.MaximumVaultTimeout, null, userId) - ) { - const policy = await this.policyService.getAll(PolicyType.MaximumVaultTimeout, userId); - // Remove negative values, and ensure it's smaller than maximum allowed value according to policy - let timeout = Math.min(vaultTimeout, policy[0].data.minutes); - - if (vaultTimeout == null || timeout < 0) { - timeout = policy[0].data.minutes; - } - - // We really shouldn't need to set the value here, but multiple services relies on this value being correct. - if (vaultTimeout !== timeout) { - await this.stateService.setVaultTimeout(timeout, { userId: userId }); - } - - return timeout; - } - - return vaultTimeout; - } - - async clear(userId?: string): Promise { - await this.stateService.setEverBeenUnlocked(false, { userId: userId }); - await this.stateService.setDecryptedPinProtected(null, { userId: userId }); - await this.stateService.setProtectedPin(null, { userId: userId }); - } - - private async isLoggedOut(userId?: string): Promise { - return !(await this.stateService.getIsAuthenticated({ userId: userId })); - } - - private async shouldLock(userId: string): Promise { - if (await this.isLoggedOut(userId)) { - return false; - } - - if (await this.isLocked(userId)) { - return false; - } - - const vaultTimeout = await this.getVaultTimeout(userId); - if (vaultTimeout == null || vaultTimeout < 0) { - return false; - } - - const lastActive = await this.stateService.getLastActive({ userId: userId }); - if (lastActive == null) { - return false; - } - - const vaultTimeoutSeconds = vaultTimeout * 60; - const diffSeconds = (new Date().getTime() - lastActive) / 1000; - return diffSeconds >= vaultTimeoutSeconds; - } - - private async executeTimeoutAction(userId: string): Promise { - const timeoutAction = await this.stateService.getVaultTimeoutAction({ userId: userId }); - timeoutAction === "logOut" ? await this.logOut(userId) : await this.lock(true, userId); - } -} diff --git a/jslib/electron/src/services/electronCrypto.service.ts b/jslib/electron/src/services/electronCrypto.service.ts deleted file mode 100644 index 2b479d0fe..000000000 --- a/jslib/electron/src/services/electronCrypto.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { CryptoFunctionService } from "@/jslib/common/src/abstractions/cryptoFunction.service"; -import { LogService } from "@/jslib/common/src/abstractions/log.service"; -import { PlatformUtilsService } from "@/jslib/common/src/abstractions/platformUtils.service"; -import { StateService } from "@/jslib/common/src/abstractions/state.service"; -import { KeySuffixOptions } from "@/jslib/common/src/enums/keySuffixOptions"; -import { SymmetricCryptoKey } from "@/jslib/common/src/models/domain/symmetricCryptoKey"; -import { CryptoService } from "@/jslib/common/src/services/crypto.service"; - -export class ElectronCryptoService extends CryptoService { - constructor( - cryptoFunctionService: CryptoFunctionService, - platformUtilService: PlatformUtilsService, - logService: LogService, - stateService: StateService, - ) { - super(cryptoFunctionService, platformUtilService, logService, stateService); - } - - async hasKeyStored(keySuffix: KeySuffixOptions): Promise { - await this.upgradeSecurelyStoredKey(); - return super.hasKeyStored(keySuffix); - } - - protected async storeKey(key: SymmetricCryptoKey, userId?: string) { - if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) { - await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId }); - } else { - this.clearStoredKey(KeySuffixOptions.Auto); - } - - if (await this.shouldStoreKey(KeySuffixOptions.Biometric, userId)) { - await this.stateService.setCryptoMasterKeyBiometric(key.keyB64, { userId: userId }); - } else { - this.clearStoredKey(KeySuffixOptions.Biometric); - } - } - - protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string) { - await this.upgradeSecurelyStoredKey(); - return super.retrieveKeyFromStorage(keySuffix, userId); - } - - /** - * @deprecated 4 Jun 2021 This is temporary upgrade method to move from a single shared stored key to - * multiple, unique stored keys for each use, e.g. never logout vs. biometric authentication. - */ - private async upgradeSecurelyStoredKey() { - // attempt key upgrade, but if we fail just delete it. Keys will be stored property upon unlock anyway. - const key = await this.stateService.getCryptoMasterKeyB64(); - - if (key == null) { - return; - } - - try { - if (await this.shouldStoreKey(KeySuffixOptions.Auto)) { - await this.stateService.setCryptoMasterKeyAuto(key); - } - if (await this.shouldStoreKey(KeySuffixOptions.Biometric)) { - await this.stateService.setCryptoMasterKeyBiometric(key); - } - } catch (e) { - this.logService.error( - `Encountered error while upgrading obsolete Bitwarden secure storage item:`, - ); - this.logService.error(e); - } - - await this.stateService.setCryptoMasterKeyB64(null); - } -} diff --git a/package-lock.json b/package-lock.json index 6285158fd..be9c09431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,6 @@ "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", "@microsoft/microsoft-graph-client": "3.0.7", - "@microsoft/signalr": "7.0.10", - "@microsoft/signalr-protocol-msgpack": "7.0.10", "big-integer": "1.6.52", "bootstrap": "4.6.2", "browser-hrtime": "1.1.8", @@ -36,7 +34,6 @@ "keytar": "7.9.0", "ldapjs": "2.3.3", "lowdb": "1.0.0", - "lunr": "2.3.9", "ngx-toastr": "16.2.0", "node-fetch": "2.7.0", "open": "8.4.2", @@ -59,7 +56,6 @@ "@types/jest": "29.5.11", "@types/ldapjs": "2.2.5", "@types/lowdb": "1.0.15", - "@types/lunr": "2.3.7", "@types/node": "18.17.12", "@types/node-fetch": "2.6.10", "@types/node-forge": "1.3.11", @@ -4561,35 +4557,6 @@ "integrity": "sha512-1fcPVrB/NkbNcGNfCy+Cgnvwxt6/sbIEEFgZHFBJ670zYLegENYJF8qMo7x3LqBjWX2/Eneq5BVVRCLTmlJN+g==", "dev": true }, - "node_modules/@microsoft/signalr": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-7.0.10.tgz", - "integrity": "sha512-tOEn32i5EatAx4sZbzmLgcBc2VbKQmx+F4rI2/Ioq2MnBaYcFxbDzOoZgISIS4IR9H1ij/sKoU8zQOAFC8GJKg==", - "dependencies": { - "abort-controller": "^3.0.0", - "eventsource": "^2.0.2", - "fetch-cookie": "^2.0.3", - "node-fetch": "^2.6.7", - "ws": "^7.4.5" - } - }, - "node_modules/@microsoft/signalr-protocol-msgpack": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@microsoft/signalr-protocol-msgpack/-/signalr-protocol-msgpack-7.0.10.tgz", - "integrity": "sha512-iZacNFQ3+BT3wZjFN2qcuQQJWK0ZlyCek4plWw1QrFqqOMBYEwPY4BCbLcwNZcTiOpTK65es1CCf3Yxb6lwlVQ==", - "dependencies": { - "@microsoft/signalr": ">=7.0.10", - "@msgpack/msgpack": "^2.7.0" - } - }, - "node_modules/@msgpack/msgpack": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", - "integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==", - "engines": { - "node": ">= 10" - } - }, "node_modules/@ngtools/webpack": { "version": "16.2.12", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.12.tgz", @@ -5077,12 +5044,6 @@ "@types/lodash": "*" } }, - "node_modules/@types/lunr": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.7.tgz", - "integrity": "sha512-Tb/kUm38e8gmjahQzdCKhbdsvQ9/ppzHFfsJ0dMs3ckqQsRj+P5IkSAwFTBrBxdyr3E/LoMUUrZngjDYAjiE3A==", - "dev": true - }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -10443,14 +10404,6 @@ "node": ">=0.8.x" } }, - "node_modules/eventsource": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", - "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -10746,15 +10699,6 @@ "pend": "~1.2.0" } }, - "node_modules/fetch-cookie": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-2.2.0.tgz", - "integrity": "sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==", - "dependencies": { - "set-cookie-parser": "^2.4.8", - "tough-cookie": "^4.0.0" - } - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -15054,11 +14998,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==" - }, "node_modules/magic-string": { "version": "0.30.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", @@ -17213,7 +17152,8 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true }, "node_modules/pump": { "version": "3.0.0", @@ -17262,7 +17202,8 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -17720,7 +17661,8 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, "node_modules/resolve": { "version": "1.22.8", @@ -18459,11 +18401,6 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, - "node_modules/set-cookie-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" - }, "node_modules/set-function-length": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", @@ -19517,6 +19454,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -19531,6 +19469,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "engines": { "node": ">=6" } @@ -19539,6 +19478,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, "engines": { "node": ">= 4.0.0" } @@ -20116,6 +20056,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -20936,6 +20877,8 @@ "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "peer": true, "engines": { "node": ">=8.3.0" }, diff --git a/package.json b/package.json index 9c09e690b..10d01e2dc 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "@types/jest": "29.5.11", "@types/ldapjs": "2.2.5", "@types/lowdb": "1.0.15", - "@types/lunr": "2.3.7", "@types/node": "18.17.12", "@types/node-fetch": "2.6.10", "@types/node-forge": "1.3.11", @@ -148,8 +147,6 @@ "@angular/platform-browser-dynamic": "16.2.12", "@angular/router": "16.2.12", "@microsoft/microsoft-graph-client": "3.0.7", - "@microsoft/signalr": "7.0.10", - "@microsoft/signalr-protocol-msgpack": "7.0.10", "big-integer": "1.6.52", "bootstrap": "4.6.2", "browser-hrtime": "1.1.8", @@ -164,7 +161,6 @@ "keytar": "7.9.0", "ldapjs": "2.3.3", "lowdb": "1.0.0", - "lunr": "2.3.9", "ngx-toastr": "16.2.0", "node-fetch": "2.7.0", "open": "8.4.2", diff --git a/src/bwdc.ts b/src/bwdc.ts index 2c8132141..df3b9d574 100644 --- a/src/bwdc.ts +++ b/src/bwdc.ts @@ -8,22 +8,14 @@ import { LogLevelType } from "@/jslib/common/src/enums/logLevelType"; import { StateFactory } from "@/jslib/common/src/factories/stateFactory"; import { GlobalState } from "@/jslib/common/src/models/domain/globalState"; import { AppIdService } from "@/jslib/common/src/services/appId.service"; -import { CipherService } from "@/jslib/common/src/services/cipher.service"; -import { CollectionService } from "@/jslib/common/src/services/collection.service"; import { ContainerService } from "@/jslib/common/src/services/container.service"; import { CryptoService } from "@/jslib/common/src/services/crypto.service"; import { EnvironmentService } from "@/jslib/common/src/services/environment.service"; -import { FileUploadService } from "@/jslib/common/src/services/fileUpload.service"; -import { FolderService } from "@/jslib/common/src/services/folder.service"; import { KeyConnectorService } from "@/jslib/common/src/services/keyConnector.service"; import { NoopMessagingService } from "@/jslib/common/src/services/noopMessaging.service"; import { OrganizationService } from "@/jslib/common/src/services/organization.service"; import { PasswordGenerationService } from "@/jslib/common/src/services/passwordGeneration.service"; import { PolicyService } from "@/jslib/common/src/services/policy.service"; -import { ProviderService } from "@/jslib/common/src/services/provider.service"; -import { SearchService } from "@/jslib/common/src/services/search.service"; -import { SendService } from "@/jslib/common/src/services/send.service"; -import { SettingsService } from "@/jslib/common/src/services/settings.service"; import { TokenService } from "@/jslib/common/src/services/token.service"; import { CliPlatformUtilsService } from "@/jslib/node/src/cli/services/cliPlatformUtils.service"; import { ConsoleLogService } from "@/jslib/node/src/cli/services/consoleLog.service"; @@ -44,7 +36,6 @@ import { SyncService } from "./services/sync.service"; // eslint-disable-next-line const packageJson = require("../package.json"); -export const searchService: SearchService = null; export class Main { dataFilePath: string; logService: ConsoleLogService; @@ -61,13 +52,6 @@ export class Main { containerService: ContainerService; cryptoFunctionService: NodeCryptoFunctionService; authService: AuthService; - collectionService: CollectionService; - cipherService: CipherService; - fileUploadService: FileUploadService; - folderService: FolderService; - searchService: SearchService; - sendService: SendService; - settingsService: SettingsService; syncService: SyncService; passwordGenerationService: PasswordGenerationService; policyService: PolicyService; @@ -76,7 +60,6 @@ export class Main { stateService: StateService; stateMigrationService: StateMigrationService; organizationService: OrganizationService; - providerService: ProviderService; twoFactorService: TwoFactorServiceAbstraction; constructor() { @@ -216,48 +199,6 @@ export class Main { this.stateService, ); - this.settingsService = new SettingsService(this.stateService); - - this.fileUploadService = new FileUploadService(this.logService, this.apiService); - - this.cipherService = new CipherService( - this.cryptoService, - this.settingsService, - this.apiService, - this.fileUploadService, - this.i18nService, - () => searchService, - this.logService, - this.stateService, - ); - - this.searchService = new SearchService(this.cipherService, this.logService, this.i18nService); - - this.folderService = new FolderService( - this.cryptoService, - this.apiService, - this.i18nService, - this.cipherService, - this.stateService, - ); - - this.collectionService = new CollectionService( - this.cryptoService, - this.i18nService, - this.stateService, - ); - - this.sendService = new SendService( - this.cryptoService, - this.apiService, - this.fileUploadService, - this.i18nService, - this.cryptoFunctionService, - this.stateService, - ); - - this.providerService = new ProviderService(this.stateService); - this.program = new Program(this); }