feat(snjs): add account recovery e2e test suite (#2134)
* feat(snjs): add account recovery e2e test suite * fix(snjs): request params in account recovery tests * fix(snjs): context password passing * refactor: replace factory functions with context Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
@@ -49,8 +49,10 @@ export class AuthApiService implements AuthApiServiceInterface {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.authServer.recoveryKeyParams({
|
const response = await this.authServer.recoveryKeyParams({
|
||||||
apiVersion: ApiVersion.v0,
|
api_version: ApiVersion.v0,
|
||||||
...dto,
|
code_challenge: dto.codeChallenge,
|
||||||
|
recovery_codes: dto.recoveryCodes,
|
||||||
|
username: dto.username,
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -75,8 +77,11 @@ export class AuthApiService implements AuthApiServiceInterface {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.authServer.signInWithRecoveryCodes({
|
const response = await this.authServer.signInWithRecoveryCodes({
|
||||||
apiVersion: ApiVersion.v0,
|
api_version: ApiVersion.v0,
|
||||||
...dto,
|
code_verifier: dto.codeVerifier,
|
||||||
|
password: dto.password,
|
||||||
|
recovery_codes: dto.recoveryCodes,
|
||||||
|
username: dto.username,
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export interface RecoveryKeyParamsRequestParams {
|
export interface RecoveryKeyParamsRequestParams {
|
||||||
apiVersion: string
|
api_version: string
|
||||||
username: string
|
username: string
|
||||||
codeChallenge: string
|
code_challenge: string
|
||||||
recoveryCodes: string
|
recovery_codes: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export interface SignInWithRecoveryCodesRequestParams {
|
export interface SignInWithRecoveryCodesRequestParams {
|
||||||
apiVersion: string
|
api_version: string
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
codeVerifier: string
|
code_verifier: string
|
||||||
recoveryCodes: string
|
recovery_codes: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ const SessionPaths = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RecoveryPaths = {
|
const RecoveryPaths = {
|
||||||
generateRecoveryCodes: '/v1/auth/recovery/codes',
|
generateRecoveryCodes: '/v1/recovery/codes',
|
||||||
recoveryKeyParams: '/v1/auth/recovery/login-params',
|
recoveryKeyParams: '/v1/recovery/login-params',
|
||||||
signInWithRecoveryCodes: '/v1/auth/recovery/login',
|
signInWithRecoveryCodes: '/v1/recovery/login',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Paths = {
|
export const Paths = {
|
||||||
|
|||||||
139
packages/snjs/mocha/recovery.test.js
Normal file
139
packages/snjs/mocha/recovery.test.js
Normal file
@@ -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
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
<script type="module" src="session.test.js"></script>
|
<script type="module" src="session.test.js"></script>
|
||||||
<script type="module" src="subscriptions.test.js"></script>
|
<script type="module" src="subscriptions.test.js"></script>
|
||||||
<script type="module" src="workspaces.test.js"></script>
|
<script type="module" src="workspaces.test.js"></script>
|
||||||
|
<script type="module" src="recovery.test.js"></script>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
mocha.run();
|
mocha.run();
|
||||||
</script>
|
</script>
|
||||||
@@ -98,4 +99,4 @@
|
|||||||
<div id="mocha"></div>
|
<div id="mocha"></div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user