diff --git a/.yarn/cache/@standardnotes-settings-npm-1.18.4-391af3f6ce-73017d1951.zip b/.yarn/cache/@standardnotes-settings-npm-1.19.0-3c8ae22306-85816e5d25.zip similarity index 89% rename from .yarn/cache/@standardnotes-settings-npm-1.18.4-391af3f6ce-73017d1951.zip rename to .yarn/cache/@standardnotes-settings-npm-1.19.0-3c8ae22306-85816e5d25.zip index 92404773b..3f085739e 100644 Binary files a/.yarn/cache/@standardnotes-settings-npm-1.18.4-391af3f6ce-73017d1951.zip and b/.yarn/cache/@standardnotes-settings-npm-1.19.0-3c8ae22306-85816e5d25.zip differ diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index e0443833c..44c219732 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -97,6 +97,7 @@ import { SessionStorageMapper } from '@Lib/Services/Mapping/SessionStorageMapper import { LegacySessionStorageMapper } from '@Lib/Services/Mapping/LegacySessionStorageMapper' import { SignInWithRecoveryCodes } from '@Lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes' import { UseCaseContainerInterface } from '@Lib/Domain/UseCase/UseCaseContainerInterface' +import { GetRecoveryCodes } from '@Lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes' /** How often to automatically sync, in milliseconds */ const DEFAULT_AUTO_SYNC_INTERVAL = 30_000 @@ -177,6 +178,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli private declare authManager: AuthClientInterface private declare _signInWithRecoveryCodes: SignInWithRecoveryCodes + private declare _getRecoveryCodes: GetRecoveryCodes private internalEventBus!: ExternalServices.InternalEventBusInterface @@ -263,6 +265,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return this._signInWithRecoveryCodes } + get getRecoveryCodes(): UseCaseInterface { + return this._getRecoveryCodes + } + public get files(): FilesClientInterface { return this.fileService } @@ -1218,6 +1224,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ;(this.authenticatorManager as unknown) = undefined ;(this.authManager as unknown) = undefined ;(this._signInWithRecoveryCodes as unknown) = undefined + ;(this._getRecoveryCodes as unknown) = undefined this.services = [] } @@ -1776,5 +1783,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.sessionManager, this.internalEventBus, ) + + this._getRecoveryCodes = new GetRecoveryCodes(this.authManager, this.settingsService) } } diff --git a/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.spec.ts b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.spec.ts new file mode 100644 index 000000000..f23193305 --- /dev/null +++ b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.spec.ts @@ -0,0 +1,48 @@ +import { AuthClientInterface } from '@standardnotes/services' +import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface' + +import { GetRecoveryCodes } from './GetRecoveryCodes' + +describe('GetRecoveryCodes', () => { + let authClient: AuthClientInterface + let settingsClient: SettingsClientInterface + + const createUseCase = () => new GetRecoveryCodes(authClient, settingsClient) + + beforeEach(() => { + authClient = {} as jest.Mocked + authClient.generateRecoveryCodes = jest.fn().mockResolvedValue('recovery-codes') + + settingsClient = {} as jest.Mocked + settingsClient.getSetting = jest.fn().mockResolvedValue('existing-recovery-codes') + }) + + it('should return existing recovery codes if they exist', async () => { + const useCase = createUseCase() + + const result = await useCase.execute() + + expect(result.getValue()).toBe('existing-recovery-codes') + }) + + it('should generate recovery codes if they do not exist', async () => { + settingsClient.getSetting = jest.fn().mockResolvedValue(undefined) + + const useCase = createUseCase() + + const result = await useCase.execute() + + expect(result.getValue()).toBe('recovery-codes') + }) + + it('should return error if recovery codes could not be generated', async () => { + settingsClient.getSetting = jest.fn().mockResolvedValue(undefined) + authClient.generateRecoveryCodes = jest.fn().mockResolvedValue(false) + + const useCase = createUseCase() + + const result = await useCase.execute() + + expect(result.isFailed()).toBe(true) + }) +}) diff --git a/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts new file mode 100644 index 000000000..698b1e2ff --- /dev/null +++ b/packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts @@ -0,0 +1,23 @@ +import { AuthClientInterface } from '@standardnotes/services' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { SettingName } from '@standardnotes/settings' + +import { SettingsClientInterface } from '@Lib/Services/Settings/SettingsClientInterface' + +export class GetRecoveryCodes implements UseCaseInterface { + constructor(private authClient: AuthClientInterface, private settingsClient: SettingsClientInterface) {} + + async execute(): Promise> { + const existingRecoveryCodes = await this.settingsClient.getSetting(SettingName.RecoveryCodes) + if (existingRecoveryCodes !== undefined) { + return Result.ok(existingRecoveryCodes) + } + + const generatedRecoveryCodes = await this.authClient.generateRecoveryCodes() + if (generatedRecoveryCodes === false) { + return Result.fail('Could not generate recovery codes') + } + + return Result.ok(generatedRecoveryCodes) + } +} diff --git a/packages/snjs/lib/Domain/UseCase/UseCaseContainerInterface.ts b/packages/snjs/lib/Domain/UseCase/UseCaseContainerInterface.ts index 3a723df98..c86ac3a45 100644 --- a/packages/snjs/lib/Domain/UseCase/UseCaseContainerInterface.ts +++ b/packages/snjs/lib/Domain/UseCase/UseCaseContainerInterface.ts @@ -2,4 +2,5 @@ import { UseCaseInterface } from '@standardnotes/domain-core' export interface UseCaseContainerInterface { get signInWithRecoveryCodes(): UseCaseInterface + get getRecoveryCodes(): UseCaseInterface } diff --git a/packages/snjs/package.json b/packages/snjs/package.json index 03792882b..d0ddfe014 100644 --- a/packages/snjs/package.json +++ b/packages/snjs/package.json @@ -45,7 +45,7 @@ "@standardnotes/responses": "workspace:*", "@standardnotes/security": "^1.7.0", "@standardnotes/services": "workspace:*", - "@standardnotes/settings": "^1.18.4", + "@standardnotes/settings": "^1.19.0", "@standardnotes/sncrypto-common": "workspace:*", "@standardnotes/sncrypto-web": "workspace:*", "@standardnotes/utils": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 4d8727375..d2a95a37a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6025,12 +6025,12 @@ __metadata: languageName: unknown linkType: soft -"@standardnotes/settings@npm:^1.18.4": - version: 1.18.4 - resolution: "@standardnotes/settings@npm:1.18.4" +"@standardnotes/settings@npm:^1.19.0": + version: 1.19.0 + resolution: "@standardnotes/settings@npm:1.19.0" dependencies: reflect-metadata: ^0.1.13 - checksum: 73017d19517c5719a3611385b76a5a3273f03c570c42da79656d0df04cb539f6a5eeb7a5d7db30a8ffc7fd0ef4c801924ff27093f84b2c40ad1542b0397745ac + checksum: 85816e5d25e2a4cfe014dbe371bc30902b1bb194f5d3c021fe754678ff9bd693255f1d209a66c4375334b1edd40a8f4133480a20a85b88859b3cfbff1a570cc9 languageName: node linkType: hard @@ -6111,7 +6111,7 @@ __metadata: "@standardnotes/responses": "workspace:*" "@standardnotes/security": ^1.7.0 "@standardnotes/services": "workspace:*" - "@standardnotes/settings": ^1.18.4 + "@standardnotes/settings": ^1.19.0 "@standardnotes/sncrypto-common": "workspace:*" "@standardnotes/sncrypto-web": "workspace:*" "@standardnotes/utils": "workspace:*"