Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 69 additions & 81 deletions tests/functional/profile/totp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,33 +63,29 @@ test.group('Profile TOTP', (group) => {
test('verify queues an email when TOTP is enabled', async ({ client }) => {
const fakeMailer = mail.fake()

try {
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: false,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.post('/profile/totp/verify')
.json({ otp: adonis2fa.generateToken(totpSecret.secret)! })
.withCsrfToken()
.loginAs(user)

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Two-factor authentication enabled')
notification.message.assertTextIncludes('Two-factor authentication was enabled')
return true
})
} finally {
mail.restore()
}
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: false,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.post('/profile/totp/verify')
.json({ otp: adonis2fa.generateToken(totpSecret.secret)! })
.withCsrfToken()
.loginAs(user)

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Two-factor authentication enabled')
notification.message.assertTextIncludes('Two-factor authentication was enabled')
return true
})
})

test('destroy throws error when TOTP is not enabled', async ({ client }) => {
Expand All @@ -108,33 +104,29 @@ test.group('Profile TOTP', (group) => {
test('destroy queues an email when TOTP is disabled', async ({ client }) => {
const fakeMailer = mail.fake()

try {
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: true,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.delete('/profile/totp')
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(204)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Two-factor authentication disabled')
notification.message.assertTextIncludes('Two-factor authentication was disabled')
return true
})
} finally {
mail.restore()
}
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: true,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.delete('/profile/totp')
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(204)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Two-factor authentication disabled')
notification.message.assertTextIncludes('Two-factor authentication was disabled')
return true
})
})

test('regenerateRecoveryCodes throws error when TOTP is not enabled', async ({ client }) => {
Expand All @@ -153,32 +145,28 @@ test.group('Profile TOTP', (group) => {
test('regenerateRecoveryCodes queues an email when recovery codes change', async ({ client }) => {
const fakeMailer = mail.fake()

try {
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: true,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.post('/profile/totp/regeneration')
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Recovery codes regenerated')
notification.message.assertTextIncludes('New two-factor recovery codes were generated')
return true
})
} finally {
mail.restore()
}
const totpSecret = await adonis2fa.generateSecret('user@example.com')
const user = await createUser({
email: 'user@example.com',
totpEnabled: true,
totpSecretEncrypted: encryption.encrypt(totpSecret.secret),
totpRecoveryCodesEncrypted: encryption.encrypt(['CODE-1', 'CODE-2']),
})

const response = await client
.post('/profile/totp/regeneration')
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Recovery codes regenerated')
notification.message.assertTextIncludes('New two-factor recovery codes were generated')
return true
})
})
})
139 changes: 66 additions & 73 deletions tests/functional/profile/webauthn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,88 +141,81 @@ test.group('Profile WebAuthn', (group) => {
test('store queues an email when a passkey is registered', async ({ client }) => {
const fakeMailer = mail.fake()

try {
const user = await createUser()

app.container.swap(WebauthnService, () => {
const service = new WebauthnService()
Sinon.stub(service.webauthnServer, 'verifyRegistrationResponse').resolves({
verified: true,
registrationInfo: {
credential: {
id: 'credential-id',
publicKey: new Uint8Array([1, 2, 3]),
counter: 0,
},
credentialDeviceType: 'singleDevice',
credentialBackedUp: false,
const user = await createUser()

app.container.swap(WebauthnService, () => {
const service = new WebauthnService()
Sinon.stub(service.webauthnServer, 'verifyRegistrationResponse').resolves({
verified: true,
registrationInfo: {
credential: {
id: 'credential-id',
publicKey: new Uint8Array([1, 2, 3]),
counter: 0,
},
} as any)
return service
})
credentialDeviceType: 'singleDevice',
credentialBackedUp: false,
},
} as any)
return service
})

const response = await client
.post('/profile/webauthn')
.json({
friendlyName: 'Work laptop',
attestation: {
id: 'test-id',
rawId: 'test-raw-id',
type: 'public-key',
response: {
clientDataJSON: 'test',
attestationObject: 'test',
},
const response = await client
.post('/profile/webauthn')
.json({
friendlyName: 'Work laptop',
attestation: {
id: 'test-id',
rawId: 'test-raw-id',
type: 'public-key',
response: {
clientDataJSON: 'test',
attestationObject: 'test',
},
})
.withCsrfToken()
.loginAs(user)
.withSession({ ...withSecurityConfirmed(), [WEBAUTHN_REG_CHALLENGE_KEY]: 'test-challenge' })

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Passkey added')
notification.message.assertTextIncludes('A passkey was added to your account.')
notification.message.assertTextIncludes('Passkey name: Work laptop')
return true
},
})
} finally {
mail.restore()
app.container.restore(WebauthnService)
}
.withCsrfToken()
.loginAs(user)
.withSession({ ...withSecurityConfirmed(), [WEBAUTHN_REG_CHALLENGE_KEY]: 'test-challenge' })

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Passkey added')
notification.message.assertTextIncludes('A passkey was added to your account.')
notification.message.assertTextIncludes('Passkey name: Work laptop')
return true
})

app.container.restore(WebauthnService)
})

test('destroy queues an email when a passkey is removed', async ({ client }) => {
const fakeMailer = mail.fake()

try {
const user = await createUser()
const credential = await createWebauthnCredential({
userId: user.id,
friendlyName: 'Phone',
})
const user = await createUser()
const credential = await createWebauthnCredential({
userId: user.id,
friendlyName: 'Phone',
})

const response = await client
.delete(`/profile/webauthn/${credential.id}`)
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Passkey removed')
notification.message.assertTextIncludes('A passkey was removed from your account.')
notification.message.assertTextIncludes('Passkey name: Phone')
return true
})
} finally {
mail.restore()
}
const response = await client
.delete(`/profile/webauthn/${credential.id}`)
.withCsrfToken()
.loginAs(user)
.withSession(withSecurityConfirmed())

response.assertStatus(200)

fakeMailer.mails.assertQueuedCount(SecuritySettingsChangedMail, 1)
fakeMailer.mails.assertQueued(SecuritySettingsChangedMail, (notification) => {
notification.message.assertTo(user.email)
notification.message.assertSubject('Passkey removed')
notification.message.assertTextIncludes('A passkey was removed from your account.')
notification.message.assertTextIncludes('Passkey name: Phone')
return true
})
})
})