diff --git a/.yarn/cache/@standardnotes-common-npm-1.43.0-ad3f3ce881-5930059441.zip b/.yarn/cache/@standardnotes-common-npm-1.43.0-ad3f3ce881-5930059441.zip new file mode 100644 index 000000000..d68f37d41 Binary files /dev/null and b/.yarn/cache/@standardnotes-common-npm-1.43.0-ad3f3ce881-5930059441.zip differ diff --git a/packages/api/package.json b/packages/api/package.json index 97b5ab9ca..85b3dbdd6 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -36,7 +36,7 @@ "typescript": "*" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/encryption": "workspace:*", "@standardnotes/models": "workspace:*", "@standardnotes/responses": "workspace:*", diff --git a/packages/api/src/Domain/Client/User/UserApiOperations.ts b/packages/api/src/Domain/Client/User/UserApiOperations.ts new file mode 100644 index 000000000..8e22a3f01 --- /dev/null +++ b/packages/api/src/Domain/Client/User/UserApiOperations.ts @@ -0,0 +1,5 @@ +export enum UserApiOperations { + Registering, + SubmittingRequest, + DeletingAccount, +} diff --git a/packages/api/src/Domain/Client/User/UserApiService.spec.ts b/packages/api/src/Domain/Client/User/UserApiService.spec.ts index 0b1af51dc..4dfdc2074 100644 --- a/packages/api/src/Domain/Client/User/UserApiService.spec.ts +++ b/packages/api/src/Domain/Client/User/UserApiService.spec.ts @@ -1,20 +1,35 @@ -import { ProtocolVersion } from '@standardnotes/common' +import { ProtocolVersion, UserRequestType } from '@standardnotes/common' import { RootKeyParamsInterface } from '@standardnotes/models' +import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse' + import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' import { UserServerInterface } from '../../Server' +import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface' + +import { UserApiOperations } from './UserApiOperations' import { UserApiService } from './UserApiService' describe('UserApiService', () => { let userServer: UserServerInterface + let userRequestServer: UserRequestServerInterface let keyParams: RootKeyParamsInterface - const createService = () => new UserApiService(userServer) + const createService = () => new UserApiService(userServer, userRequestServer) beforeEach(() => { userServer = {} as jest.Mocked userServer.register = jest.fn().mockReturnValue({ data: { user: { email: 'test@test.te', uuid: '1-2-3' } }, } as jest.Mocked) + userServer.deleteAccount = jest.fn().mockReturnValue({ + data: { message: 'Success' }, + } as jest.Mocked) + + userRequestServer = {} as jest.Mocked + userRequestServer.submitUserRequest = jest.fn().mockReturnValue({ + data: { success: true }, + } as jest.Mocked) keyParams = {} as jest.Mocked keyParams.getPortableValue = jest.fn().mockReturnValue({ @@ -51,8 +66,8 @@ describe('UserApiService', () => { it('should not register a user if it is already registering', async () => { const service = createService() - Object.defineProperty(service, 'registering', { - get: () => true, + Object.defineProperty(service, 'operationsInProgress', { + get: () => new Map([[UserApiOperations.Registering, true]]), }) let error = null @@ -84,4 +99,99 @@ describe('UserApiService', () => { expect(error).not.toBeNull() }) + + it('should submit a user request', async () => { + const response = await createService().submitUserRequest({ + userUuid: '1-2-3', + requestType: UserRequestType.ExitDiscount, + }) + + expect(response).toEqual({ + data: { + success: true, + }, + }) + expect(userRequestServer.submitUserRequest).toHaveBeenCalledWith({ + userUuid: '1-2-3', + requestType: UserRequestType.ExitDiscount, + }) + }) + + it('should not submit a user request if it is already submitting', async () => { + const service = createService() + Object.defineProperty(service, 'operationsInProgress', { + get: () => new Map([[UserApiOperations.SubmittingRequest, true]]), + }) + + let error = null + try { + await service.submitUserRequest({ userUuid: '1-2-3', requestType: UserRequestType.ExitDiscount }) + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should not submit a user request if the server fails', async () => { + userRequestServer.submitUserRequest = jest.fn().mockImplementation(() => { + throw new Error('Oops') + }) + + let error = null + try { + await createService().submitUserRequest({ + userUuid: '1-2-3', + requestType: UserRequestType.ExitDiscount, + }) + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should delete a user', async () => { + const response = await createService().deleteAccount('1-2-3') + + expect(response).toEqual({ + data: { + message: 'Success', + }, + }) + expect(userServer.deleteAccount).toHaveBeenCalledWith({ + userUuid: '1-2-3', + }) + }) + + it('should not delete a user if it is already deleting', async () => { + const service = createService() + Object.defineProperty(service, 'operationsInProgress', { + get: () => new Map([[UserApiOperations.DeletingAccount, true]]), + }) + + let error = null + try { + await service.deleteAccount('1-2-3') + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) + + it('should not delete a user if the server fails', async () => { + userServer.deleteAccount = jest.fn().mockImplementation(() => { + throw new Error('Oops') + }) + + let error = null + try { + await createService().deleteAccount('1-2-3') + } catch (caughtError) { + error = caughtError + } + + expect(error).not.toBeNull() + }) }) diff --git a/packages/api/src/Domain/Client/User/UserApiService.ts b/packages/api/src/Domain/Client/User/UserApiService.ts index b116f169c..7e66daedf 100644 --- a/packages/api/src/Domain/Client/User/UserApiService.ts +++ b/packages/api/src/Domain/Client/User/UserApiService.ts @@ -1,17 +1,57 @@ import { RootKeyParamsInterface } from '@standardnotes/models' +import { UserRequestType } from '@standardnotes/common' + import { ErrorMessage } from '../../Error/ErrorMessage' import { ApiCallError } from '../../Error/ApiCallError' import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse' import { UserServerInterface } from '../../Server/User/UserServerInterface' import { ApiVersion } from '../../Api/ApiVersion' import { ApiEndpointParam } from '../../Request/ApiEndpointParam' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' +import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse' +import { UserRequestServerInterface } from '../../Server/UserRequest/UserRequestServerInterface' + +import { UserApiOperations } from './UserApiOperations' import { UserApiServiceInterface } from './UserApiServiceInterface' export class UserApiService implements UserApiServiceInterface { - private registering: boolean + private operationsInProgress: Map - constructor(private userServer: UserServerInterface) { - this.registering = false + constructor(private userServer: UserServerInterface, private userRequestServer: UserRequestServerInterface) { + this.operationsInProgress = new Map() + } + + async deleteAccount(userUuid: string): Promise { + this.lockOperation(UserApiOperations.DeletingAccount) + + try { + const response = await this.userServer.deleteAccount({ + userUuid: userUuid, + }) + + this.unlockOperation(UserApiOperations.DeletingAccount) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericRegistrationFail) + } + } + + async submitUserRequest(dto: { userUuid: string; requestType: UserRequestType }): Promise { + this.lockOperation(UserApiOperations.SubmittingRequest) + + try { + const response = await this.userRequestServer.submitUserRequest({ + userUuid: dto.userUuid, + requestType: dto.requestType, + }) + + this.unlockOperation(UserApiOperations.SubmittingRequest) + + return response + } catch (error) { + throw new ApiCallError(ErrorMessage.GenericRegistrationFail) + } } async register(registerDTO: { @@ -20,10 +60,7 @@ export class UserApiService implements UserApiServiceInterface { keyParams: RootKeyParamsInterface ephemeral: boolean }): Promise { - if (this.registering) { - throw new ApiCallError(ErrorMessage.RegistrationInProgress) - } - this.registering = true + this.lockOperation(UserApiOperations.Registering) try { const response = await this.userServer.register({ @@ -34,11 +71,23 @@ export class UserApiService implements UserApiServiceInterface { ...registerDTO.keyParams.getPortableValue(), }) - this.registering = false + this.unlockOperation(UserApiOperations.Registering) return response } catch (error) { throw new ApiCallError(ErrorMessage.GenericRegistrationFail) } } + + private lockOperation(operation: UserApiOperations): void { + if (this.operationsInProgress.get(operation)) { + throw new ApiCallError(ErrorMessage.GenericInProgress) + } + + this.operationsInProgress.set(operation, true) + } + + private unlockOperation(operation: UserApiOperations): void { + this.operationsInProgress.set(operation, false) + } } diff --git a/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts b/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts index 89feed373..952ea89ee 100644 --- a/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts +++ b/packages/api/src/Domain/Client/User/UserApiServiceInterface.ts @@ -1,5 +1,9 @@ +import { UserRequestType, Uuid } from '@standardnotes/common' import { RootKeyParamsInterface } from '@standardnotes/models' + +import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse' import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' export interface UserApiServiceInterface { register(registerDTO: { @@ -8,4 +12,6 @@ export interface UserApiServiceInterface { keyParams: RootKeyParamsInterface ephemeral: boolean }): Promise + submitUserRequest(dto: { userUuid: Uuid; requestType: UserRequestType }): Promise + deleteAccount(userUuid: string): Promise } diff --git a/packages/api/src/Domain/Client/index.ts b/packages/api/src/Domain/Client/index.ts index 4235c45c6..996a87657 100644 --- a/packages/api/src/Domain/Client/index.ts +++ b/packages/api/src/Domain/Client/index.ts @@ -1,6 +1,7 @@ export * from './Subscription/SubscriptionApiOperations' export * from './Subscription/SubscriptionApiService' export * from './Subscription/SubscriptionApiServiceInterface' +export * from './User/UserApiOperations' export * from './User/UserApiService' export * from './User/UserApiServiceInterface' export * from './WebSocket/WebSocketApiService' diff --git a/packages/api/src/Domain/Request/User/UserDeletionRequestParams.ts b/packages/api/src/Domain/Request/User/UserDeletionRequestParams.ts new file mode 100644 index 000000000..4b82aeb68 --- /dev/null +++ b/packages/api/src/Domain/Request/User/UserDeletionRequestParams.ts @@ -0,0 +1,6 @@ +import { Uuid } from '@standardnotes/common' + +export type UserDeletionRequestParams = { + userUuid: Uuid + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/UserRequest/UserRequestRequestParams.ts b/packages/api/src/Domain/Request/UserRequest/UserRequestRequestParams.ts new file mode 100644 index 000000000..9a7cda719 --- /dev/null +++ b/packages/api/src/Domain/Request/UserRequest/UserRequestRequestParams.ts @@ -0,0 +1,7 @@ +import { UserRequestType, Uuid } from '@standardnotes/common' + +export type UserRequestRequestParams = { + userUuid: Uuid + requestType: UserRequestType + [additionalParam: string]: unknown +} diff --git a/packages/api/src/Domain/Request/index.ts b/packages/api/src/Domain/Request/index.ts index d6d491f82..7b4fe6eb6 100644 --- a/packages/api/src/Domain/Request/index.ts +++ b/packages/api/src/Domain/Request/index.ts @@ -5,6 +5,7 @@ export * from './Subscription/SubscriptionInviteDeclineRequestParams' export * from './Subscription/SubscriptionInviteListRequestParams' export * from './Subscription/SubscriptionInviteRequestParams' export * from './User/UserRegistrationRequestParams' +export * from './UserRequest/UserRequestRequestParams' export * from './WebSocket/WebSocketConnectionTokenRequestParams' export * from './Workspace/WorkspaceCreationRequestParams' export * from './Workspace/WorkspaceInvitationAcceptingRequestParams' diff --git a/packages/api/src/Domain/Response/User/UserDeletionResponse.ts b/packages/api/src/Domain/Response/User/UserDeletionResponse.ts new file mode 100644 index 000000000..bb783f443 --- /dev/null +++ b/packages/api/src/Domain/Response/User/UserDeletionResponse.ts @@ -0,0 +1,9 @@ +import { Either } from '@standardnotes/common' +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' + +import { UserDeletionResponseBody } from './UserDeletionResponseBody' + +export interface UserDeletionResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/User/UserDeletionResponseBody.ts b/packages/api/src/Domain/Response/User/UserDeletionResponseBody.ts new file mode 100644 index 000000000..cfb8b7220 --- /dev/null +++ b/packages/api/src/Domain/Response/User/UserDeletionResponseBody.ts @@ -0,0 +1,3 @@ +export type UserDeletionResponseBody = { + message: string +} diff --git a/packages/api/src/Domain/Response/UserRequest/UserRequestResponse.ts b/packages/api/src/Domain/Response/UserRequest/UserRequestResponse.ts new file mode 100644 index 000000000..cc58c63c1 --- /dev/null +++ b/packages/api/src/Domain/Response/UserRequest/UserRequestResponse.ts @@ -0,0 +1,9 @@ +import { Either } from '@standardnotes/common' + +import { HttpErrorResponseBody } from '../../Http/HttpErrorResponseBody' +import { HttpResponse } from '../../Http/HttpResponse' +import { UserRequestResponseBody } from './UserRequestResponseBody' + +export interface UserRequestResponse extends HttpResponse { + data: Either +} diff --git a/packages/api/src/Domain/Response/UserRequest/UserRequestResponseBody.ts b/packages/api/src/Domain/Response/UserRequest/UserRequestResponseBody.ts new file mode 100644 index 000000000..dcc19cfbb --- /dev/null +++ b/packages/api/src/Domain/Response/UserRequest/UserRequestResponseBody.ts @@ -0,0 +1,3 @@ +export type UserRequestResponseBody = { + success: boolean +} diff --git a/packages/api/src/Domain/Response/index.ts b/packages/api/src/Domain/Response/index.ts index 7bb95761a..b17f58901 100644 --- a/packages/api/src/Domain/Response/index.ts +++ b/packages/api/src/Domain/Response/index.ts @@ -8,8 +8,12 @@ export * from './Subscription/SubscriptionInviteListResponse' export * from './Subscription/SubscriptionInviteListResponseBody' export * from './Subscription/SubscriptionInviteResponse' export * from './Subscription/SubscriptionInviteResponseBody' +export * from './User/UserDeletionResponse' +export * from './User/UserDeletionResponseBody' export * from './User/UserRegistrationResponse' export * from './User/UserRegistrationResponseBody' +export * from './UserRequest/UserRequestResponse' +export * from './UserRequest/UserRequestResponseBody' export * from './WebSocket/WebSocketConnectionTokenResponse' export * from './WebSocket/WebSocketConnectionTokenResponseBody' export * from './Workspace/WorkspaceCreationResponse' diff --git a/packages/api/src/Domain/Server/User/Paths.ts b/packages/api/src/Domain/Server/User/Paths.ts index 5b9a09fc5..00e735241 100644 --- a/packages/api/src/Domain/Server/User/Paths.ts +++ b/packages/api/src/Domain/Server/User/Paths.ts @@ -1,5 +1,8 @@ +import { Uuid } from '@standardnotes/common' + const UserPaths = { register: '/v1/users', + deleteAccount: (userUuid: Uuid) => `/v1/users/${userUuid}`, } export const Paths = { diff --git a/packages/api/src/Domain/Server/User/UserServer.spec.ts b/packages/api/src/Domain/Server/User/UserServer.spec.ts index 43251cae7..38f890388 100644 --- a/packages/api/src/Domain/Server/User/UserServer.spec.ts +++ b/packages/api/src/Domain/Server/User/UserServer.spec.ts @@ -1,7 +1,7 @@ import { ProtocolVersion } from '@standardnotes/common' import { ApiVersion } from '../../Api' import { HttpServiceInterface } from '../../Http' -import { UserRegistrationResponse } from '../../Response' +import { UserDeletionResponse, UserRegistrationResponse } from '../../Response' import { UserServer } from './UserServer' describe('UserServer', () => { @@ -14,6 +14,9 @@ describe('UserServer', () => { httpService.post = jest.fn().mockReturnValue({ data: { user: { email: 'test@test.te', uuid: '1-2-3' } }, } as jest.Mocked) + httpService.delete = jest.fn().mockReturnValue({ + data: { message: 'Success' }, + } as jest.Mocked) }) it('should register a user', async () => { @@ -36,4 +39,16 @@ describe('UserServer', () => { }, }) }) + + it('should delete a user', async () => { + const response = await createServer().deleteAccount({ + userUuid: '1-2-3', + }) + + expect(response).toEqual({ + data: { + message: 'Success', + }, + }) + }) }) diff --git a/packages/api/src/Domain/Server/User/UserServer.ts b/packages/api/src/Domain/Server/User/UserServer.ts index ca8f796f7..a18a3e8d3 100644 --- a/packages/api/src/Domain/Server/User/UserServer.ts +++ b/packages/api/src/Domain/Server/User/UserServer.ts @@ -1,5 +1,7 @@ import { HttpServiceInterface } from '../../Http/HttpServiceInterface' +import { UserDeletionRequestParams } from '../../Request/User/UserDeletionRequestParams' import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams' +import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse' import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse' import { Paths } from './Paths' import { UserServerInterface } from './UserServerInterface' @@ -7,6 +9,12 @@ import { UserServerInterface } from './UserServerInterface' export class UserServer implements UserServerInterface { constructor(private httpService: HttpServiceInterface) {} + async deleteAccount(params: UserDeletionRequestParams): Promise { + const response = await this.httpService.delete(Paths.v1.deleteAccount(params.userUuid), params) + + return response as UserDeletionResponse + } + async register(params: UserRegistrationRequestParams): Promise { const response = await this.httpService.post(Paths.v1.register, params) diff --git a/packages/api/src/Domain/Server/User/UserServerInterface.ts b/packages/api/src/Domain/Server/User/UserServerInterface.ts index e531f38d9..67c796861 100644 --- a/packages/api/src/Domain/Server/User/UserServerInterface.ts +++ b/packages/api/src/Domain/Server/User/UserServerInterface.ts @@ -1,6 +1,9 @@ +import { UserDeletionRequestParams } from '../../Request/User/UserDeletionRequestParams' import { UserRegistrationRequestParams } from '../../Request/User/UserRegistrationRequestParams' +import { UserDeletionResponse } from '../../Response/User/UserDeletionResponse' import { UserRegistrationResponse } from '../../Response/User/UserRegistrationResponse' export interface UserServerInterface { register(params: UserRegistrationRequestParams): Promise + deleteAccount(params: UserDeletionRequestParams): Promise } diff --git a/packages/api/src/Domain/Server/UserRequest/Paths.ts b/packages/api/src/Domain/Server/UserRequest/Paths.ts new file mode 100644 index 000000000..dde538580 --- /dev/null +++ b/packages/api/src/Domain/Server/UserRequest/Paths.ts @@ -0,0 +1,11 @@ +import { Uuid } from '@standardnotes/common' + +const UserRequestPaths = { + submitUserRequest: (userUuid: Uuid) => `/v1/users/${userUuid}/requests`, +} + +export const Paths = { + v1: { + ...UserRequestPaths, + }, +} diff --git a/packages/api/src/Domain/Server/UserRequest/UserRequestServer.spec.ts b/packages/api/src/Domain/Server/UserRequest/UserRequestServer.spec.ts new file mode 100644 index 000000000..aec4210d7 --- /dev/null +++ b/packages/api/src/Domain/Server/UserRequest/UserRequestServer.spec.ts @@ -0,0 +1,32 @@ +import { UserRequestType } from '@standardnotes/common' + +import { HttpServiceInterface } from '../../Http' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' + +import { UserRequestServer } from './UserRequestServer' + +describe('UserRequestServer', () => { + let httpService: HttpServiceInterface + + const createServer = () => new UserRequestServer(httpService) + + beforeEach(() => { + httpService = {} as jest.Mocked + httpService.post = jest.fn().mockReturnValue({ + data: { success: true }, + } as jest.Mocked) + }) + + it('should submit a user request', async () => { + const response = await createServer().submitUserRequest({ + userUuid: '1-2-3', + requestType: UserRequestType.ExitDiscount, + }) + + expect(response).toEqual({ + data: { + success: true, + }, + }) + }) +}) diff --git a/packages/api/src/Domain/Server/UserRequest/UserRequestServer.ts b/packages/api/src/Domain/Server/UserRequest/UserRequestServer.ts new file mode 100644 index 000000000..1c8273e14 --- /dev/null +++ b/packages/api/src/Domain/Server/UserRequest/UserRequestServer.ts @@ -0,0 +1,16 @@ +import { HttpServiceInterface } from '../../Http/HttpServiceInterface' +import { UserRequestRequestParams } from '../../Request/UserRequest/UserRequestRequestParams' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' + +import { Paths } from './Paths' +import { UserRequestServerInterface } from './UserRequestServerInterface' + +export class UserRequestServer implements UserRequestServerInterface { + constructor(private httpService: HttpServiceInterface) {} + + async submitUserRequest(params: UserRequestRequestParams): Promise { + const response = await this.httpService.post(Paths.v1.submitUserRequest(params.userUuid), params) + + return response as UserRequestResponse + } +} diff --git a/packages/api/src/Domain/Server/UserRequest/UserRequestServerInterface.ts b/packages/api/src/Domain/Server/UserRequest/UserRequestServerInterface.ts new file mode 100644 index 000000000..0a06d37d5 --- /dev/null +++ b/packages/api/src/Domain/Server/UserRequest/UserRequestServerInterface.ts @@ -0,0 +1,6 @@ +import { UserRequestRequestParams } from '../../Request/UserRequest/UserRequestRequestParams' +import { UserRequestResponse } from '../../Response/UserRequest/UserRequestResponse' + +export interface UserRequestServerInterface { + submitUserRequest(params: UserRequestRequestParams): Promise +} diff --git a/packages/api/src/Domain/Server/index.ts b/packages/api/src/Domain/Server/index.ts index 36bbc562a..390d4805e 100644 --- a/packages/api/src/Domain/Server/index.ts +++ b/packages/api/src/Domain/Server/index.ts @@ -2,6 +2,8 @@ export * from './Subscription/SubscriptionServer' export * from './Subscription/SubscriptionServerInterface' export * from './User/UserServer' export * from './User/UserServerInterface' +export * from './UserRequest/UserRequestServer' +export * from './UserRequest/UserRequestServerInterface' export * from './WebSocket/WebSocketServer' export * from './WebSocket/WebSocketServerInterface' export * from './Workspace/WorkspaceServer' diff --git a/packages/encryption/package.json b/packages/encryption/package.json index 1e90c21a3..c0016370f 100644 --- a/packages/encryption/package.json +++ b/packages/encryption/package.json @@ -35,7 +35,7 @@ "typescript": "*" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/models": "workspace:*", "@standardnotes/responses": "workspace:*", "@standardnotes/sncrypto-common": "workspace:*", diff --git a/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts b/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts index 89516d228..5db3962a3 100644 --- a/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts +++ b/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts @@ -1,54 +1,67 @@ -import { ProtocolVersion } from '@standardnotes/common' +import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common' import { BackupFile, DecryptedPayloadInterface, EncryptedPayloadInterface, ItemContent, + ItemsKeyInterface, RootKeyInterface, } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' + import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams' import { KeyedDecryptionSplit } from '../../Split/KeyedDecryptionSplit' import { KeyedEncryptionSplit } from '../../Split/KeyedEncryptionSplit' export interface EncryptionProviderInterface { encryptSplitSingle(split: KeyedEncryptionSplit): Promise - encryptSplit(split: KeyedEncryptionSplit): Promise - decryptSplitSingle< C extends ItemContent = ItemContent, P extends DecryptedPayloadInterface = DecryptedPayloadInterface, >( split: KeyedDecryptionSplit, ): Promise

