From b2faa815e967a9e7052b3ce6955e967c6bc68864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Wed, 2 Nov 2022 11:33:02 +0100 Subject: [PATCH] feat: add sending user requests to process (#1908) * feat: add sending user requests to process * fix(snjs): yarn lock * fix(snjs): imports * fix: specs --- ...ommon-npm-1.43.0-ad3f3ce881-5930059441.zip | Bin 0 -> 43837 bytes packages/api/package.json | 2 +- .../Domain/Client/User/UserApiOperations.ts | 5 + .../Domain/Client/User/UserApiService.spec.ts | 118 +++++++++++++++++- .../src/Domain/Client/User/UserApiService.ts | 65 ++++++++-- .../Client/User/UserApiServiceInterface.ts | 6 + packages/api/src/Domain/Client/index.ts | 1 + .../Request/User/UserDeletionRequestParams.ts | 6 + .../UserRequest/UserRequestRequestParams.ts | 7 ++ packages/api/src/Domain/Request/index.ts | 1 + .../Response/User/UserDeletionResponse.ts | 9 ++ .../Response/User/UserDeletionResponseBody.ts | 3 + .../UserRequest/UserRequestResponse.ts | 9 ++ .../UserRequest/UserRequestResponseBody.ts | 3 + packages/api/src/Domain/Response/index.ts | 4 + packages/api/src/Domain/Server/User/Paths.ts | 3 + .../src/Domain/Server/User/UserServer.spec.ts | 17 ++- .../api/src/Domain/Server/User/UserServer.ts | 8 ++ .../Domain/Server/User/UserServerInterface.ts | 3 + .../src/Domain/Server/UserRequest/Paths.ts | 11 ++ .../UserRequest/UserRequestServer.spec.ts | 32 +++++ .../Server/UserRequest/UserRequestServer.ts | 16 +++ .../UserRequest/UserRequestServerInterface.ts | 6 + packages/api/src/Domain/Server/index.ts | 2 + packages/encryption/package.json | 2 +- .../Encryption/EncryptionProviderInterface.ts | 47 ++++--- packages/features/package.json | 2 +- packages/filepicker/package.json | 2 +- packages/files/package.json | 2 +- packages/models/package.json | 2 +- packages/responses/package.json | 2 +- packages/services/jest.config.js | 8 +- packages/services/package.json | 3 +- .../Application/ApplicationInterface.ts | 2 +- .../src/Domain}/Challenge/Challenge.ts | 7 +- .../Challenge/ChallengeServiceInterface.ts | 18 ++- .../services/src/Domain/Challenge/index.ts | 1 + .../src/Domain/Item/ItemManagerInterface.ts | 40 +----- .../Domain/Protection/MobileUnlockTiming.ts | 4 + .../Protection/ProtectionClientInterface.ts} | 12 +- .../Domain/Protection/TimingDisplayOption.ts} | 5 +- .../Domain/Session/SessionManagerResponse.ts | 9 ++ .../Domain/Session/SessionsClientInterface.ts | 34 +++++ .../Domain/Storage/StorageServiceInterface.ts | 13 +- .../src/Domain/Strings/InfoStrings.ts} | 0 .../src/Domain/Strings}/Messages.ts | 0 .../src/Domain/Sync/SyncServiceInterface.ts | 7 ++ .../UserClientInterface.ts | 4 +- .../src/Domain}/User/UserService.ts | 88 +++++++------ packages/services/src/Domain/index.ts | 11 +- packages/snjs/lib/Application/Application.ts | 52 +++++--- .../snjs/lib/Client/NoteViewController.ts | 2 +- packages/snjs/lib/Migrations/Base.ts | 5 +- packages/snjs/lib/Migrations/Migration.ts | 9 +- .../lib/Services/Actions/ActionsService.ts | 3 +- packages/snjs/lib/Services/Api/ApiService.ts | 97 +++++++------- packages/snjs/lib/Services/Api/HttpService.ts | 8 +- packages/snjs/lib/Services/Api/index.ts | 1 - .../Services/Challenge/ChallengeOperation.ts | 3 +- .../Services/Challenge/ChallengeResponse.ts | 2 +- .../Services/Challenge/ChallengeService.ts | 2 +- packages/snjs/lib/Services/Challenge/index.ts | 1 - .../Services/Features/FeaturesService.spec.ts | 20 +-- .../lib/Services/Features/FeaturesService.ts | 22 ++-- .../KeyRecovery/KeyRecoveryService.ts | 5 +- packages/snjs/lib/Services/Mfa/MfaService.ts | 5 +- .../lib/Services/Mutator/MutatorService.ts | 16 ++- .../Services/Protection/ProtectionService.ts | 68 +++++++--- .../snjs/lib/Services/Protection/index.ts | 2 - .../lib/Services/Session/SessionManager.ts | 41 +++--- .../Session/SessionsClientInterface.ts | 8 -- packages/snjs/lib/Services/Session/index.ts | 1 - .../lib/Services/Settings/SettingsGateway.ts | 4 +- .../lib/Services/User/UserServerInterface.ts | 5 - packages/snjs/lib/Services/User/index.ts | 2 - packages/snjs/lib/Services/index.ts | 1 - packages/snjs/lib/Strings/index.ts | 2 - packages/snjs/package.json | 2 +- packages/ui-services/package.json | 2 +- packages/utils/package.json | 2 +- yarn.lock | 36 ++++-- 81 files changed, 766 insertions(+), 325 deletions(-) create mode 100644 .yarn/cache/@standardnotes-common-npm-1.43.0-ad3f3ce881-5930059441.zip create mode 100644 packages/api/src/Domain/Client/User/UserApiOperations.ts create mode 100644 packages/api/src/Domain/Request/User/UserDeletionRequestParams.ts create mode 100644 packages/api/src/Domain/Request/UserRequest/UserRequestRequestParams.ts create mode 100644 packages/api/src/Domain/Response/User/UserDeletionResponse.ts create mode 100644 packages/api/src/Domain/Response/User/UserDeletionResponseBody.ts create mode 100644 packages/api/src/Domain/Response/UserRequest/UserRequestResponse.ts create mode 100644 packages/api/src/Domain/Response/UserRequest/UserRequestResponseBody.ts create mode 100644 packages/api/src/Domain/Server/UserRequest/Paths.ts create mode 100644 packages/api/src/Domain/Server/UserRequest/UserRequestServer.spec.ts create mode 100644 packages/api/src/Domain/Server/UserRequest/UserRequestServer.ts create mode 100644 packages/api/src/Domain/Server/UserRequest/UserRequestServerInterface.ts rename packages/{snjs/lib/Services => services/src/Domain}/Challenge/Challenge.ts (92%) create mode 100644 packages/services/src/Domain/Protection/MobileUnlockTiming.ts rename packages/{snjs/lib/Services/Protection/ClientInterface.ts => services/src/Domain/Protection/ProtectionClientInterface.ts} (64%) rename packages/{snjs/lib/Services/Protection/MobileUnlockTiming.ts => services/src/Domain/Protection/TimingDisplayOption.ts} (53%) create mode 100644 packages/services/src/Domain/Session/SessionManagerResponse.ts create mode 100644 packages/services/src/Domain/Session/SessionsClientInterface.ts rename packages/{snjs/lib/Strings/Info.ts => services/src/Domain/Strings/InfoStrings.ts} (100%) rename packages/{snjs/lib/Services/Api => services/src/Domain/Strings}/Messages.ts (100%) rename packages/services/src/Domain/{Application => User}/UserClientInterface.ts (75%) rename packages/{snjs/lib/Services => services/src/Domain}/User/UserService.ts (87%) delete mode 100644 packages/snjs/lib/Services/Session/SessionsClientInterface.ts delete mode 100644 packages/snjs/lib/Services/User/UserServerInterface.ts delete mode 100644 packages/snjs/lib/Services/User/index.ts 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 0000000000000000000000000000000000000000..d68f37d41baee47e3e86b08c194f428f684fb80b GIT binary patch literal 43837 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 literal 0 HcmV?d00001 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