Skip to content

Commit

Permalink
test(e2e): complete SPA tests for all mfa flows
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Oct 19, 2021
1 parent 0cc984b commit 2196129
Show file tree
Hide file tree
Showing 21 changed files with 718 additions and 597 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ describe('Basic email profile with failing login flows', () => {
})

it('fails when a disallowed return_to url is requested', () => {
cy.visit(route + '?return_to=https://not-allowed', {failOnStatusCode: false})
if (app === 'react') {
cy.location('pathname').should('include', '/login')
cy.get('.Toastify').should('contain.text', 'The return_to address is not allowed.')
} else {
cy.location('pathname').should('contain', 'error')
cy.get('code').should('contain.text', 'Requested return_to URL \\"https://not-allowed\\" is not whitelisted.')
}
cy.shouldErrorOnDisallowedReturnTo(route + '?return_to=https://not-allowed', {app})
})

describe('shows validation errors when invalid signup data is used', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,7 @@ describe('Registration failures with email profile', () => {
})

it('fails when a disallowed return_to url is requested', () => {
cy.visit(route + '?return_to=https://not-allowed', {failOnStatusCode: false})
if (app === 'react') {
cy.location('pathname').should('include', '/registration')
cy.get('.Toastify').should('contain.text', 'The return_to address is not allowed.')
} else {
cy.location('pathname').should('contain', 'error')
cy.get('code').should('contain.text', 'Requested return_to URL \\"https://not-allowed\\" is not whitelisted.')
}
cy.shouldErrorOnDisallowedReturnTo(route + '?return_to=https://not-allowed', {app})
})

describe('show errors when invalid signup data is used', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,39 +43,13 @@ context('Settings failures with email profile', () => {

describe('global errors', () => {
it('fails when CSRF is incorrect', () => {
let initial
cy.location().should((location) => {
initial = location.search
})

cy.getCookies().should((cookies) => {
const csrf = cookies.find(({name}) => name.indexOf('csrf') > -1)
expect(csrf).to.not.be.undefined
cy.clearCookie(csrf.name)
})

cy.get('button[name="method"][value="profile"]').click()

// We end up at a new flow
cy.location('search').should('not.eq', initial)
if (app === 'express') {
cy.location('pathname').should('include', '/error')
cy.get('code').should('contain.text', 'csrf_token')
} else {
cy.location('pathname').should('include', '/settings')
cy.get('.Toastify').should('contain.text', 'A security violation was detected, please fill out the form again.')
}
cy.get('input[name="password"]')
.type('123456')
cy.shouldHaveCsrfError({app})
})

it('fails when a disallowed return_to url is requested', () => {
cy.visit(route + '?return_to=https://not-allowed', {failOnStatusCode: false})
if (app === 'react') {
cy.location('pathname').should('include', '/settings')
cy.get('.Toastify').should('contain.text', 'The return_to address is not allowed.')
} else {
cy.location('pathname').should('contain', 'error')
cy.get('code').should('contain.text', 'Requested return_to URL \\"https://not-allowed\\" is not whitelisted.')
}
cy.shouldErrorOnDisallowedReturnTo(route + '?return_to=https://not-allowed', {app})
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,37 @@ context('Settings success with email profile', () => {
})

describe('profile', () => {
it('modifies an unprotected trait', () => {
it('modifies an unprotected traits', () => {
cy.get('input[name="traits.website"]')
.clear()
.type('https://github.com/ory')
cy.get('button[value="profile"]').click()
cy.get('input[name="traits.age"]')
.clear()
.type('30')
cy.get('input[type="checkbox"][name="traits.tos"]')
.click({force: true})
cy.submitProfileForm()
cy.expectSettingsSaved()

cy.get('input[name="traits.website"]').should(
'contain.value',
'https://github.com/ory'
)
cy.get('input[type="checkbox"][name="traits.tos"]')
.should('be.checked')
.click({force: true})
cy.get('input[name="traits.age"]')
.should('have.value','30')
.clear()
.type('90')

cy.submitProfileForm()
cy.expectSettingsSaved()

cy.get('input[type="checkbox"][name="traits.tos"]')
.should('not.be.checked')
cy.get('input[name="traits.age"]')
.should('have.value','90')
})

it('modifies a protected trait with privileged session', () => {
Expand Down
14 changes: 8 additions & 6 deletions test/e2e/cypress/integration/profiles/mfa/lookup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {gen, website} from '../../../helpers'
import {routes as express} from "../../../helpers/express";
import {routes as react} from "../../../helpers/react";

context('Registration success with email profile', () => {
context('2FA lookup secrets', () => {
[
{
login: react.login,
Expand Down Expand Up @@ -54,17 +54,19 @@ context('Registration success with email profile', () => {
})
cy.expectSettingsSaved()

cy.clearCookies()
cy.login({email: email, password: password, cookieUrl: base})

cy.visit(login + '?aal=aal2')
cy.get('h2').should(
'contain.text',
'Two-Factor Authentication'
)
cy.get('*[name="method"][value="totp"]').should('not.exist')
cy.get('*[name="method"][value="lookup_secret"]').should('not.exist')
cy.get('*[name="method"][value="password"]').should('not.exist')

cy.clearCookies()
cy.login({email: email, password: password, cookieUrl: base})
})

return

it('should go through several lookup secret lifecycles', () => {
cy.visit(settings)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
import { APP_URL, gen, website } from '../../../helpers'
import { authenticator } from 'otplib'
import {routes as react} from "../../../helpers/react";
import {routes as express} from "../../../helpers/express";

context('MFA Profile', () => {
describe('Test MFA combinations', () => {
before(() => {
cy.useConfigProfile('mfa')
})

context('2FA with various methods', () => {
[
{
login: react.login,
settings: react.settings,
base: react.base,
app: 'react', profile: 'spa'
},
{
login: express.login,
settings: express.settings,
base: express.base,
app: 'express', profile: 'mfa'
}
].forEach(({settings, login, profile, app, base}) => {
describe(`for app ${app}`, () => {
before(() => {
cy.useConfigProfile(profile)
})
let email = gen.email()
let password = gen.password()

Expand All @@ -15,7 +30,7 @@ context('MFA Profile', () => {
email = gen.email()
password = gen.password()
cy.registerApi({ email, password, fields: { 'traits.website': website } })
cy.login({ email, password })
cy.login({ email, password, cookieUrl: base })
cy.longPrivilegedSessionTime()
cy.task('sendCRI', {
query: 'WebAuthn.disable',
Expand All @@ -24,7 +39,7 @@ context('MFA Profile', () => {
})

it('should set up an use all mfa combinations', () => {
cy.visit(APP_URL + '/settings')
cy.visit(settings)
cy.task('sendCRI', {
query: 'WebAuthn.enable',
opts: {}
Expand All @@ -43,46 +58,37 @@ context('MFA Profile', () => {
}).then(() => {
// Set up TOTP
let secret
cy.get('p[data-testid="text-totp_secret_key-content"]').then(($e) => {
cy.get('[data-testid="node/text/totp_secret_key/text"]').then(($e) => {
secret = $e.text().trim()
})
cy.get('input[name="totp_code"]').then(($e) => {
cy.get('[name="totp_code"]').then(($e) => {
cy.wrap($e).type(authenticator.generate(secret))
})
cy.get('*[name="method"][value="totp"]').click()
cy.get('form .messages .message').should(
'contain.text',
'Your changes have been saved!'
)
cy.get('[name="method"][value="totp"]').click()
cy.expectSettingsSaved()

// Set up lookup secrets
cy.get('button[name="lookup_secret_regenerate"]').click()
cy.get('[name="lookup_secret_regenerate"]').click()
let codes
cy.get('p[data-testid="text-lookup_secret_codes-content"]').then(
($e) => {
codes = $e.text().trim().split(', ')
cy.getLookupSecrets().then(
(c) => {
codes = c
}
)
cy.get('button[name="lookup_secret_confirm"]').click()
cy.get('form .messages .message').should(
'contain.text',
'Your changes have been saved!'
)
cy.get('[name="lookup_secret_confirm"]').click()
cy.expectSettingsSaved()

// Set up WebAuthn
cy.get('*[name="webauthn_register_displayname"]').type('my-key')
cy.get('[name="webauthn_register_displayname"]').type('my-key')
// We need a workaround here. So first we click, then we submit
cy.get('*[name="webauthn_register_trigger"]').click()
cy.get('form .messages .message').should(
'contain.text',
'Your changes have been saved!'
)
cy.clickWebAuthButton('register')
cy.expectSettingsSaved()

cy.visit(APP_URL + '/login?aal=aal2&refresh=true')
cy.get('input[name="totp_code"]').then(($e) => {
cy.visit(login + '?aal=aal2&refresh=true')
cy.get('[name="totp_code"]').then(($e) => {
cy.wrap($e).type(authenticator.generate(secret))
})
cy.get('*[name="method"][value="totp"]').click()
cy.get('[name="method"][value="totp"]').click()
cy.getSession({
expectAal: 'aal2',
expectMethods: [
Expand All @@ -95,8 +101,8 @@ context('MFA Profile', () => {
})

// Use TOTP
cy.visit(APP_URL + '/login?aal=aal2&refresh=true')
cy.get('button[name="webauthn_login_trigger"]').click()
cy.visit(login + '?aal=aal2&refresh=true')
cy.clickWebAuthButton('login')
cy.getSession({
expectAal: 'aal2',
expectMethods: [
Expand All @@ -110,11 +116,11 @@ context('MFA Profile', () => {
})

// Use lookup
cy.visit(APP_URL + '/login?aal=aal2&refresh=true')
cy.get('input[name="lookup_secret"]').then(($e) => {
cy.visit(login + '?aal=aal2&refresh=true')
cy.get('[name="lookup_secret"]').then(($e) => {
cy.wrap($e).type(codes[1])
})
cy.get('*[name="method"][value="lookup_secret"]').click()
cy.get('[name="method"][value="lookup_secret"]').click()
cy.getSession({
expectAal: 'aal2',
expectMethods: [
Expand All @@ -131,4 +137,5 @@ context('MFA Profile', () => {
})
})
})
})
})
39 changes: 0 additions & 39 deletions test/e2e/cypress/integration/profiles/mfa/settings.spec.js

This file was deleted.

57 changes: 57 additions & 0 deletions test/e2e/cypress/integration/profiles/mfa/settings.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {gen, website} from '../../../helpers'
import {routes as express} from "../../../helpers/express";
import {routes as react} from "../../../helpers/react";

context('2FA UI settings tests', () => {
[
{
settings: react.settings,
base: react.base,
app: 'react', profile: 'spa'
},
{
settings: express.settings,
base: express.base,
app: 'express', profile: 'mfa'
}
].forEach(({settings, profile, base, app}) => {
describe(`for app ${app}`, () => {
before(() => {
cy.useConfigProfile(profile)
})

const email = gen.email()
const password = gen.password()

before(() => {
cy.registerApi({email, password, fields: {'traits.website': website}})
})

beforeEach(() => {
cy.clearCookies()
cy.login({email, password, cookieUrl: base})
cy.visit(settings)
})

it('shows all settings forms', () => {
cy.get('h3').should('contain.text', 'Profile Settings')
cy.get('h3').should('contain.text', 'Change Password')
cy.get('h3').should('contain.text', 'Manage 2FA Backup Recovery Codes')
cy.get('h3').should('contain.text', 'Manage 2FA TOTP Authenticator App')
cy.get('h3').should('contain.text', 'Manage Hardware Tokens')
cy.get('input[name="traits.email"]').should('contain.value', email)
cy.get('input[name="traits.website"]').should('contain.value', website)

cy.get('[data-testid="node/text/totp_secret_key/label"]').should(
'contain.text',
'This is your authenticator app secret'
)
cy.get('button').should(
'contain.text',
'Generate new backup recovery codes'
)
cy.get('button').should('contain.text', 'Add security key')
})
})
})
})

0 comments on commit 2196129

Please sign in to comment.