- decryptSplit< C extends ItemContent = ItemContent, P extends DecryptedPayloadInterface = DecryptedPayloadInterface, >( split: KeyedDecryptionSplit, ): Promise<(P | EncryptedPayloadInterface)[]> - hasRootKeyEncryptionSource(): boolean - getKeyEmbeddedKeyParams(key: EncryptedPayloadInterface): SNRootKeyParams | undefined - computeRootKey(password: string, keyParams: SNRootKeyParams): Promise - - /** - * @returns The versions that this library supports. - */ supportedVersions(): ProtocolVersion[] - getUserVersion(): ProtocolVersion | undefined - - /** - * Decrypts a backup file using user-inputted password - * @param password - The raw user password associated with this backup file - */ decryptBackupFile( file: BackupFile, password?: string, ): Promise + hasAccount(): boolean + decryptErroredPayloads(): Promise + deleteWorkspaceSpecificKeyStateFromDevice(): Promise + hasPasscode(): boolean + createRootKey( + identifier: string, + password: string, + origination: KeyParamsOrigination, + version?: ProtocolVersion, + ): Promise + setNewRootKeyWrapper(wrappingKey: RootKeyInterface): Promise + removePasscode(): Promise + validateAccountPassword(password: string): Promise< + | { + valid: true + artifacts: { + rootKey: RootKeyInterface + } + } + | { + valid: boolean + } + > + createNewItemsKeyWithRollback(): Promise<() => Promise> + reencryptItemsKeys(): Promise + getSureDefaultItemsKey(): ItemsKeyInterface + getRootKeyParams(): Promise } diff --git a/packages/features/package.json b/packages/features/package.json index 13977121c..fc6ae947d 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -26,7 +26,7 @@ }, "dependencies": { "@standardnotes/auth": "^3.19.4", - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/security": "^1.2.0", "reflect-metadata": "^0.1.13" }, diff --git a/packages/filepicker/package.json b/packages/filepicker/package.json index 6fe46a1d4..2e4abaaa2 100644 --- a/packages/filepicker/package.json +++ b/packages/filepicker/package.json @@ -32,7 +32,7 @@ "typescript": "*" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/files": "workspace:*", "@standardnotes/utils": "workspace:*", "@types/wicg-file-system-access": "^2020.9.5", diff --git a/packages/files/package.json b/packages/files/package.json index 81357b6cf..7bef32a27 100644 --- a/packages/files/package.json +++ b/packages/files/package.json @@ -30,7 +30,7 @@ "ts-jest": "^28.0.5" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/encryption": "workspace:*", "@standardnotes/models": "workspace:*", "@standardnotes/responses": "workspace:*", diff --git a/packages/models/package.json b/packages/models/package.json index 55ee7729c..431e2287a 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -32,7 +32,7 @@ "typescript": "*" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/features": "workspace:*", "@standardnotes/responses": "workspace:*", "@standardnotes/utils": "workspace:*", diff --git a/packages/responses/package.json b/packages/responses/package.json index 838766148..ebd51d5e9 100644 --- a/packages/responses/package.json +++ b/packages/responses/package.json @@ -31,7 +31,7 @@ "ts-jest": "^28.0.5" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/features": "workspace:*", "@standardnotes/security": "^1.1.0", "reflect-metadata": "^0.1.13" diff --git a/packages/services/jest.config.js b/packages/services/jest.config.js index 2f386801f..9460b4b2a 100644 --- a/packages/services/jest.config.js +++ b/packages/services/jest.config.js @@ -10,10 +10,10 @@ module.exports = { }, coverageThreshold: { global: { - branches: 9, - functions: 10, - lines: 16, - statements: 16 + branches: 6, + functions: 9, + lines: 13, + statements: 13 } } }; diff --git a/packages/services/package.json b/packages/services/package.json index 27fc05765..ad6a9cf83 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -26,12 +26,13 @@ "dependencies": { "@standardnotes/api": "workspace:^", "@standardnotes/auth": "^3.19.4", - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/encryption": "workspace:^", "@standardnotes/files": "workspace:^", "@standardnotes/models": "workspace:^", "@standardnotes/responses": "workspace:*", "@standardnotes/security": "^1.2.0", + "@standardnotes/sncrypto-common": "workspace:^", "@standardnotes/utils": "workspace:*", "reflect-metadata": "^0.1.13" }, diff --git a/packages/services/src/Domain/Application/ApplicationInterface.ts b/packages/services/src/Domain/Application/ApplicationInterface.ts index 12e453aad..84afe8fdb 100644 --- a/packages/services/src/Domain/Application/ApplicationInterface.ts +++ b/packages/services/src/Domain/Application/ApplicationInterface.ts @@ -16,7 +16,7 @@ import { StorageValueModes } from '../Storage/StorageTypes' import { DeinitMode } from './DeinitMode' import { DeinitSource } from './DeinitSource' -import { UserClientInterface } from './UserClientInterface' +import { UserClientInterface } from '../User/UserClientInterface' export interface ApplicationInterface { deinit(mode: DeinitMode, source: DeinitSource): void diff --git a/packages/snjs/lib/Services/Challenge/Challenge.ts b/packages/services/src/Domain/Challenge/Challenge.ts similarity index 92% rename from packages/snjs/lib/Services/Challenge/Challenge.ts rename to packages/services/src/Domain/Challenge/Challenge.ts index 1fad6dafb..31dd2d5fd 100644 --- a/packages/snjs/lib/Services/Challenge/Challenge.ts +++ b/packages/services/src/Domain/Challenge/Challenge.ts @@ -1,6 +1,9 @@ -import { ChallengeModalTitle, ChallengeStrings } from '../Api/Messages' import { assertUnreachable } from '@standardnotes/utils' -import { ChallengeValidation, ChallengeReason, ChallengeInterface, ChallengePrompt } from '@standardnotes/services' +import { ChallengeModalTitle, ChallengeStrings } from '../Strings/Messages' +import { ChallengeInterface } from './ChallengeInterface' +import { ChallengePrompt } from './Prompt/ChallengePrompt' +import { ChallengeReason } from './Types/ChallengeReason' +import { ChallengeValidation } from './Types/ChallengeValidation' /** * A challenge is a stateless description of what the client needs to provide diff --git a/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts b/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts index 6f5f5b269..292216bc7 100644 --- a/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts +++ b/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts @@ -1,3 +1,5 @@ +import { RootKeyInterface } from '@standardnotes/models' + import { AbstractService } from '../Service/AbstractService' import { ChallengeInterface } from './ChallengeInterface' import { ChallengePromptInterface } from './Prompt/ChallengePromptInterface' @@ -10,7 +12,6 @@ export interface ChallengeServiceInterface extends AbstractService { * For non-validated challenges, will resolve when the first value is submitted. */ promptForChallengeResponse(challenge: ChallengeInterface): Promise - createChallenge( prompts: ChallengePromptInterface[], reason: ChallengeReason, @@ -18,6 +19,19 @@ export interface ChallengeServiceInterface extends AbstractService { heading?: string, subheading?: string, ): ChallengeInterface - completeChallenge(challenge: ChallengeInterface): void + getWrappingKeyIfApplicable(passcode?: string): Promise< + | { + canceled?: undefined + wrappingKey?: undefined + } + | { + canceled: boolean + wrappingKey?: undefined + } + | { + wrappingKey: RootKeyInterface + canceled?: undefined + } + > } diff --git a/packages/services/src/Domain/Challenge/index.ts b/packages/services/src/Domain/Challenge/index.ts index 283e0d1a2..20cf444bc 100644 --- a/packages/services/src/Domain/Challenge/index.ts +++ b/packages/services/src/Domain/Challenge/index.ts @@ -1,3 +1,4 @@ +export * from './Challenge' export * from './ChallengeInterface' export * from './ChallengeResponseInterface' export * from './ChallengeServiceInterface' diff --git a/packages/services/src/Domain/Item/ItemManagerInterface.ts b/packages/services/src/Domain/Item/ItemManagerInterface.ts index 21869a979..0a7058a44 100644 --- a/packages/services/src/Domain/Item/ItemManagerInterface.ts +++ b/packages/services/src/Domain/Item/ItemManagerInterface.ts @@ -44,50 +44,22 @@ export interface ItemManagerInterface extends AbstractService { contentType: ContentType | ContentType[], callback: ItemManagerChangeObserverCallback, ): () => void - - /** - * Marks the item as deleted and needing sync. - */ setItemToBeDeleted(itemToLookupUuidFor: DecryptedItemInterface, source?: PayloadEmitSource): Promise - setItemsToBeDeleted(itemsToLookupUuidsFor: DecryptedItemInterface[]): Promise - setItemsDirty( itemsToLookupUuidsFor: DecryptedItemInterface[], isUserModified?: boolean, ): Promise - get items(): DecryptedItemInterface[] - - /** - * Inserts the item as-is by reading its payload value. This function will not - * modify item in any way (such as marking it as dirty). It is up to the caller - * to pass in a dirtied item if that is their intention. - */ insertItem(item: DecryptedItemInterface): Promise - emitItemFromPayload(payload: DecryptedPayloadInterface, source: PayloadEmitSource): Promise - getItems(contentType: ContentType | ContentType[]): T[] - - /** - * Returns all non-deleted items keys - */ getDisplayableItemsKeys(): ItemsKeyInterface[] - - /** - * Creates an item and conditionally maps it and marks it as dirty. - * @param needsSync - Whether to mark the item as needing sync - */ createItem( contentType: ContentType, content: C, needsSync?: boolean, ): Promise - - /** - * Create an unmanaged item that can later be inserted via `insertItem` - */ createTemplateItem< C extends ItemContent = ItemContent, I extends DecryptedItemInterface = DecryptedItemInterface, @@ -96,12 +68,6 @@ export interface ItemManagerInterface extends AbstractService { content?: C, override?: Partial>, ): I - - /** - * Consumers wanting to modify an item should run it through this block, - * so that data is properly mapped through our function, and latest state - * is properly reconciled. - */ changeItem< M extends DecryptedItemMutator = DecryptedItemMutator, I extends DecryptedItemInterface = DecryptedItemInterface, @@ -112,7 +78,6 @@ export interface ItemManagerInterface extends AbstractService { emitSource?: PayloadEmitSource, payloadSourceKey?: string, ): Promise - changeItemsKey( itemToLookupUuidFor: ItemsKeyInterface, mutate: (mutator: ItemsKeyMutatorInterface) => void, @@ -120,16 +85,15 @@ export interface ItemManagerInterface extends AbstractService { emitSource?: PayloadEmitSource, payloadSourceKey?: string, ): Promise - itemsMatchingPredicate( contentType: ContentType, predicate: PredicateInterface, ): T[] - itemsMatchingPredicates( contentType: ContentType, predicates: PredicateInterface[], ): T[] - subItemsMatchingPredicates(items: T[], predicates: PredicateInterface[]): T[] + removeAllItemsFromMemory(): Promise + getDirtyItems(): (DecryptedItemInterface | DeletedItemInterface)[] } diff --git a/packages/services/src/Domain/Protection/MobileUnlockTiming.ts b/packages/services/src/Domain/Protection/MobileUnlockTiming.ts new file mode 100644 index 000000000..dfacd6ce6 --- /dev/null +++ b/packages/services/src/Domain/Protection/MobileUnlockTiming.ts @@ -0,0 +1,4 @@ +export enum MobileUnlockTiming { + Immediately = 'immediately', + OnQuit = 'on-quit', +} diff --git a/packages/snjs/lib/Services/Protection/ClientInterface.ts b/packages/services/src/Domain/Protection/ProtectionClientInterface.ts similarity index 64% rename from packages/snjs/lib/Services/Protection/ClientInterface.ts rename to packages/services/src/Domain/Protection/ProtectionClientInterface.ts index fc9da0c2a..a6f5fd1c0 100644 --- a/packages/snjs/lib/Services/Protection/ClientInterface.ts +++ b/packages/services/src/Domain/Protection/ProtectionClientInterface.ts @@ -1,6 +1,7 @@ -import { ChallengeReason } from '@standardnotes/services' import { DecryptedItem } from '@standardnotes/models' -import { TimingDisplayOption, MobileUnlockTiming } from './MobileUnlockTiming' +import { ChallengeReason } from '../Challenge' +import { MobileUnlockTiming } from './MobileUnlockTiming' +import { TimingDisplayOption } from './TimingDisplayOption' export interface ProtectionsClientInterface { authorizeProtectedActionForItems(files: T[], challengeReason: ChallengeReason): Promise @@ -16,4 +17,11 @@ export interface ProtectionsClientInterface { hasBiometricsEnabled(): boolean enableBiometrics(): boolean disableBiometrics(): Promise + authorizeAction( + reason: ChallengeReason, + dto: { fallBackToAccountPassword: boolean; requireAccountPassword: boolean; forcePrompt: boolean }, + ): Promise + authorizeAddingPasscode(): Promise + authorizeRemovingPasscode(): Promise + authorizeChangingPasscode(): Promise } diff --git a/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts b/packages/services/src/Domain/Protection/TimingDisplayOption.ts similarity index 53% rename from packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts rename to packages/services/src/Domain/Protection/TimingDisplayOption.ts index 3939c7f3b..6965eaab4 100644 --- a/packages/snjs/lib/Services/Protection/MobileUnlockTiming.ts +++ b/packages/services/src/Domain/Protection/TimingDisplayOption.ts @@ -1,7 +1,4 @@ -export enum MobileUnlockTiming { - Immediately = 'immediately', - OnQuit = 'on-quit', -} +import { MobileUnlockTiming } from './MobileUnlockTiming' export type TimingDisplayOption = { title: string diff --git a/packages/services/src/Domain/Session/SessionManagerResponse.ts b/packages/services/src/Domain/Session/SessionManagerResponse.ts new file mode 100644 index 000000000..cfd2302af --- /dev/null +++ b/packages/services/src/Domain/Session/SessionManagerResponse.ts @@ -0,0 +1,9 @@ +import { AnyKeyParamsContent } from '@standardnotes/common' +import { RootKeyInterface } from '@standardnotes/models' +import { HttpResponse } from '@standardnotes/responses' + +export type SessionManagerResponse = { + response: HttpResponse + rootKey?: RootKeyInterface + keyParams?: AnyKeyParamsContent +} diff --git a/packages/services/src/Domain/Session/SessionsClientInterface.ts b/packages/services/src/Domain/Session/SessionsClientInterface.ts new file mode 100644 index 000000000..f5ed7e53b --- /dev/null +++ b/packages/services/src/Domain/Session/SessionsClientInterface.ts @@ -0,0 +1,34 @@ +import { UserRegistrationResponseBody } from '@standardnotes/api' +import { ProtocolVersion } from '@standardnotes/common' +import { RootKeyInterface } from '@standardnotes/models' +import { ClientDisplayableError, HttpResponse, SignInResponse, User } from '@standardnotes/responses' +import { Base64String } from '@standardnotes/sncrypto-common' + +import { SessionManagerResponse } from './SessionManagerResponse' + +export interface SessionsClientInterface { + createDemoShareToken(): Promise + populateSessionFromDemoShareToken(token: Base64String): Promise + getUser(): User | undefined + register(email: string, password: string, ephemeral: boolean): Promise + signIn( + email: string, + password: string, + strict: boolean, + ephemeral: boolean, + minAllowedVersion?: ProtocolVersion, + ): Promise + getSureUser(): User + bypassChecksAndSignInWithRootKey( + email: string, + rootKey: RootKeyInterface, + ephemeral: boolean, + ): Promise + signOut(): Promise + changeCredentials(parameters: { + currentServerPassword: string + newRootKey: RootKeyInterface + wrappingKey?: RootKeyInterface + newEmail?: string + }): Promise +} diff --git a/packages/services/src/Domain/Storage/StorageServiceInterface.ts b/packages/services/src/Domain/Storage/StorageServiceInterface.ts index 2e814bf50..9faba6ce2 100644 --- a/packages/services/src/Domain/Storage/StorageServiceInterface.ts +++ b/packages/services/src/Domain/Storage/StorageServiceInterface.ts @@ -1,16 +1,15 @@ -import { PayloadInterface, RootKeyInterface } from '@standardnotes/models' -import { StorageValueModes } from './StorageTypes' +import { FullyFormedPayloadInterface, PayloadInterface, RootKeyInterface } from '@standardnotes/models' +import { StoragePersistencePolicies, StorageValueModes } from './StorageTypes' export interface StorageServiceInterface { getValue(key: string, mode?: StorageValueModes, defaultValue?: T): T - canDecryptWithKey(key: RootKeyInterface): Promise - savePayload(payload: PayloadInterface): Promise - savePayloads(decryptedPayloads: PayloadInterface[]): Promise - setValue(key: string, value: unknown, mode?: StorageValueModes): void - removeValue(key: string, mode?: StorageValueModes): Promise + setPersistencePolicy(persistencePolicy: StoragePersistencePolicies): Promise + clearAllData(): Promise + forceDeletePayloads(payloads: FullyFormedPayloadInterface[]): Promise + clearAllPayloads(): Promise } diff --git a/packages/snjs/lib/Strings/Info.ts b/packages/services/src/Domain/Strings/InfoStrings.ts similarity index 100% rename from packages/snjs/lib/Strings/Info.ts rename to packages/services/src/Domain/Strings/InfoStrings.ts diff --git a/packages/snjs/lib/Services/Api/Messages.ts b/packages/services/src/Domain/Strings/Messages.ts similarity index 100% rename from packages/snjs/lib/Services/Api/Messages.ts rename to packages/services/src/Domain/Strings/Messages.ts diff --git a/packages/services/src/Domain/Sync/SyncServiceInterface.ts b/packages/services/src/Domain/Sync/SyncServiceInterface.ts index f332fe3ae..7d5047db5 100644 --- a/packages/services/src/Domain/Sync/SyncServiceInterface.ts +++ b/packages/services/src/Domain/Sync/SyncServiceInterface.ts @@ -1,7 +1,14 @@ /* istanbul ignore file */ +import { FullyFormedPayloadInterface } from '@standardnotes/models' import { SyncOptions } from './SyncOptions' export interface SyncServiceInterface { sync(options?: Partial): Promise + resetSyncState(): void + markAllItemsAsNeedingSyncAndPersist(): Promise + downloadFirstSync(waitTimeOnFailureMs: number, otherSyncOptions?: Partial): Promise + persistPayloads(payloads: FullyFormedPayloadInterface[]): Promise + lockSyncing(): void + unlockSyncing(): void } diff --git a/packages/services/src/Domain/Application/UserClientInterface.ts b/packages/services/src/Domain/User/UserClientInterface.ts similarity index 75% rename from packages/services/src/Domain/Application/UserClientInterface.ts rename to packages/services/src/Domain/User/UserClientInterface.ts index 0533679c9..4901656b4 100644 --- a/packages/services/src/Domain/Application/UserClientInterface.ts +++ b/packages/services/src/Domain/User/UserClientInterface.ts @@ -1,9 +1,9 @@ -import { DeinitSource } from './DeinitSource' +import { DeinitSource } from '../Application/DeinitSource' + export interface UserClientInterface { deleteAccount(): Promise<{ error: boolean message?: string }> - signOut(force?: boolean, source?: DeinitSource): Promise } diff --git a/packages/snjs/lib/Services/User/UserService.ts b/packages/services/src/Domain/User/UserService.ts similarity index 87% rename from packages/snjs/lib/Services/User/UserService.ts rename to packages/services/src/Domain/User/UserService.ts index 3d7f2decc..17d8f1a9f 100644 --- a/packages/snjs/lib/Services/User/UserService.ts +++ b/packages/services/src/Domain/User/UserService.ts @@ -1,32 +1,29 @@ -import { Challenge } from '../Challenge' -import { ChallengeService } from '../Challenge/ChallengeService' -import { SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' +import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' import { HttpResponse, SignInResponse, User } from '@standardnotes/responses' -import { ItemManager } from '@Lib/Services/Items/ItemManager' import { KeyParamsOrigination } from '@standardnotes/common' +import { UuidGenerator } from '@standardnotes/utils' +import { UserApiServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api' + +import * as Messages from '../Strings/Messages' +import { InfoStrings } from '../Strings/InfoStrings' +import { SyncServiceInterface } from '../Sync/SyncServiceInterface' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { AlertService } from '../Alert/AlertService' import { - AbstractService, - AlertService, + Challenge, ChallengePrompt, ChallengeReason, + ChallengeServiceInterface, ChallengeValidation, - DeinitSource, - InternalEventBusInterface, - UserClientInterface, - StoragePersistencePolicies, - EncryptionService, -} from '@standardnotes/services' -import { SNApiService } from './../Api/ApiService' -import { SNProtectionService } from '../Protection/ProtectionService' -import { SNSessionManager, MINIMUM_PASSWORD_LENGTH } from '../Session/SessionManager' -import { DiskStorageService } from '@Lib/Services/Storage/DiskStorageService' -import { SNSyncService } from '../Sync/SyncService' -import { Strings } from '../../Strings/index' -import { UuidGenerator } from '@standardnotes/utils' -import * as Messages from '../Api/Messages' -import { UserRegistrationResponseBody } from '@standardnotes/api' - -const MINIMUM_PASSCODE_LENGTH = 1 +} from '../Challenge' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { AbstractService } from '../Service/AbstractService' +import { UserClientInterface } from './UserClientInterface' +import { DeinitSource } from '../Application/DeinitSource' +import { StoragePersistencePolicies } from '../Storage/StorageTypes' +import { SessionsClientInterface } from '../Session/SessionsClientInterface' +import { ProtectionsClientInterface } from '../Protection/ProtectionClientInterface' export type CredentialsChangeFunctionResponse = { error?: { message: string } } export type AccountServiceResponse = HttpResponse @@ -44,16 +41,19 @@ export class UserService extends AbstractService private signingIn = false private registering = false + private readonly MINIMUM_PASSCODE_LENGTH = 1 + private readonly MINIMUM_PASSWORD_LENGTH = 8 + constructor( - private sessionManager: SNSessionManager, - private syncService: SNSyncService, - private storageService: DiskStorageService, - private itemManager: ItemManager, - private protocolService: EncryptionService, + private sessionManager: SessionsClientInterface, + private syncService: SyncServiceInterface, + private storageService: StorageServiceInterface, + private itemManager: ItemManagerInterface, + private protocolService: EncryptionProviderInterface, private alertService: AlertService, - private challengeService: ChallengeService, - private protectionService: SNProtectionService, - private apiService: SNApiService, + private challengeService: ChallengeServiceInterface, + private protectionService: ProtectionsClientInterface, + private userApiService: UserApiServiceInterface, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) @@ -69,7 +69,7 @@ export class UserService extends AbstractService ;(this.alertService as unknown) = undefined ;(this.challengeService as unknown) = undefined ;(this.protectionService as unknown) = undefined - ;(this.apiService as unknown) = undefined + ;(this.userApiService as unknown) = undefined } /** @@ -204,7 +204,9 @@ export class UserService extends AbstractService }> { if ( !(await this.protectionService.authorizeAction(ChallengeReason.DeleteAccount, { + fallBackToAccountPassword: true, requireAccountPassword: true, + forcePrompt: false, })) ) { return { @@ -214,17 +216,17 @@ export class UserService extends AbstractService } const uuid = this.sessionManager.getSureUser().uuid - const response = await this.apiService.deleteAccount(uuid) - if (response.error) { + const response = await this.userApiService.deleteAccount(uuid) + if (response.data.error) { return { error: true, - message: response.error.message, + message: response.data.error.message, } } await this.signOut(true) - void this.alertService.alert(Strings.Info.AccountDeleted) + void this.alertService.alert(InfoStrings.AccountDeleted) return { error: false, @@ -239,7 +241,11 @@ export class UserService extends AbstractService public async correctiveSignIn(rootKey: SNRootKey): Promise { this.lockSyncing() - const response = await this.sessionManager.bypassChecksAndSignInWithRootKey(rootKey.keyParams.identifier, rootKey) + const response = await this.sessionManager.bypassChecksAndSignInWithRootKey( + rootKey.keyParams.identifier, + rootKey, + false, + ) if (!response.error) { await this.notifyEvent(AccountEvent.SignedInOrRegistered) @@ -381,7 +387,7 @@ export class UserService extends AbstractService } public async addPasscode(passcode: string): Promise { - if (passcode.length < MINIMUM_PASSCODE_LENGTH) { + if (passcode.length < this.MINIMUM_PASSCODE_LENGTH) { return false } if (!(await this.protectionService.authorizeAddingPasscode())) { @@ -424,7 +430,7 @@ export class UserService extends AbstractService newPasscode: string, origination = KeyParamsOrigination.PasscodeChange, ): Promise { - if (newPasscode.length < MINIMUM_PASSCODE_LENGTH) { + if (newPasscode.length < this.MINIMUM_PASSCODE_LENGTH) { return false } if (!(await this.protectionService.authorizeChangingPasscode())) { @@ -501,9 +507,9 @@ export class UserService extends AbstractService } if (parameters.newPassword !== undefined && parameters.validateNewPasswordStrength) { - if (parameters.newPassword.length < MINIMUM_PASSWORD_LENGTH) { + if (parameters.newPassword.length < this.MINIMUM_PASSWORD_LENGTH) { return { - error: Error(Messages.InsufficientPasswordMessage(MINIMUM_PASSWORD_LENGTH)), + error: Error(Messages.InsufficientPasswordMessage(this.MINIMUM_PASSWORD_LENGTH)), } } } diff --git a/packages/services/src/Domain/index.ts b/packages/services/src/Domain/index.ts index c55046011..f40af8310 100644 --- a/packages/services/src/Domain/index.ts +++ b/packages/services/src/Domain/index.ts @@ -6,7 +6,7 @@ export * from './Application/ApplicationStage' export * from './Application/DeinitCallback' export * from './Application/DeinitSource' export * from './Application/DeinitMode' -export * from './Application/UserClientInterface' +export * from './User/UserClientInterface' export * from './Application/WebApplicationInterface' export * from './Backups/BackupService' export * from './Challenge' @@ -58,8 +58,13 @@ export * from './Item/ItemRelationshipDirection' export * from './Mutator/MutatorClientInterface' export * from './Payloads/PayloadManagerInterface' export * from './Preferences/PreferenceServiceInterface' +export * from './Protection/MobileUnlockTiming' +export * from './Protection/ProtectionClientInterface' +export * from './Protection/TimingDisplayOption' export * from './Service/AbstractService' export * from './Service/ServiceInterface' +export * from './Session/SessionManagerResponse' +export * from './Session/SessionsClientInterface' export * from './Status/StatusService' export * from './Status/StatusServiceInterface' export * from './Storage/StorageKeys' @@ -67,6 +72,8 @@ export * from './Storage/InMemoryStore' export * from './Storage/KeyValueStoreInterface' export * from './Storage/StorageServiceInterface' export * from './Storage/StorageTypes' +export * from './Strings/InfoStrings' +export * from './Strings/Messages' export * from './Subscription/SubscriptionClientInterface' export * from './Subscription/SubscriptionManager' export * from './Sync/SyncMode' @@ -74,5 +81,7 @@ export * from './Sync/SyncOptions' export * from './Sync/SyncQueueStrategy' export * from './Sync/SyncServiceInterface' export * from './Sync/SyncSource' +export * from './User/UserClientInterface' +export * from './User/UserService' export * from './Workspace/WorkspaceClientInterface' export * from './Workspace/WorkspaceManager' diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index 0032fcb88..6d1a03aa1 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -8,6 +8,8 @@ import { UserApiService, UserApiServiceInterface, UserRegistrationResponseBody, + UserRequestServer, + UserRequestServerInterface, UserServer, UserServerInterface, WebSocketApiService, @@ -52,6 +54,15 @@ import { WorkspaceClientInterface, WorkspaceManager, ChallengePrompt, + Challenge, + ErrorAlertStrings, + SessionsClientInterface, + ProtectionsClientInterface, + UserService, + ProtocolUpgradeStrings, + CredentialsChangeFunctionResponse, + SessionStrings, + AccountEvent, } from '@standardnotes/services' import { FilesClientInterface } from '@standardnotes/files' import { ComputePrivateUsername } from '@standardnotes/encryption' @@ -68,7 +79,7 @@ import { ClientDisplayableError } from '@standardnotes/responses' import { SnjsVersion } from './../Version' import { SNLog } from '../Log' -import { Challenge, ChallengeResponse, ListedClientInterface } from '../Services' +import { ChallengeResponse, ListedClientInterface } from '../Services' import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions' import { ApplicationOptionsDefaults } from './Options/Defaults' @@ -112,6 +123,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli private apiService!: InternalServices.SNApiService private declare userApiService: UserApiServiceInterface private declare userServer: UserServerInterface + private declare userRequestServer: UserRequestServerInterface private declare subscriptionApiService: SubscriptionApiServiceInterface private declare subscriptionServer: SubscriptionServerInterface private declare subscriptionManager: SubscriptionClientInterface @@ -132,7 +144,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli private keyRecoveryService!: InternalServices.SNKeyRecoveryService private preferencesService!: InternalServices.SNPreferencesService private featuresService!: InternalServices.SNFeaturesService - private userService!: InternalServices.UserService + private userService!: UserService private webSocketsService!: InternalServices.SNWebSocketsService private settingsService!: InternalServices.SNSettingsService private mfaService!: InternalServices.SNMfaService @@ -235,7 +247,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return this.itemManager } - public get protections(): InternalServices.ProtectionsClientInterface { + public get protections(): ProtectionsClientInterface { return this.protectionService } @@ -255,7 +267,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return this.mutatorService } - public get sessions(): InternalServices.SessionsClientInterface { + public get sessions(): SessionsClientInterface { return this.sessionManager } @@ -347,8 +359,8 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli await this.diskStorageService.decryptStorage() } catch (_error) { void this.alertService.alert( - InternalServices.ErrorAlertStrings.StorageDecryptErrorBody, - InternalServices.ErrorAlertStrings.StorageDecryptErrorTitle, + ErrorAlertStrings.StorageDecryptErrorBody, + ErrorAlertStrings.StorageDecryptErrorTitle, ) } } @@ -628,12 +640,12 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli const result = await this.userService.performProtocolUpgrade() if (result.success) { if (this.hasAccount()) { - void this.alertService.alert(InternalServices.ProtocolUpgradeStrings.SuccessAccount) + void this.alertService.alert(ProtocolUpgradeStrings.SuccessAccount) } else { - void this.alertService.alert(InternalServices.ProtocolUpgradeStrings.SuccessPasscodeOnly) + void this.alertService.alert(ProtocolUpgradeStrings.SuccessPasscodeOnly) } } else if (result.error) { - void this.alertService.alert(InternalServices.ProtocolUpgradeStrings.Fail) + void this.alertService.alert(ProtocolUpgradeStrings.Fail) } return result } @@ -848,7 +860,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli currentPassword: string, passcode?: string, origination = Common.KeyParamsOrigination.EmailChange, - ): Promise { + ): Promise { return this.userService.changeCredentials({ currentPassword, newEmail, @@ -864,7 +876,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli passcode?: string, origination = Common.KeyParamsOrigination.PasswordChange, validateNewPasswordStrength = true, - ): Promise { + ): Promise { return this.userService.changeCredentials({ currentPassword, newPassword, @@ -886,7 +898,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli /** Keep a reference to the soon-to-be-cleared alertService */ const alertService = this.alertService await this.user.signOut(true) - void alertService.alert(InternalServices.SessionStrings.CurrentSessionRevoked) + void alertService.alert(SessionStrings.CurrentSessionRevoked) } public async validateAccountPassword(password: string): Promise { @@ -1064,6 +1076,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.createApiService() this.createHttpService() this.createUserServer() + this.createUserRequestServer() this.createUserApiService() this.createSubscriptionServer() this.createSubscriptionApiService() @@ -1111,6 +1124,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ;(this.apiService as unknown) = undefined ;(this.userApiService as unknown) = undefined ;(this.userServer as unknown) = undefined + ;(this.userRequestServer as unknown) = undefined ;(this.subscriptionApiService as unknown) = undefined ;(this.subscriptionServer as unknown) = undefined ;(this.subscriptionManager as unknown) = undefined @@ -1261,7 +1275,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } private createUserService(): void { - this.userService = new InternalServices.UserService( + this.userService = new UserService( this.sessionManager, this.syncService, this.diskStorageService, @@ -1270,17 +1284,17 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.alertService, this.challengeService, this.protectionService, - this.apiService, + this.userApiService, this.internalEventBus, ) this.serviceObservers.push( this.userService.addEventObserver(async (event, data) => { switch (event) { - case InternalServices.AccountEvent.SignedInOrRegistered: { + case AccountEvent.SignedInOrRegistered: { void this.notifyEvent(ApplicationEvent.SignedIn) break } - case InternalServices.AccountEvent.SignedOut: { + case AccountEvent.SignedOut: { await this.notifyEvent(ApplicationEvent.SignedOut) await this.prepareForDeinit() this.deinit(this.getDeinitMode(), data?.source || DeinitSource.SignOut) @@ -1308,13 +1322,17 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } private createUserApiService() { - this.userApiService = new UserApiService(this.userServer) + this.userApiService = new UserApiService(this.userServer, this.userRequestServer) } private createUserServer() { this.userServer = new UserServer(this.httpService) } + private createUserRequestServer() { + this.userRequestServer = new UserRequestServer(this.httpService) + } + private createSubscriptionApiService() { this.subscriptionApiService = new SubscriptionApiService(this.subscriptionServer) } diff --git a/packages/snjs/lib/Client/NoteViewController.ts b/packages/snjs/lib/Client/NoteViewController.ts index cb0329607..44f2ba614 100644 --- a/packages/snjs/lib/Client/NoteViewController.ts +++ b/packages/snjs/lib/Client/NoteViewController.ts @@ -1,5 +1,5 @@ -import { InfoStrings } from '../Strings/Info' import { NoteType } from '@standardnotes/features' +import { InfoStrings } from '@standardnotes/services' import { NoteMutator, SNNote, diff --git a/packages/snjs/lib/Migrations/Base.ts b/packages/snjs/lib/Migrations/Base.ts index cb319880b..64f9458fd 100644 --- a/packages/snjs/lib/Migrations/Base.ts +++ b/packages/snjs/lib/Migrations/Base.ts @@ -1,8 +1,6 @@ import { AnyKeyParamsContent } from '@standardnotes/common' import { SNLog } from '@Lib/Log' import { EncryptedPayload, EncryptedTransferPayload, isErrorDecryptingPayload } from '@standardnotes/models' -import { Challenge } from '../Services/Challenge' -import { KeychainRecoveryStrings, SessionStrings } from '../Services/Api/Messages' import { PreviousSnjsVersion1_0_0, PreviousSnjsVersion2_0_0, SnjsVersion } from '../Version' import { Migration } from '@Lib/Migrations/Migration' import { @@ -12,6 +10,9 @@ import { ChallengeValidation, ChallengeReason, ChallengePrompt, + KeychainRecoveryStrings, + SessionStrings, + Challenge, } from '@standardnotes/services' import { isNullOrUndefined } from '@standardnotes/utils' import { CreateReader } from './StorageReaders/Functions' diff --git a/packages/snjs/lib/Migrations/Migration.ts b/packages/snjs/lib/Migrations/Migration.ts index 3bcfa02af..6c47fc5d5 100644 --- a/packages/snjs/lib/Migrations/Migration.ts +++ b/packages/snjs/lib/Migrations/Migration.ts @@ -1,6 +1,11 @@ -import { Challenge } from '../Services/Challenge' import { MigrationServices } from './MigrationServices' -import { ApplicationStage, ChallengeValidation, ChallengeReason, ChallengePrompt } from '@standardnotes/services' +import { + ApplicationStage, + ChallengeValidation, + ChallengeReason, + ChallengePrompt, + Challenge, +} from '@standardnotes/services' type StageHandler = () => Promise diff --git a/packages/snjs/lib/Services/Actions/ActionsService.ts b/packages/snjs/lib/Services/Actions/ActionsService.ts index 1fd006cc3..e7ac2ca61 100644 --- a/packages/snjs/lib/Services/Actions/ActionsService.ts +++ b/packages/snjs/lib/Services/Actions/ActionsService.ts @@ -1,5 +1,5 @@ import { SNRootKey } from '@standardnotes/encryption' -import { Challenge, ChallengeService } from '../Challenge' +import { ChallengeService } from '../Challenge' import { ListedService } from '../Listed/ListedService' import { ActionResponse, HttpResponse } from '@standardnotes/responses' import { ContentType } from '@standardnotes/common' @@ -31,6 +31,7 @@ import { ChallengeReason, ChallengePrompt, EncryptionService, + Challenge, } from '@standardnotes/services' /** diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index 6277c2bf8..6907bb60f 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -13,28 +13,44 @@ import { MetaReceivedData, DiagnosticInfo, KeyValueStoreInterface, + API_MESSAGE_GENERIC_SYNC_FAIL, + API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL, + API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS, + API_MESSAGE_FAILED_ACCESS_PURCHASE, + API_MESSAGE_FAILED_CREATE_FILE_TOKEN, + API_MESSAGE_FAILED_DELETE_REVISION, + API_MESSAGE_FAILED_GET_SETTINGS, + API_MESSAGE_FAILED_LISTED_REGISTRATION, + API_MESSAGE_FAILED_OFFLINE_ACTIVATION, + API_MESSAGE_FAILED_OFFLINE_FEATURES, + API_MESSAGE_FAILED_SUBSCRIPTION_INFO, + API_MESSAGE_FAILED_UPDATE_SETTINGS, + API_MESSAGE_GENERIC_CHANGE_CREDENTIALS_FAIL, + API_MESSAGE_GENERIC_INTEGRITY_CHECK_FAIL, + API_MESSAGE_GENERIC_INVALID_LOGIN, + API_MESSAGE_GENERIC_SINGLE_ITEM_SYNC_FAIL, + API_MESSAGE_INVALID_SESSION, + API_MESSAGE_LOGIN_IN_PROGRESS, + API_MESSAGE_TOKEN_REFRESH_IN_PROGRESS, } from '@standardnotes/services' import { FilesApiInterface } from '@standardnotes/files' import { ServerSyncPushContextualPayload, SNFeatureRepo, FileContent } from '@standardnotes/models' import * as Responses from '@standardnotes/responses' -import { API_MESSAGE_FAILED_OFFLINE_ACTIVATION } from '@Lib/Services/Api/Messages' import { HttpParams, HttpRequest, HttpVerb, SNHttpService } from './HttpService' import { isUrlFirstParty, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts' import { Paths } from './Paths' import { Session } from '../Session/Sessions/Session' import { TokenSession } from '../Session/Sessions/TokenSession' import { DiskStorageService } from '../Storage/DiskStorageService' -import { UserServerInterface } from '../User/UserServerInterface' +import { HttpResponseMeta } from '@standardnotes/api' import { UuidString } from '../../Types/UuidString' -import * as messages from '@Lib/Services/Api/Messages' import merge from 'lodash/merge' import { SettingsServerInterface } from '../Settings/SettingsServerInterface' import { Strings } from '@Lib/Strings' import { SNRootKeyParams } from '@standardnotes/encryption' import { ApiEndpointParam, ClientDisplayableError, CreateValetTokenPayload } from '@standardnotes/responses' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' -import { HttpResponseMeta } from '@standardnotes/api' /** Legacy api version field to be specified in params when calling v0 APIs. */ const V0_API_VERSION = '20200115' @@ -48,7 +64,6 @@ export class SNApiService FilesApiInterface, IntegrityApiInterface, ItemsServerInterface, - UserServerInterface, SettingsServerInterface { private session?: Session @@ -232,7 +247,7 @@ export class SNApiService return this.request({ verb: HttpVerb.Post, url: joinPaths(this.host, Paths.v2.keyParams), - fallbackErrorMessage: messages.API_MESSAGE_GENERIC_INVALID_LOGIN, + fallbackErrorMessage: API_MESSAGE_GENERIC_INVALID_LOGIN, params, /** A session is optional here, if valid, endpoint bypasses 2FA and returns additional params */ authentication: this.session?.authorizationValue, @@ -245,7 +260,7 @@ export class SNApiService ephemeral: boolean }): Promise { if (this.authenticating) { - return this.createErrorResponse(messages.API_MESSAGE_LOGIN_IN_PROGRESS) as Responses.SignInResponse + return this.createErrorResponse(API_MESSAGE_LOGIN_IN_PROGRESS) as Responses.SignInResponse } this.authenticating = true const url = joinPaths(this.host, Paths.v2.signIn) @@ -260,7 +275,7 @@ export class SNApiService verb: HttpVerb.Post, url, params, - fallbackErrorMessage: messages.API_MESSAGE_GENERIC_INVALID_LOGIN, + fallbackErrorMessage: API_MESSAGE_GENERIC_INVALID_LOGIN, }) this.authenticating = false @@ -285,7 +300,7 @@ export class SNApiService newEmail?: string }): Promise { if (this.changing) { - return this.createErrorResponse(messages.API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS) + return this.createErrorResponse(API_MESSAGE_CHANGE_CREDENTIALS_IN_PROGRESS) } const preprocessingError = this.preprocessingError() if (preprocessingError) { @@ -309,10 +324,7 @@ export class SNApiService params, }) } - return this.errorResponseWithFallbackMessage( - errorResponse, - messages.API_MESSAGE_GENERIC_CHANGE_CREDENTIALS_FAIL, - ) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_CHANGE_CREDENTIALS_FAIL) }) this.processResponse(response) @@ -321,17 +333,6 @@ export class SNApiService return response } - public async deleteAccount(userUuid: string): Promise { - const url = joinPaths(this.host, Paths.v1.deleteAccount(userUuid)) - const response = await this.request({ - verb: HttpVerb.Delete, - url, - authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.ServerErrorStrings.DeleteAccountError, - }) - return response - } - async sync( payloads: ServerSyncPushContextualPayload[], lastSyncToken: string, @@ -360,7 +361,7 @@ export class SNApiService params, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) @@ -405,7 +406,7 @@ export class SNApiService }) .catch((errorResponse) => { this.preprocessAuthenticatedErrorResponse(errorResponse) - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_TOKEN_REFRESH_FAIL) }) this.refreshingSession = false return result @@ -427,7 +428,7 @@ export class SNApiService url, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) @@ -451,7 +452,7 @@ export class SNApiService url, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) return response @@ -473,7 +474,7 @@ export class SNApiService url, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) return response @@ -498,7 +499,7 @@ export class SNApiService url, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) return response @@ -516,7 +517,7 @@ export class SNApiService url, }) } - return this.errorResponseWithFallbackMessage(errorResponse, messages.API_MESSAGE_GENERIC_SYNC_FAIL) + return this.errorResponseWithFallbackMessage(errorResponse, API_MESSAGE_GENERIC_SYNC_FAIL) }) this.processResponse(response) return response @@ -546,7 +547,7 @@ export class SNApiService return await this.tokenRefreshableRequest({ verb: HttpVerb.Get, url: joinPaths(this.host, Paths.v1.settings(userUuid)), - fallbackErrorMessage: messages.API_MESSAGE_FAILED_GET_SETTINGS, + fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS, authentication: this.session?.authorizationValue, }) } @@ -566,7 +567,7 @@ export class SNApiService verb: HttpVerb.Put, url: joinPaths(this.host, Paths.v1.settings(userUuid)), authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_UPDATE_SETTINGS, + fallbackErrorMessage: API_MESSAGE_FAILED_UPDATE_SETTINGS, params, }) } @@ -576,7 +577,7 @@ export class SNApiService verb: HttpVerb.Get, url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName.toLowerCase() as SettingName)), authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_GET_SETTINGS, + fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS, }) } @@ -591,7 +592,7 @@ export class SNApiService Paths.v1.subscriptionSetting(userUuid, settingName.toLowerCase() as SubscriptionSettingName), ), authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_GET_SETTINGS, + fallbackErrorMessage: API_MESSAGE_FAILED_GET_SETTINGS, }) } @@ -600,7 +601,7 @@ export class SNApiService verb: HttpVerb.Delete, url: joinPaths(this.host, Paths.v1.setting(userUuid, settingName)), authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_UPDATE_SETTINGS, + fallbackErrorMessage: API_MESSAGE_FAILED_UPDATE_SETTINGS, }) } @@ -612,7 +613,7 @@ export class SNApiService const response = await this.tokenRefreshableRequest({ verb: HttpVerb.Delete, url, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_DELETE_REVISION, + fallbackErrorMessage: API_MESSAGE_FAILED_DELETE_REVISION, authentication: this.session?.authorizationValue, }) return response @@ -623,7 +624,7 @@ export class SNApiService verb: HttpVerb.Get, url, external: true, - fallbackErrorMessage: messages.API_MESSAGE_GENERIC_INVALID_LOGIN, + fallbackErrorMessage: API_MESSAGE_GENERIC_INVALID_LOGIN, }) } @@ -633,7 +634,7 @@ export class SNApiService verb: HttpVerb.Get, url, authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_SUBSCRIPTION_INFO, + fallbackErrorMessage: API_MESSAGE_FAILED_SUBSCRIPTION_INFO, }) return response } @@ -645,7 +646,7 @@ export class SNApiService const response = await this.request({ verb: HttpVerb.Get, url, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_SUBSCRIPTION_INFO, + fallbackErrorMessage: API_MESSAGE_FAILED_SUBSCRIPTION_INFO, }) return response } @@ -656,7 +657,7 @@ export class SNApiService verb: HttpVerb.Post, url, authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_ACCESS_PURCHASE, + fallbackErrorMessage: API_MESSAGE_FAILED_ACCESS_PURCHASE, }) return (response as Responses.PostSubscriptionTokensResponse).data?.token } @@ -680,7 +681,7 @@ export class SNApiService const response: Responses.HttpResponse | Responses.GetOfflineFeaturesResponse = await this.request({ verb: HttpVerb.Get, url: featuresUrl, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_OFFLINE_FEATURES, + fallbackErrorMessage: API_MESSAGE_FAILED_OFFLINE_FEATURES, customHeaders: [{ key: 'x-offline-token', value: extensionKey }], }) @@ -702,7 +703,7 @@ export class SNApiService return await this.tokenRefreshableRequest({ verb: HttpVerb.Post, url: joinPaths(this.host, Paths.v1.listedRegistration(this.user.uuid)), - fallbackErrorMessage: messages.API_MESSAGE_FAILED_LISTED_REGISTRATION, + fallbackErrorMessage: API_MESSAGE_FAILED_LISTED_REGISTRATION, authentication: this.session?.authorizationValue, }) } @@ -723,7 +724,7 @@ export class SNApiService verb: HttpVerb.Post, url: url, authentication: this.session?.authorizationValue, - fallbackErrorMessage: messages.API_MESSAGE_FAILED_CREATE_FILE_TOKEN, + fallbackErrorMessage: API_MESSAGE_FAILED_CREATE_FILE_TOKEN, params, }) @@ -856,7 +857,7 @@ export class SNApiService params: { integrityPayloads, }, - fallbackErrorMessage: messages.API_MESSAGE_GENERIC_INTEGRITY_CHECK_FAIL, + fallbackErrorMessage: API_MESSAGE_GENERIC_INTEGRITY_CHECK_FAIL, authentication: this.session?.authorizationValue, }) } @@ -865,17 +866,17 @@ export class SNApiService return await this.tokenRefreshableRequest({ verb: HttpVerb.Get, url: joinPaths(this.host, Paths.v1.getSingleItem(itemUuid)), - fallbackErrorMessage: messages.API_MESSAGE_GENERIC_SINGLE_ITEM_SYNC_FAIL, + fallbackErrorMessage: API_MESSAGE_GENERIC_SINGLE_ITEM_SYNC_FAIL, authentication: this.session?.authorizationValue, }) } private preprocessingError() { if (this.refreshingSession) { - return this.createErrorResponse(messages.API_MESSAGE_TOKEN_REFRESH_IN_PROGRESS) + return this.createErrorResponse(API_MESSAGE_TOKEN_REFRESH_IN_PROGRESS) } if (!this.session) { - return this.createErrorResponse(messages.API_MESSAGE_INVALID_SESSION) + return this.createErrorResponse(API_MESSAGE_INVALID_SESSION) } return undefined } diff --git a/packages/snjs/lib/Services/Api/HttpService.ts b/packages/snjs/lib/Services/Api/HttpService.ts index e66c0e277..1757ff920 100644 --- a/packages/snjs/lib/Services/Api/HttpService.ts +++ b/packages/snjs/lib/Services/Api/HttpService.ts @@ -1,8 +1,12 @@ -import { API_MESSAGE_RATE_LIMITED, UNKNOWN_ERROR } from './Messages' import { HttpResponse, StatusCode } from '@standardnotes/responses' import { isString } from '@standardnotes/utils' import { SnjsVersion } from '@Lib/Version' -import { AbstractService, InternalEventBusInterface } from '@standardnotes/services' +import { + AbstractService, + API_MESSAGE_RATE_LIMITED, + InternalEventBusInterface, + UNKNOWN_ERROR, +} from '@standardnotes/services' import { Environment } from '@standardnotes/models' export enum HttpVerb { diff --git a/packages/snjs/lib/Services/Api/index.ts b/packages/snjs/lib/Services/Api/index.ts index 88e50bbd5..32f79dc41 100644 --- a/packages/snjs/lib/Services/Api/index.ts +++ b/packages/snjs/lib/Services/Api/index.ts @@ -1,6 +1,5 @@ export * from './ApiService' export * from './HttpService' -export * from './Messages' export * from './Paths' export * from '../Session/Sessions/Session' export * from '../Session/SessionManager' diff --git a/packages/snjs/lib/Services/Challenge/ChallengeOperation.ts b/packages/snjs/lib/Services/Challenge/ChallengeOperation.ts index 63a182152..d9f06164b 100644 --- a/packages/snjs/lib/Services/Challenge/ChallengeOperation.ts +++ b/packages/snjs/lib/Services/Challenge/ChallengeOperation.ts @@ -1,8 +1,7 @@ -import { Challenge } from './Challenge' +import { Challenge, ChallengeValue, ChallengeArtifacts } from '@standardnotes/services' import { ChallengeResponse } from './ChallengeResponse' import { removeFromArray } from '@standardnotes/utils' import { ValueCallback } from './ChallengeService' -import { ChallengeValue, ChallengeArtifacts } from '@standardnotes/services' /** * A challenge operation stores user-submitted values and callbacks. diff --git a/packages/snjs/lib/Services/Challenge/ChallengeResponse.ts b/packages/snjs/lib/Services/Challenge/ChallengeResponse.ts index 6ac661c36..306254fd5 100644 --- a/packages/snjs/lib/Services/Challenge/ChallengeResponse.ts +++ b/packages/snjs/lib/Services/Challenge/ChallengeResponse.ts @@ -1,6 +1,6 @@ import { isNullOrUndefined } from '@standardnotes/utils' -import { Challenge } from './Challenge' import { + Challenge, ChallengeResponseInterface, ChallengeValidation, ChallengeValue, diff --git a/packages/snjs/lib/Services/Challenge/ChallengeService.ts b/packages/snjs/lib/Services/Challenge/ChallengeService.ts index 3f6729de6..0f418c005 100644 --- a/packages/snjs/lib/Services/Challenge/ChallengeService.ts +++ b/packages/snjs/lib/Services/Challenge/ChallengeService.ts @@ -6,6 +6,7 @@ import { AbstractService, ChallengeServiceInterface, InternalEventBusInterface, + Challenge, ChallengeArtifacts, ChallengeReason, ChallengeValidation, @@ -17,7 +18,6 @@ import { } from '@standardnotes/services' import { ChallengeResponse } from './ChallengeResponse' import { ChallengeOperation } from './ChallengeOperation' -import { Challenge } from './Challenge' type ChallengeValidationResponse = { valid: boolean diff --git a/packages/snjs/lib/Services/Challenge/index.ts b/packages/snjs/lib/Services/Challenge/index.ts index 55476a23c..2598259ed 100644 --- a/packages/snjs/lib/Services/Challenge/index.ts +++ b/packages/snjs/lib/Services/Challenge/index.ts @@ -1,4 +1,3 @@ -export * from './Challenge' export * from './ChallengeOperation' export * from './ChallengeResponse' export * from './ChallengeService' diff --git a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts index 844700b0d..364f61c49 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts @@ -1,15 +1,6 @@ import { ItemInterface, SNComponent, SNFeatureRepo } from '@standardnotes/models' import { SNSyncService } from '../Sync/SyncService' import { SettingName } from '@standardnotes/settings' -import { - ItemManager, - AlertService, - SNApiService, - UserService, - SNSessionManager, - DiskStorageService, - StorageKey, -} from '@Lib/index' import { SNFeaturesService } from '@Lib/Services/Features' import { ContentType, RoleName } from '@standardnotes/common' import { FeatureDescription, FeatureIdentifier, GetFeatures } from '@standardnotes/features' @@ -17,7 +8,16 @@ import { SNWebSocketsService } from '../Api/WebsocketsService' import { SNSettingsService } from '../Settings' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { convertTimestampToMilliseconds } from '@standardnotes/utils' -import { FeatureStatus, InternalEventBusInterface } from '@standardnotes/services' +import { + AlertService, + FeatureStatus, + InternalEventBusInterface, + StorageKey, + UserService, +} from '@standardnotes/services' +import { SNApiService, SNSessionManager } from '../Api' +import { ItemManager } from '../Items' +import { DiskStorageService } from '../Storage/DiskStorageService' describe('featuresService', () => { let storageService: DiskStorageService diff --git a/packages/snjs/lib/Services/Features/FeaturesService.ts b/packages/snjs/lib/Services/Features/FeaturesService.ts index cafe0b50f..07da4f38c 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.ts @@ -1,4 +1,3 @@ -import { AccountEvent, UserService } from '../User/UserService' import { SNApiService } from '../Api/ApiService' import { arraysEqual, @@ -24,12 +23,15 @@ import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hos import { UserRolesChangedEvent } from '@standardnotes/domain-events' import { UuidString } from '@Lib/Types/UuidString' import * as FeaturesImports from '@standardnotes/features' -import * as Messages from '@Lib/Services/Api/Messages' import * as Models from '@standardnotes/models' import { AbstractService, + AccountEvent, AlertService, ApiServiceEvent, + API_MESSAGE_FAILED_DOWNLOADING_EXTENSION, + API_MESSAGE_FAILED_OFFLINE_ACTIVATION, + API_MESSAGE_UNTRUSTED_EXTENSIONS_WARNING, ApplicationStage, ButtonType, DiagnosticInfo, @@ -39,10 +41,12 @@ import { InternalEventBusInterface, InternalEventHandlerInterface, InternalEventInterface, + INVALID_EXTENSION_URL, MetaReceivedData, OfflineSubscriptionEntitlements, SetOfflineFeaturesFunctionResponse, StorageKey, + UserService, } from '@standardnotes/services' import { FeatureIdentifier } from '@standardnotes/features' @@ -250,7 +254,7 @@ export class SNFeaturesService void this.syncService.sync() return this.downloadOfflineFeatures(offlineRepo) } catch (err) { - return new ClientDisplayableError(Messages.API_MESSAGE_FAILED_OFFLINE_ACTIVATION) + return new ClientDisplayableError(API_MESSAGE_FAILED_OFFLINE_ACTIVATION) } } @@ -280,7 +284,7 @@ export class SNFeaturesService extensionKey, } } catch (error) { - return new ClientDisplayableError(Messages.API_MESSAGE_FAILED_OFFLINE_ACTIVATION) + return new ClientDisplayableError(API_MESSAGE_FAILED_OFFLINE_ACTIVATION) } } @@ -631,7 +635,7 @@ export class SNFeaturesService const { host } = new URL(url) if (!trustedCustomExtensionsUrls.includes(host)) { const didConfirm = await this.alertService.confirm( - Messages.API_MESSAGE_UNTRUSTED_EXTENSIONS_WARNING, + API_MESSAGE_UNTRUSTED_EXTENSIONS_WARNING, 'Install extension from an untrusted source?', 'Proceed to install', ButtonType.Danger, @@ -644,7 +648,7 @@ export class SNFeaturesService return this.performDownloadExternalFeature(url) } } catch (err) { - void this.alertService.alert(Messages.INVALID_EXTENSION_URL) + void this.alertService.alert(INVALID_EXTENSION_URL) } return undefined @@ -653,7 +657,7 @@ export class SNFeaturesService private async performDownloadExternalFeature(url: string): Promise { const response = await this.apiService.downloadFeatureUrl(url) if (response.error) { - await this.alertService.alert(Messages.API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) + await this.alertService.alert(API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) return undefined } @@ -683,14 +687,14 @@ export class SNFeaturesService const nativeFeature = FeaturesImports.FindNativeFeature(rawFeature.identifier) if (nativeFeature) { - await this.alertService.alert(Messages.API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) + await this.alertService.alert(API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) return } if (rawFeature.url) { for (const nativeFeature of FeaturesImports.GetFeatures()) { if (rawFeature.url.includes(nativeFeature.identifier)) { - await this.alertService.alert(Messages.API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) + await this.alertService.alert(API_MESSAGE_FAILED_DOWNLOADING_EXTENSION) return } } diff --git a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts index b8f8b3d35..2da7f193a 100644 --- a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts +++ b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts @@ -1,6 +1,5 @@ import { KeyRecoveryOperation } from './KeyRecoveryOperation' import { SNRootKeyParams, SNRootKey, KeyParamsFromApiResponse, KeyRecoveryStrings } from '@standardnotes/encryption' -import { UserService } from '../User/UserService' import { isErrorDecryptingPayload, EncryptedPayloadInterface, @@ -14,7 +13,7 @@ import { import { SNSyncService } from '../Sync/SyncService' import { DiskStorageService } from '../Storage/DiskStorageService' import { PayloadManager } from '../Payloads/PayloadManager' -import { Challenge, ChallengeService } from '../Challenge' +import { ChallengeService } from '../Challenge' import { SNApiService } from '@Lib/Services/Api/ApiService' import { ContentType, Uuid } from '@standardnotes/common' import { ItemManager } from '../Items/ItemManager' @@ -32,6 +31,8 @@ import { ChallengeReason, ChallengePrompt, EncryptionService, + Challenge, + UserService, } from '@standardnotes/services' import { UndecryptableItemsStorage, diff --git a/packages/snjs/lib/Services/Mfa/MfaService.ts b/packages/snjs/lib/Services/Mfa/MfaService.ts index 74935d1fe..ecccba7c9 100644 --- a/packages/snjs/lib/Services/Mfa/MfaService.ts +++ b/packages/snjs/lib/Services/Mfa/MfaService.ts @@ -1,11 +1,10 @@ import { SettingName } from '@standardnotes/settings' import { SNSettingsService } from '../Settings' -import * as messages from '../Api/Messages' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SNFeaturesService } from '../Features/FeaturesService' import { FeatureIdentifier } from '@standardnotes/features' -import { AbstractService, InternalEventBusInterface } from '@standardnotes/services' +import { AbstractService, InternalEventBusInterface, SignInStrings } from '@standardnotes/services' export class SNMfaService extends AbstractService { constructor( @@ -38,7 +37,7 @@ export class SNMfaService extends AbstractService { const otpTokenValid = otpToken != undefined && otpToken === (await this.getOtpToken(secret)) if (!otpTokenValid) { - throw new Error(messages.SignInStrings.IncorrectMfa) + throw new Error(SignInStrings.IncorrectMfa) } return this.saveMfaSetting(secret) diff --git a/packages/snjs/lib/Services/Mutator/MutatorService.ts b/packages/snjs/lib/Services/Mutator/MutatorService.ts index ad1023b5c..1407c5777 100644 --- a/packages/snjs/lib/Services/Mutator/MutatorService.ts +++ b/packages/snjs/lib/Services/Mutator/MutatorService.ts @@ -7,6 +7,8 @@ import { ChallengePrompt, ChallengeReason, MutatorClientInterface, + Challenge, + InfoStrings, } from '@standardnotes/services' import { EncryptionProviderInterface } from '@standardnotes/encryption' import { ClientDisplayableError } from '@standardnotes/responses' @@ -18,7 +20,7 @@ import { SNProtectionService } from '../Protection/ProtectionService' import { SNSyncService } from '../Sync' import { Strings } from '../../Strings' import { TagsToFoldersMigrationApplicator } from '@Lib/Migrations/Applicators/TagsToFolders' -import { Challenge, ChallengeService } from '../Challenge' +import { ChallengeService } from '../Challenge' import { BackupFile, BackupFileDecryptedContextualPayload, @@ -170,7 +172,13 @@ export class MutatorService extends AbstractService implements MutatorClientInte items: I[], reason: ChallengeReason, ): Promise { - if (!(await this.protectionService.authorizeAction(reason))) { + if ( + !(await this.protectionService.authorizeAction(reason, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + })) + ) { return undefined } @@ -314,13 +322,13 @@ export class MutatorService extends AbstractService implements MutatorClientInte const supportedVersions = this.encryption.supportedVersions() if (!supportedVersions.includes(version)) { - return { error: new ClientDisplayableError(Strings.Info.UnsupportedBackupFileVersion) } + return { error: new ClientDisplayableError(InfoStrings.UnsupportedBackupFileVersion) } } const userVersion = this.encryption.getUserVersion() if (userVersion && compareVersions(version, userVersion) === 1) { /** File was made with a greater version than the user's account */ - return { error: new ClientDisplayableError(Strings.Info.BackupFileMoreRecentThanAccount) } + return { error: new ClientDisplayableError(InfoStrings.BackupFileMoreRecentThanAccount) } } } diff --git a/packages/snjs/lib/Services/Protection/ProtectionService.ts b/packages/snjs/lib/Services/Protection/ProtectionService.ts index 1ef9592aa..6ebc71c78 100644 --- a/packages/snjs/lib/Services/Protection/ProtectionService.ts +++ b/packages/snjs/lib/Services/Protection/ProtectionService.ts @@ -1,4 +1,3 @@ -import { Challenge } from './../Challenge/Challenge' import { ChallengeService } from './../Challenge/ChallengeService' import { SNLog } from '@Lib/Log' import { DecryptedItem } from '@standardnotes/models' @@ -11,14 +10,16 @@ import { ApplicationStage, StorageKey, DiagnosticInfo, + Challenge, ChallengeReason, ChallengePrompt, ChallengeValidation, EncryptionService, + MobileUnlockTiming, + TimingDisplayOption, + ProtectionsClientInterface, } from '@standardnotes/services' -import { ProtectionsClientInterface } from './ClientInterface' import { ContentType } from '@standardnotes/common' -import { MobileUnlockTiming, TimingDisplayOption } from './MobileUnlockTiming' export enum ProtectionEvent { UnprotectedSessionBegan = 'UnprotectedSessionBegan', @@ -176,62 +177,95 @@ export class SNProtectionService extends AbstractService implem item.content_type === ContentType.Note ? ChallengeReason.AccessProtectedNote : ChallengeReason.AccessProtectedFile, + { fallBackToAccountPassword: true, requireAccountPassword: false, forcePrompt: false }, ) } authorizeAddingPasscode(): Promise { - return this.authorizeAction(ChallengeReason.AddPasscode) + return this.authorizeAction(ChallengeReason.AddPasscode, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } authorizeChangingPasscode(): Promise { - return this.authorizeAction(ChallengeReason.ChangePasscode) + return this.authorizeAction(ChallengeReason.ChangePasscode, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } authorizeRemovingPasscode(): Promise { - return this.authorizeAction(ChallengeReason.RemovePasscode) + return this.authorizeAction(ChallengeReason.RemovePasscode, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } authorizeSearchingProtectedNotesText(): Promise { - return this.authorizeAction(ChallengeReason.SearchProtectedNotesText) + return this.authorizeAction(ChallengeReason.SearchProtectedNotesText, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } authorizeFileImport(): Promise { - return this.authorizeAction(ChallengeReason.ImportFile) + return this.authorizeAction(ChallengeReason.ImportFile, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } async authorizeBackupCreation(): Promise { return this.authorizeAction(ChallengeReason.ExportBackup, { fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, }) } async authorizeMfaDisable(): Promise { return this.authorizeAction(ChallengeReason.DisableMfa, { + fallBackToAccountPassword: true, requireAccountPassword: true, + forcePrompt: false, }) } async authorizeAutolockIntervalChange(): Promise { - return this.authorizeAction(ChallengeReason.ChangeAutolockInterval) + return this.authorizeAction(ChallengeReason.ChangeAutolockInterval, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } async authorizeSessionRevoking(): Promise { - return this.authorizeAction(ChallengeReason.RevokeSession) + return this.authorizeAction(ChallengeReason.RevokeSession, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: false, + }) } async authorizeListedPublishing(): Promise { - return this.authorizeAction(ChallengeReason.AuthorizeNoteForListed, { forcePrompt: true }) + return this.authorizeAction(ChallengeReason.AuthorizeNoteForListed, { + fallBackToAccountPassword: true, + requireAccountPassword: false, + forcePrompt: true, + }) } async authorizeAction( reason: ChallengeReason, - { fallBackToAccountPassword = true, requireAccountPassword = false, forcePrompt = false } = {}, + dto: { fallBackToAccountPassword: boolean; requireAccountPassword: boolean; forcePrompt: boolean }, ): Promise { - return this.validateOrRenewSession(reason, { - requireAccountPassword, - fallBackToAccountPassword, - forcePrompt, - }) + return this.validateOrRenewSession(reason, dto) } getMobilePasscodeTimingOptions(): TimingDisplayOption[] { diff --git a/packages/snjs/lib/Services/Protection/index.ts b/packages/snjs/lib/Services/Protection/index.ts index f0cb4a4fe..b43e8b430 100644 --- a/packages/snjs/lib/Services/Protection/index.ts +++ b/packages/snjs/lib/Services/Protection/index.ts @@ -1,3 +1 @@ -export * from './ClientInterface' export * from './ProtectionService' -export * from './MobileUnlockTiming' diff --git a/packages/snjs/lib/Services/Session/SessionManager.ts b/packages/snjs/lib/Services/Session/SessionManager.ts index b62cb3028..338017d84 100644 --- a/packages/snjs/lib/Services/Session/SessionManager.ts +++ b/packages/snjs/lib/Services/Session/SessionManager.ts @@ -10,6 +10,18 @@ import { ChallengeReason, ChallengePromptTitle, EncryptionService, + SessionsClientInterface, + SessionManagerResponse, + SessionStrings, + SignInStrings, + INVALID_PASSWORD_COST, + API_MESSAGE_FALLBACK_LOGIN_FAIL, + API_MESSAGE_GENERIC_SYNC_FAIL, + EXPIRED_PROTOCOL_VERSION, + StrictSignInFailed, + UNSUPPORTED_KEY_DERIVATION, + UNSUPPORTED_PROTOCOL_VERSION, + Challenge, } from '@standardnotes/services' import { Base64String } from '@standardnotes/sncrypto-common' import { ClientDisplayableError } from '@standardnotes/responses' @@ -17,11 +29,9 @@ import { CopyPayloadWithContentOverride } from '@standardnotes/models' import { isNullOrUndefined } from '@standardnotes/utils' import { JwtSession } from './Sessions/JwtSession' import { KeyParamsFromApiResponse, SNRootKeyParams, SNRootKey, CreateNewRootKey } from '@standardnotes/encryption' -import { SessionStrings, SignInStrings } from '../Api/Messages' import { RemoteSession, RawStorageValue } from './Sessions/Types' import { Session } from './Sessions/Session' import { SessionFromRawStorageValue } from './Sessions/Generator' -import { SessionsClientInterface } from './SessionsClientInterface' import { ShareToken } from './ShareToken' import { SNApiService } from '../Api/ApiService' import { DiskStorageService } from '../Storage/DiskStorageService' @@ -31,9 +41,8 @@ import { Subscription } from '@standardnotes/security' import { TokenSession } from './Sessions/TokenSession' import { UuidString } from '@Lib/Types/UuidString' import * as Common from '@standardnotes/common' -import * as Messages from '../Api/Messages' import * as Responses from '@standardnotes/responses' -import { Challenge, ChallengeService } from '../Challenge' +import { ChallengeService } from '../Challenge' import { ApiCallError, ErrorMessage, @@ -46,12 +55,6 @@ import { export const MINIMUM_PASSWORD_LENGTH = 8 export const MissingAccountParams = 'missing-params' -type SessionManagerResponse = { - response: Responses.HttpResponse - rootKey?: SNRootKey - keyParams?: Common.AnyKeyParamsContent -} - const cleanedEmailString = (email: string) => { return email.trim().toLowerCase() } @@ -338,7 +341,7 @@ export class SNSessionManager extends AbstractService implements S const keyParams = KeyParamsFromApiResponse(response as Responses.KeyParamsResponse, email) if (!keyParams || !keyParams.version) { return { - response: this.apiService.createErrorResponse(Messages.API_MESSAGE_FALLBACK_LOGIN_FAIL), + response: this.apiService.createErrorResponse(API_MESSAGE_FALLBACK_LOGIN_FAIL), } } return { keyParams, response, mfaKeyPath, mfaCode } @@ -388,11 +391,11 @@ export class SNSessionManager extends AbstractService implements S if (!this.protocolService.supportedVersions().includes(keyParams.version)) { if (this.protocolService.isVersionNewerThanLibraryVersion(keyParams.version)) { return { - response: this.apiService.createErrorResponse(Messages.UNSUPPORTED_PROTOCOL_VERSION), + response: this.apiService.createErrorResponse(UNSUPPORTED_PROTOCOL_VERSION), } } else { return { - response: this.apiService.createErrorResponse(Messages.EXPIRED_PROTOCOL_VERSION), + response: this.apiService.createErrorResponse(EXPIRED_PROTOCOL_VERSION), } } } @@ -402,7 +405,7 @@ export class SNSessionManager extends AbstractService implements S const minimum = this.protocolService.costMinimumForVersion(keyParams.version) if (keyParams.content002.pw_cost < minimum) { return { - response: this.apiService.createErrorResponse(Messages.INVALID_PASSWORD_COST), + response: this.apiService.createErrorResponse(INVALID_PASSWORD_COST), } } @@ -415,14 +418,14 @@ export class SNSessionManager extends AbstractService implements S if (!confirmed) { return { - response: this.apiService.createErrorResponse(Messages.API_MESSAGE_FALLBACK_LOGIN_FAIL), + response: this.apiService.createErrorResponse(API_MESSAGE_FALLBACK_LOGIN_FAIL), } } } if (!this.protocolService.platformSupportsKeyDerivation(keyParams)) { return { - response: this.apiService.createErrorResponse(Messages.UNSUPPORTED_KEY_DERIVATION), + response: this.apiService.createErrorResponse(UNSUPPORTED_KEY_DERIVATION), } } @@ -433,9 +436,7 @@ export class SNSessionManager extends AbstractService implements S if (!isNullOrUndefined(minAllowedVersion)) { if (!Common.leftVersionGreaterThanOrEqualToRight(keyParams.version, minAllowedVersion)) { return { - response: this.apiService.createErrorResponse( - Messages.StrictSignInFailed(keyParams.version, minAllowedVersion), - ), + response: this.apiService.createErrorResponse(StrictSignInFailed(keyParams.version, minAllowedVersion)), } } } @@ -532,7 +533,7 @@ export class SNSessionManager extends AbstractService implements S public async revokeAllOtherSessions(): Promise { const response = await this.getSessionsList() if (response.error != undefined || response.data == undefined) { - throw new Error(response.error?.message ?? Messages.API_MESSAGE_GENERIC_SYNC_FAIL) + throw new Error(response.error?.message ?? API_MESSAGE_GENERIC_SYNC_FAIL) } const sessions = response.data as RemoteSession[] const otherSessions = sessions.filter((session) => !session.current) diff --git a/packages/snjs/lib/Services/Session/SessionsClientInterface.ts b/packages/snjs/lib/Services/Session/SessionsClientInterface.ts deleted file mode 100644 index c80f4f9dc..000000000 --- a/packages/snjs/lib/Services/Session/SessionsClientInterface.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ClientDisplayableError, User } from '@standardnotes/responses' -import { Base64String } from '@standardnotes/sncrypto-common' - -export interface SessionsClientInterface { - createDemoShareToken(): Promise - populateSessionFromDemoShareToken(token: Base64String): Promise - getUser(): User | undefined -} diff --git a/packages/snjs/lib/Services/Session/index.ts b/packages/snjs/lib/Services/Session/index.ts index b47d62dd7..c28890cba 100644 --- a/packages/snjs/lib/Services/Session/index.ts +++ b/packages/snjs/lib/Services/Session/index.ts @@ -1,4 +1,3 @@ export * from './SessionManager' export * from './Sessions' -export * from './SessionsClientInterface' export * from './ShareToken' diff --git a/packages/snjs/lib/Services/Settings/SettingsGateway.ts b/packages/snjs/lib/Services/Settings/SettingsGateway.ts index fea42b26e..495a4bb1e 100644 --- a/packages/snjs/lib/Services/Settings/SettingsGateway.ts +++ b/packages/snjs/lib/Services/Settings/SettingsGateway.ts @@ -1,6 +1,6 @@ import { SettingsList } from './SettingsList' import { SettingName, SensitiveSettingName, SubscriptionSettingName } from '@standardnotes/settings' -import * as messages from '../Api/Messages' +import { API_MESSAGE_INVALID_SESSION } from '@standardnotes/services' import { StatusCode, User } from '@standardnotes/responses' import { SettingsServerInterface } from './SettingsServerInterface' @@ -25,7 +25,7 @@ export class SettingsGateway { private get userUuid() { const user = this.getUser() if (user == undefined || user.uuid == undefined) { - throw new Error(messages.API_MESSAGE_INVALID_SESSION) + throw new Error(API_MESSAGE_INVALID_SESSION) } return user.uuid } diff --git a/packages/snjs/lib/Services/User/UserServerInterface.ts b/packages/snjs/lib/Services/User/UserServerInterface.ts deleted file mode 100644 index 031d26395..000000000 --- a/packages/snjs/lib/Services/User/UserServerInterface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { HttpResponse, MinimalHttpResponse } from '@standardnotes/responses' - -export interface UserServerInterface { - deleteAccount(userUuid: string): Promise -} diff --git a/packages/snjs/lib/Services/User/index.ts b/packages/snjs/lib/Services/User/index.ts deleted file mode 100644 index 384a674f0..000000000 --- a/packages/snjs/lib/Services/User/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './UserServerInterface' -export * from './UserService' diff --git a/packages/snjs/lib/Services/index.ts b/packages/snjs/lib/Services/index.ts index 8a6257f67..57986a748 100644 --- a/packages/snjs/lib/Services/index.ts +++ b/packages/snjs/lib/Services/index.ts @@ -19,4 +19,3 @@ export * from './Settings' export * from './Singleton/SingletonManager' export * from './Storage/DiskStorageService' export * from './Sync' -export * from './User' diff --git a/packages/snjs/lib/Strings/index.ts b/packages/snjs/lib/Strings/index.ts index 268e89c41..ca0aa6255 100644 --- a/packages/snjs/lib/Strings/index.ts +++ b/packages/snjs/lib/Strings/index.ts @@ -1,10 +1,8 @@ import { ConfirmStrings } from './Confirm' -import { InfoStrings } from './Info' import { InputStrings } from './Input' import { NetworkStrings } from './Network' export const Strings = { - Info: InfoStrings, Network: NetworkStrings, Confirm: ConfirmStrings, Input: InputStrings, diff --git a/packages/snjs/package.json b/packages/snjs/package.json index 821b0ee7f..5581f6637 100644 --- a/packages/snjs/package.json +++ b/packages/snjs/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@standardnotes/api": "workspace:*", - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/domain-events": "^2.39.0", "@standardnotes/encryption": "workspace:*", "@standardnotes/features": "workspace:*", diff --git a/packages/ui-services/package.json b/packages/ui-services/package.json index 6cfddb17d..682821694 100644 --- a/packages/ui-services/package.json +++ b/packages/ui-services/package.json @@ -23,7 +23,7 @@ "test": "jest spec" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "@standardnotes/features": "workspace:^", "@standardnotes/filepicker": "workspace:^", "@standardnotes/services": "workspace:^", diff --git a/packages/utils/package.json b/packages/utils/package.json index 529c48d77..058feddd9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -25,7 +25,7 @@ "test": "jest spec" }, "dependencies": { - "@standardnotes/common": "^1.39.0", + "@standardnotes/common": "^1.43.0", "dompurify": "^2.3.8", "lodash": "^4.17.21", "reflect-metadata": "^0.1.13" diff --git a/yarn.lock b/yarn.lock index b0090db55..6c0dd7d95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6354,7 +6354,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/api@workspace:packages/api" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/encryption": "workspace:*" "@standardnotes/models": "workspace:*" "@standardnotes/responses": "workspace:*" @@ -6521,7 +6521,7 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/common@npm:1.40.0, @standardnotes/common@npm:^1.23.1, @standardnotes/common@npm:^1.39.0": +"@standardnotes/common@npm:1.40.0, @standardnotes/common@npm:^1.23.1": version: 1.40.0 resolution: "@standardnotes/common@npm:1.40.0" dependencies: @@ -6530,6 +6530,15 @@ __metadata: languageName: node linkType: hard +"@standardnotes/common@npm:^1.43.0": + version: 1.43.0 + resolution: "@standardnotes/common@npm:1.43.0" + dependencies: + reflect-metadata: ^0.1.13 + checksum: 59300594418a5cb9d4b811240c23007bb927df6f620cb37460a978d82b1b8baf7107e4a3557110c032636ab02f7e61669613d35bdcac2bcb0e8f0e66b8a16f8d + languageName: node + linkType: hard + "@standardnotes/component-relay@npm:2.2.0": version: 2.2.0 resolution: "@standardnotes/component-relay@npm:2.2.0" @@ -6713,7 +6722,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/encryption@workspace:packages/encryption" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/config": 2.4.3 "@standardnotes/models": "workspace:*" "@standardnotes/responses": "workspace:*" @@ -6755,7 +6764,7 @@ __metadata: resolution: "@standardnotes/features@workspace:packages/features" dependencies: "@standardnotes/auth": ^3.19.4 - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/security": ^1.2.0 "@types/jest": ^28.1.5 "@typescript-eslint/eslint-plugin": ^5.30.0 @@ -6771,7 +6780,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/filepicker@workspace:packages/filepicker" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/files": "workspace:*" "@standardnotes/utils": "workspace:*" "@types/jest": ^28.1.5 @@ -6790,7 +6799,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/files@workspace:packages/files" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/encryption": "workspace:*" "@standardnotes/models": "workspace:*" "@standardnotes/responses": "workspace:*" @@ -7119,7 +7128,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/models@workspace:packages/models" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/features": "workspace:*" "@standardnotes/responses": "workspace:*" "@standardnotes/utils": "workspace:*" @@ -7170,7 +7179,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/responses@workspace:packages/responses" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/features": "workspace:*" "@standardnotes/security": ^1.1.0 "@types/jest": ^28.1.5 @@ -7228,12 +7237,13 @@ __metadata: dependencies: "@standardnotes/api": "workspace:^" "@standardnotes/auth": ^3.19.4 - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/encryption": "workspace:^" "@standardnotes/files": "workspace:^" "@standardnotes/models": "workspace:^" "@standardnotes/responses": "workspace:*" "@standardnotes/security": ^1.2.0 + "@standardnotes/sncrypto-common": "workspace:^" "@standardnotes/utils": "workspace:*" "@types/jest": ^28.1.5 "@typescript-eslint/eslint-plugin": ^5.30.0 @@ -7293,7 +7303,7 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/sncrypto-common@workspace:*, @standardnotes/sncrypto-common@workspace:packages/sncrypto-common": +"@standardnotes/sncrypto-common@workspace:*, @standardnotes/sncrypto-common@workspace:^, @standardnotes/sncrypto-common@workspace:packages/sncrypto-common": version: 0.0.0-use.local resolution: "@standardnotes/sncrypto-common@workspace:packages/sncrypto-common" dependencies: @@ -7343,7 +7353,7 @@ __metadata: "@babel/core": "*" "@babel/preset-env": "*" "@standardnotes/api": "workspace:*" - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/domain-events": ^2.39.0 "@standardnotes/encryption": "workspace:*" "@standardnotes/features": "workspace:*" @@ -7478,7 +7488,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/ui-services@workspace:packages/ui-services" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@standardnotes/features": "workspace:^" "@standardnotes/filepicker": "workspace:^" "@standardnotes/services": "workspace:^" @@ -7499,7 +7509,7 @@ __metadata: version: 0.0.0-use.local resolution: "@standardnotes/utils@workspace:packages/utils" dependencies: - "@standardnotes/common": ^1.39.0 + "@standardnotes/common": ^1.43.0 "@types/dompurify": ^2.3.3 "@types/jest": ^28.1.5 "@types/jsdom": ^16.2.14