cypress/integration/ete/reset_password_1.3.cy.ts (451 lines of code) (raw):
import { Status } from '../../../src/server/models/okta/User';
import { randomPassword } from '../../support/commands/testUser';
const breachCheck = () => {
cy.intercept({
method: 'GET',
url: 'https://api.pwnedpasswords.com/range/*',
}).as('breachCheck');
};
describe('Password reset flow in Okta - useOktaClassic', () => {
context('Account exists', () => {
it("changes the reader's password", () => {
const encodedReturnUrl =
'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay';
const encodedRef = 'https%3A%2F%2Fm.theguardian.com';
const refViewId = 'testRefViewId';
const clientId = 'jobs';
// these params should *not* persist between initial registration and welcome page
// despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method
// and subsequent interception, and not by gateway
const appClientId = 'appClientId1';
const fromURI = 'fromURI1';
breachCheck();
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.visit(
`/reset-password?useOktaClassic=true&returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`,
);
const timeRequestWasMade = new Date();
cy.contains('Reset password');
cy.get('input[name=email]').type(emailAddress);
// Continue checking the password reset flow after reCAPTCHA assertions above.
cy.get('[data-cy="main-form-submit-button"]').click();
cy.contains('Check your inbox');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ token }) => {
cy.visit(`/reset-password/${token}`);
cy.get('form')
.should('have.attr', 'action')
.and('match', new RegExp(encodedReturnUrl))
.and('match', new RegExp(refViewId))
.and('match', new RegExp(encodedRef))
.and('match', new RegExp(clientId))
.and('not.match', new RegExp(appClientId))
.and('not.match', new RegExp(fromURI));
//we are reloading here to make sure the params are persisted even on page refresh
cy.reload();
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password updated');
cy.contains(emailAddress.toLowerCase());
cy.url().should('contain', encodedReturnUrl);
cy.url().should('contain', refViewId);
cy.url().should('contain', encodedRef);
cy.url().should('contain', clientId);
cy.url().should('not.contain', 'useOkta=true');
cy.url().should('not.contain', appClientId);
cy.url().should('not.contain', fromURI);
});
});
});
it("changes the reader's password, and has a prefixed recovery token when using a native app", () => {
const encodedReturnUrl =
'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay';
const encodedRef = 'https%3A%2F%2Fm.theguardian.com';
const refViewId = 'testRefViewId';
const clientId = 'jobs';
// these params should *not* persist between initial registration and welcome page
// despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method
// and subsequent interception, and not by gateway
const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID');
const fromURI = 'fromURI1';
breachCheck();
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.visit(
`/reset-password?useOktaClassic=true&returnUrl=${encodedReturnUrl}&ref=${encodedRef}&refViewId=${refViewId}&clientId=${clientId}&appClientId=${appClientId}&fromURI=${fromURI}`,
);
const timeRequestWasMade = new Date();
cy.contains('Reset password');
cy.get('input[name=email]').type(emailAddress);
// Continue checking the password reset flow after reCAPTCHA assertions above.
cy.get('[data-cy="main-form-submit-button"]').click();
cy.contains('Check your inbox');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ token }) => {
expect(token).to.have.string('al_');
cy.visit(`/reset-password/${token}`);
cy.get('form')
.should('have.attr', 'action')
.and('match', new RegExp(encodedReturnUrl))
.and('match', new RegExp(refViewId))
.and('match', new RegExp(encodedRef))
.and('match', new RegExp(clientId))
.and('not.match', new RegExp(appClientId))
.and('not.match', new RegExp(fromURI));
//we are reloading here to make sure the params are persisted even on page refresh
cy.reload();
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password updated');
cy.contains(emailAddress.toLowerCase());
cy.url().should('contain', encodedReturnUrl);
cy.url().should('contain', refViewId);
cy.url().should('contain', encodedRef);
cy.url().should('contain', clientId);
cy.url().should('not.contain', 'useOkta=true');
cy.url().should('not.contain', appClientId);
cy.url().should('not.contain', fromURI);
});
});
});
it("changes the reader's password and overrides returnUrl from encryptedStateCookie if one set on reset password page url", () => {
const encodedReturnUrl =
'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay';
breachCheck();
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.visit(
`/reset-password?useOktaClassic=true&returnUrl=${encodedReturnUrl}`,
);
const timeRequestWasMade = new Date();
cy.contains('Reset password');
cy.get('input[name=email]').type(emailAddress);
// Continue checking the password reset flow after reCAPTCHA assertions above.
cy.get('[data-cy="main-form-submit-button"]').click();
cy.contains('Check your inbox');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ token }) => {
const newReturnUrl = encodeURIComponent(
'https://www.theguardian.com/technology/2017/may/04/nier-automata-sci-fi-game-sleeper-hit-designer-yoko-taro',
);
cy.visit(`/reset-password/${token}&returnUrl=${newReturnUrl}`);
cy.url()
.should('contain', newReturnUrl)
.and('not.contain', encodedReturnUrl);
cy.get('form')
.should('have.attr', 'action')
.and('match', new RegExp(newReturnUrl))
.and('not.match', new RegExp(encodedReturnUrl));
//we are reloading here to make sure the params are persisted even on page refresh
cy.reload();
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password updated');
cy.contains(emailAddress.toLowerCase());
cy.url()
.should('contain', newReturnUrl)
.and('not.contain', encodedReturnUrl)
.and('not.contain', 'useOkta=true');
});
});
});
it('overrides appClientId and fromURI if set on reset password page url', () => {
const encodedReturnUrl =
'https%3A%2F%2Fm.code.dev-theguardian.com%2Ftravel%2F2019%2Fdec%2F18%2Ffood-culture-tour-bethlehem-palestine-east-jerusalem-photo-essay';
// these params should *not* persist between initial registration and welcome page
// despite the fact that they PersistableQueryParams, as these are set by the Okta SDK sign in method
// and subsequent interception, and not by gateway
const appClientId1 = 'appClientId1';
const fromURI1 = '/oauth2/v1/authorize';
breachCheck();
cy
.createTestUser({
isUserEmailValidated: true,
})
?.then(({ emailAddress }) => {
cy.visit(
`/reset-password?useOktaClassic=true&returnUrl=${encodedReturnUrl}&appClientId=${appClientId1}&fromURI=${fromURI1}`,
);
const timeRequestWasMade = new Date();
cy.contains('Reset password');
cy.get('input[name=email]').type(emailAddress);
// Continue checking the password reset flow after reCAPTCHA assertions above.
cy.get('[data-cy="main-form-submit-button"]').click();
cy.contains('Check your inbox');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ token }) => {
// should contain these params instead
const appClientId2 = 'appClientId2';
const fromURI2 = '/oauth2/v2/authorize';
cy.visit(
`/reset-password/${token}&appClientId=${appClientId2}&fromURI=${fromURI2}`,
);
cy.url()
.should('contain', appClientId2)
.and('contain', fromURI2)
.and('not.contain', appClientId1)
.and('not.contain', fromURI1);
cy.get('form')
.should('have.attr', 'action')
.and('match', new RegExp(appClientId2))
.and('match', new RegExp(encodeURIComponent(fromURI2)))
.and('not.match', new RegExp(appClientId1))
.and('not.match', new RegExp(encodeURIComponent(fromURI1)));
});
});
});
});
context('STAGED user', () => {
it("changes the reader's password", () => {
breachCheck();
cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => {
cy.getTestOktaUser(emailAddress).then((oktaUser) => {
expect(oktaUser.status).to.eq(Status.STAGED);
cy.visit('/reset-password?useOktaClassic=true');
const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('button[type="submit"]').click();
cy.contains('Check your inbox');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/\/set-password\/([^"]*)/,
).then(({ links, body }) => {
expect(body).to.have.string('Welcome back');
expect(body).to.have.string('Create password');
expect(links.length).to.eq(2);
const setPasswordLink = links.find((s) =>
s.text?.includes('Create password'),
);
expect(setPasswordLink?.href ?? '').not.to.have.string(
'useOkta=true',
);
cy.visit(setPasswordLink?.href as string);
cy.contains('Create password');
cy.contains(emailAddress);
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password created');
cy.contains(emailAddress.toLowerCase());
});
});
});
});
it("changes the reader's password, and has a prefixed recovery token when using a native app", () => {
breachCheck();
cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => {
cy.getTestOktaUser(emailAddress).then((oktaUser) => {
expect(oktaUser.status).to.eq(Status.STAGED);
const appClientId = Cypress.env('OKTA_ANDROID_CLIENT_ID');
const fromURI = 'fromURI1';
cy.visit(
`/reset-password?useOktaClassic=true&appClientId=${appClientId}&fromURI=${fromURI}`,
);
const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('button[type="submit"]').click();
cy.contains('Check your inbox');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/\/set-password\/([^"]*)/,
).then(({ links, body }) => {
expect(body).to.have.string('Welcome back');
expect(body).to.have.string('Create password');
expect(links.length).to.eq(2);
const setPasswordLink = links.find((s) =>
s.text?.includes('Create password'),
);
expect(setPasswordLink?.href ?? '')
.to.have.string('al_')
.and.not.to.have.string('useOkta=true');
cy.visit(setPasswordLink?.href as string);
cy.contains('Create password');
cy.contains(emailAddress);
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password created');
cy.contains(emailAddress.toLowerCase());
});
});
});
});
});
context('PROVISIONED user', () => {
it("changes the reader's password", () => {
breachCheck();
cy.createTestUser({ isGuestUser: true })?.then(({ emailAddress }) => {
cy.activateTestOktaUser(emailAddress).then(() => {
cy.getTestOktaUser(emailAddress).then((oktaUser) => {
expect(oktaUser.status).to.eq(Status.PROVISIONED);
cy.visit('/reset-password?useOktaClassic=true');
const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('button[type="submit"]').click();
cy.contains('Check your inbox');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/\/set-password\/([^"]*)/,
).then(({ links, body }) => {
expect(body).to.have.string('Welcome back');
expect(body).to.have.string('Create password');
expect(links.length).to.eq(2);
const setPasswordLink = links.find((s) =>
s.text?.includes('Create password'),
);
expect(setPasswordLink?.href ?? '').not.to.have.string(
'useOkta=true',
);
cy.visit(setPasswordLink?.href as string);
cy.contains('Create password');
cy.contains(emailAddress);
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password created');
cy.contains(emailAddress.toLowerCase());
});
});
});
});
});
});
context('RECOVERY user', () => {
it("changes the reader's password", () => {
breachCheck();
cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => {
cy.resetOktaUserPassword(emailAddress).then(() => {
cy.getTestOktaUser(emailAddress).then((oktaUser) => {
expect(oktaUser.status).to.eq(Status.RECOVERY);
cy.visit('/reset-password?useOktaClassic=true');
const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('button[type="submit"]').click();
cy.contains('Check your inbox');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ links, body }) => {
expect(body).to.have.string('Password reset');
expect(body).to.have.string('Reset password');
expect(links.length).to.eq(3);
const resetPasswordLink = links.find((s) =>
s.text?.includes('Reset password'),
);
expect(resetPasswordLink?.href ?? '').not.to.have.string(
'useOkta=true',
);
cy.visit(resetPasswordLink?.href as string);
cy.contains('Create new password');
cy.contains(emailAddress);
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password updated');
cy.contains(emailAddress.toLowerCase());
});
});
});
});
});
});
context('PASSWORD_EXPIRED user', () => {
it("changes the reader's password", () => {
breachCheck();
cy.createTestUser({ isGuestUser: false })?.then(({ emailAddress }) => {
cy.expireOktaUserPassword(emailAddress).then(() => {
cy.getTestOktaUser(emailAddress).then((oktaUser) => {
expect(oktaUser.status).to.eq(Status.PASSWORD_EXPIRED);
cy.visit('/reset-password?useOktaClassic=true');
const timeRequestWasMade = new Date();
cy.get('input[name=email]').type(emailAddress);
cy.get('button[type="submit"]').click();
cy.contains('Check your inbox');
cy.contains(emailAddress);
cy.contains('send again');
cy.contains('try another address');
cy.checkForEmailAndGetDetails(
emailAddress,
timeRequestWasMade,
/reset-password\/([^"]*)/,
).then(({ links, body }) => {
expect(body).to.have.string('Password reset');
expect(body).to.have.string('Reset password');
expect(links.length).to.eq(3);
const resetPasswordLink = links.find((s) =>
s.text?.includes('Reset password'),
);
expect(resetPasswordLink?.href ?? '').not.to.have.string(
'useOkta=true',
);
cy.visit(resetPasswordLink?.href as string);
cy.contains('Create new password');
cy.contains(emailAddress);
cy.get('input[name=password]').type(randomPassword());
cy.wait('@breachCheck');
cy.get('[data-cy="main-form-submit-button"]')
.click()
.should('be.disabled');
cy.contains('Password updated');
cy.contains(emailAddress.toLowerCase());
});
});
});
});
});
});
// /verify-email is just a reskinned reset password page
context('/verify-email page', () => {
it('redirects to /verify-email if a token is in the URL', () => {
cy.visit('/verify-email/123');
cy.url().should('contain', '/verify-email');
});
it('shows the reset passsword success page when the form is submitted', () => {
cy.visit('/verify-email');
cy.contains('Verify your email');
cy.contains(
'As a security measure, to verify your email, you will need to reset your password.',
);
cy.get('input[name=email]').type('test@email.com');
cy.get('button[type="submit"]').click();
cy.contains('Enter your one-time code');
});
});
});