From 10751ea2edb2ca48e4493b87053383f27460ef75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20S=C3=B3jko?= Date: Mon, 9 Jan 2023 08:13:12 +0100 Subject: [PATCH] feat(snjs): add getting recovery codes (#2132) --- ...ings-npm-1.19.0-3c8ae22306-85816e5d25.zip} | Bin 30790 -> 30831 bytes packages/snjs/lib/Application/Application.ts | 9 ++++ .../GetRecoveryCodes/GetRecoveryCodes.spec.ts | 48 ++++++++++++++++++ .../GetRecoveryCodes/GetRecoveryCodes.ts | 23 +++++++++ .../UseCase/UseCaseContainerInterface.ts | 1 + packages/snjs/package.json | 2 +- yarn.lock | 10 ++-- 7 files changed, 87 insertions(+), 6 deletions(-) rename .yarn/cache/{@standardnotes-settings-npm-1.18.4-391af3f6ce-73017d1951.zip => @standardnotes-settings-npm-1.19.0-3c8ae22306-85816e5d25.zip} (89%) create mode 100644 packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.spec.ts create mode 100644 packages/snjs/lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes.ts 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 92404773bfaafecf71bae9c6d293786ed0c7d069..3f085739ea730c2e6f38f478c6960e35ea1aad0f 100644 GIT binary patch delta 1728 zcmYjSd05lO7R|yI78A;jSc_nT7E!2StstlYf+Qd%64u9_u!RIfMIZ?fS;PSTSR@6k zfuNvG3A8L46+sqJ32TA$DQlY&6o?9b5RhW})%WI~JLjCackVYc_fF3WczFffnC_-N z8C*XM27zFTAdts3tSvx`R{@^7A!yFznj;A2^16;La-Z*5wT%c0KH&|Dw*3AleCess z&L;OacP+g+N4L|fvP;j*)E(*#eI-_|w!x1>VoKvMk)G)Jb$E%SCG%8Qs71G7t!T0_ zNUdJ9JHk>a-n=0}@iz@ojVE6+?Qo_EnyimQr_UWylk8EArzxjqBZO_$#W9!#Ne7%S@iYw4GDid%go^_8Q)*|uBYPOGAZN*1*q;SbiBpy7C zKrz>@KM^P{jCRO2(WZm;sh9{XrN0t{>@mOPm9NoP_07S*3UPdoCCWx$;Oxv?dk%jq zW$yag;Nmw;FJvT|{^HA3cr$<@fEDNU$~Id7sfRlaFc*?2J=XSk;>{ZEw|@8{Zmer} zaY60-tl{n8bquzt5SdJI!WF_~5h!V}pUJxp-5 zrcElgSH%?#O}Z8t|2_e`>Wh(ir6N7zwFQm6>Q~Yy{BxRUw@r@>1V0>8K| zNlQ0ZI;&Y^SPuEP{h@8?w+_p&v^deD*)Ja`O^Wx?K-Q_EAFAt~!5Jxs_hGMxI zur2D|2Kr9b<^%kIvovbv5pCuNN5$~gJ?RiV6}^5?o@WRDjyf}-|29nHX3faLeLZRP zFQOZ9Ne?ek#g)@_c#)stpLb5zFq?t`jIP#4xHqBv_d@SDNIiNi$BB3IPzA>V5)<`J zyJR1At(78u4j9a)AnB(d*~%rkh-w!59R8;|!o#>&++4sFE80|SxI^xf(Ui{QufJ0& zJb)0ZwEK?NP8_ioLkqGlq}t~h>R1!M)6E9qWpTxce35JwcU$M-{)81o!lZU}z>^Xnsc z;y(6AtIKD(>a=;A5@knCa67)YB6yZpIz^v|GByQE4;SB#QP)k04K96U)9T8hBVRsy zee~jwuf1lqI2hIoUJoftWZ}IQUwC^8b}>X;0@Z4P&rhUXZclZ6aPE7?128mafN>0# zbFU*OOZfOgS~RL*>|(1+(0R35b9Uucqqn8i3u4t+NONLP0|NHBj{+tMd60Sx0LI$L zo9Pn95P&5hjO2B_ zD1BgqiUv;r&PgSZ0be=kL{bHKL~~#7gwCOhg1Z8hD#WI!2E)?Qg!erhra=Ruo8O! delta 1718 zcmY+E2{aUV9LMJfGsb8NYZ`Z#$t}g?KFT=8(8x_;5oL^t8Ar=d?B?2`|0q|4i5yAJ z(vZltYfP(bRyiuyWHqvDqbw_LkGKE#e((SP{od#O-|zjt|L}YeyH1S4LY1-M*VM9@Xx8pg@SH}bZxtp z@W+Xo+;g<-0c=wa{-OAwLbT;Wo^T39X@Ex;@v}NUT)RX$diTvb@~KCY42zNWezsTV zdT6A9Q7xkn<%asc)+*s!Y4PE+F7bNh4037q&A~GTp|*8=Jac!^xo71eJdaKvK6+|x zm@RTubS-(J)oG!Az&UPQYdP>4)ep%snd)JM1$|cY86{`m$t#=^?fL$OeJ^G6F7aLo zS4*bBJNvUBigFKXK452`#Cm(k>~%e7MfzY?HnuVN&1R=c!!qvZ(%~+iuvez+Tpe~9 z@u5o)i-StERQS`3GmORR5}A12qCZS`P%Q$y((`Ldlyxs+g6B@&d~uSzukB+j*{*?2 z3G<_uu~{4?m(BDm?hF3D3^Y ztGGDAcYu2lxW+yQ6DXRUp~1SpC3PSi0@2@=+K9A=mHX>xI^EI8+k4*sYpG>|y9@U` zu_q^s8?Ak7GhsOQ2KQz0@AM%vW8;q<+K7S7Q5)uo28FrQlU*7Hv5^npqGq8lbC>og;2M=v#)YTc>@fUu=Fkj;Bi`{Hm zQ=piV)?%teq3&EN%L)?lAh3z{D=n^Sc_^6OLejHWcvwDO&$21k%4)%Sno!lbT8te>P41Sgf>8&v z!shde_G(&($4_pSWr_bJOIclNmb)oulNY}uN~K-#h#Q5IvEPr?+FU+WE|&AYsZ=QT zO@MbIZnfGS3e|@ zDTDv45q$Atl3X@xwHNC>-6bQBU-2C@Gd=ZPns;|guDoTat$Cm_Q;>5r^hi)h&E&1I zqpd_3yCK5BAcVbt?E31J{vzwfJM>pchodkFFX3;P)?+V@Rp8%LWat%=6A$j(8DqQ8 zCztu-2&2_oQS3MHGC$a0e6X6kA@;>zvJe;q0kN$68PQnjSA;?S`=0_*L>de@06NtI zn8jj&4sEyM|11dkZ*h|+9CMP@!DX5uY{m&dLyQ24tO`cx7_c`I2OA=PDP1*?2mC;J zAYjUZ)~zbvc^j-bA_r9HHn1L-?bIbm0SLF^3pGQ>K?PSYgu^sl0o~XTa4+b*TK4d{ni0pCz*;2ceYwYh_PVKIOjEejSr zFjD_C%}pNg*y2n8i{T05d2SyC$M{cr}uxjt^L72xJQvC1r kLLg56T(`AzlM8&dp&JoWU?@fh7U}o*@U+Akr@w%I0l#+$t^fc4 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:*"