diff --git a/packages/api/src/Domain/Client/Auth/AuthApiService.ts b/packages/api/src/Domain/Client/Auth/AuthApiService.ts index 7b306c607a6..4787f28dcde 100644 --- a/packages/api/src/Domain/Client/Auth/AuthApiService.ts +++ b/packages/api/src/Domain/Client/Auth/AuthApiService.ts @@ -49,8 +49,10 @@ export class AuthApiService implements AuthApiServiceInterface { try { const response = await this.authServer.recoveryKeyParams({ - apiVersion: ApiVersion.v0, - ...dto, + api_version: ApiVersion.v0, + code_challenge: dto.codeChallenge, + recovery_codes: dto.recoveryCodes, + username: dto.username, }) return response @@ -75,8 +77,11 @@ export class AuthApiService implements AuthApiServiceInterface { try { const response = await this.authServer.signInWithRecoveryCodes({ - apiVersion: ApiVersion.v0, - ...dto, + api_version: ApiVersion.v0, + code_verifier: dto.codeVerifier, + password: dto.password, + recovery_codes: dto.recoveryCodes, + username: dto.username, }) return response diff --git a/packages/api/src/Domain/Request/Recovery/RecoveryKeyParamsRequestParams.ts b/packages/api/src/Domain/Request/Recovery/RecoveryKeyParamsRequestParams.ts index f2a688198c0..7c87c8fe906 100644 --- a/packages/api/src/Domain/Request/Recovery/RecoveryKeyParamsRequestParams.ts +++ b/packages/api/src/Domain/Request/Recovery/RecoveryKeyParamsRequestParams.ts @@ -1,6 +1,6 @@ export interface RecoveryKeyParamsRequestParams { - apiVersion: string + api_version: string username: string - codeChallenge: string - recoveryCodes: string + code_challenge: string + recovery_codes: string } diff --git a/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts b/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts index a096c9e02fb..f4233bab044 100644 --- a/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts +++ b/packages/api/src/Domain/Request/Recovery/SignInWithRecoveryCodesRequestParams.ts @@ -1,7 +1,7 @@ export interface SignInWithRecoveryCodesRequestParams { - apiVersion: string + api_version: string username: string password: string - codeVerifier: string - recoveryCodes: string + code_verifier: string + recovery_codes: string } diff --git a/packages/api/src/Domain/Server/Auth/Paths.ts b/packages/api/src/Domain/Server/Auth/Paths.ts index 94940a8e46b..e7155900be4 100644 --- a/packages/api/src/Domain/Server/Auth/Paths.ts +++ b/packages/api/src/Domain/Server/Auth/Paths.ts @@ -3,9 +3,9 @@ const SessionPaths = { } const RecoveryPaths = { - generateRecoveryCodes: '/v1/auth/recovery/codes', - recoveryKeyParams: '/v1/auth/recovery/login-params', - signInWithRecoveryCodes: '/v1/auth/recovery/login', + generateRecoveryCodes: '/v1/recovery/codes', + recoveryKeyParams: '/v1/recovery/login-params', + signInWithRecoveryCodes: '/v1/recovery/login', } export const Paths = { diff --git a/packages/snjs/mocha/recovery.test.js b/packages/snjs/mocha/recovery.test.js new file mode 100644 index 00000000000..57a9e985922 --- /dev/null +++ b/packages/snjs/mocha/recovery.test.js @@ -0,0 +1,139 @@ +import * as Factory from './lib/factory.js' + +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('account recovery', function () { + this.timeout(Factory.ThirtySecondTimeout) + + let application + let context + + beforeEach(async function () { + localStorage.clear() + context = await Factory.createAppContextWithFakeCrypto() + + await context.launch() + + application = context.application + + await context.register() + }) + + afterEach(async function () { + await context.deinit() + localStorage.clear() + }) + + it('should get the same recovery codes at each consecutive call', async () => { + let recoveryCodesSetting = await application.settings.getSetting(SettingName.RecoveryCodes) + expect(recoveryCodesSetting).to.equal(undefined) + + const generatedRecoveryCodesAfterFirstCall = await application.getRecoveryCodes.execute() + expect(generatedRecoveryCodesAfterFirstCall.getValue().length).to.equal(49) + + recoveryCodesSetting = await application.settings.getSetting(SettingName.RecoveryCodes) + expect(recoveryCodesSetting).to.equal(generatedRecoveryCodesAfterFirstCall.getValue()) + + const fetchedRecoveryCodesOnTheSecondCall = await application.getRecoveryCodes.execute() + expect(generatedRecoveryCodesAfterFirstCall.getValue()).to.equal(fetchedRecoveryCodesOnTheSecondCall.getValue()) + }) + + it('should allow to sign in with recovery codes', async () => { + const generatedRecoveryCodes = await application.getRecoveryCodes.execute() + + application = await context.signout() + + expect(await application.protocolService.getRootKey()).to.not.be.ok + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: generatedRecoveryCodes.getValue(), + username: context.email, + password: context.password, + }) + + expect(await application.protocolService.getRootKey()).to.be.ok + }) + + it('should automatically generate new recovery codes after recovery sign in', async () => { + const generatedRecoveryCodes = await application.getRecoveryCodes.execute() + + application = await context.signout() + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: generatedRecoveryCodes.getValue(), + username: context.email, + password: context.password, + }) + + const recoveryCodesAfterRecoverySignIn = await application.getRecoveryCodes.execute() + expect(recoveryCodesAfterRecoverySignIn.getValue()).not.to.equal(generatedRecoveryCodes.getValue()) + }) + + it('should disable MFA after recovery sign in', async () => { + const secret = await application.generateMfaSecret() + const token = await application.getOtpToken(secret) + + await application.enableMfa(secret, token) + + expect(await application.isMfaActivated()).to.equal(true) + + const generatedRecoveryCodes = await application.getRecoveryCodes.execute() + + application = await context.signout() + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: generatedRecoveryCodes.getValue(), + username: context.email, + password: context.password, + }) + + expect(await application.isMfaActivated()).to.equal(false) + }) + + it('should not allow to sign in with recovery codes and invalid credentials', async () => { + const generatedRecoveryCodes = await application.getRecoveryCodes.execute() + + application = await context.signout() + + expect(await application.protocolService.getRootKey()).to.not.be.ok + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: generatedRecoveryCodes.getValue(), + username: context.email, + password: 'foobar', + }) + + expect(await application.protocolService.getRootKey()).to.not.be.ok + }) + + it('should not allow to sign in with invalid recovery codes', async () => { + await application.getRecoveryCodes.execute() + + application = await context.signout() + + expect(await application.protocolService.getRootKey()).to.not.be.ok + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: 'invalid recovery codes', + username: context.email, + password: context.paswword, + }) + + expect(await application.protocolService.getRootKey()).to.not.be.ok + }) + + it('should not allow to sign in with recovery codes if user has none', async () => { + application = await context.signout() + + expect(await application.protocolService.getRootKey()).to.not.be.ok + + await application.signInWithRecoveryCodes.execute({ + recoveryCodes: 'foo bar', + username: context.email, + password: context.paswword, + }) + + expect(await application.protocolService.getRootKey()).to.not.be.ok + }) +}) diff --git a/packages/snjs/mocha/test.html b/packages/snjs/mocha/test.html index e9bc11b6f67..bcc21aa497e 100644 --- a/packages/snjs/mocha/test.html +++ b/packages/snjs/mocha/test.html @@ -89,6 +89,7 @@ + @@ -98,4 +99,4 @@
- \ No newline at end of file +