0

Normally, you get single-use TOTP token, and need to wait for 30s for next one. This in not acceptable for tests, tho. Is there any way of skipping TOTP validation during testing or maybe guys you know some solution?

I am using cypress software.

1 Answers1

0

I'm myself looking for a solid solution, but here's what I'm doing atm.

  1. I created an e2e@mydomain.com user and logged the TOTP secret so I could use it in tests.
Auth.setupTOTP(user).then(secret => {console.log(secret)})
  1. Added the following cypress command with retry mechanism. (offset is used to avoid "already used" cognito errors when running multiple tests):
import { authenticator } from 'otplib';
import { Amplify, Auth } from 'aws-amplify';

Amplify.configure(Cypress.env('amplifyConfig'));

export function generateOTP(secret: string, offset = 0) {
  if (offset === 0) {
    return authenticator.generate(secret);
  }
  const allOptions = authenticator.allOptions();
  const delta = allOptions.step * 1000;
  authenticator.options = { epoch: Date.now() + offset * delta };
  const offsetToken = authenticator.generate(secret);
  authenticator.resetOptions();
  return offsetToken;
}

Cypress.Commands.add('loginByCognitoApi', (email: string, password: string) => {
  const log = Cypress.log({
    displayName: 'COGNITO LOGIN',
    message: [` Authenticating | ${email} `],
    autoEnd: false,
  });

  log.snapshot('before');

  const signIn = Auth.signIn({ username: email, password });

  cy.wrap(signIn, { log: false } )
    .then(async user => {
      // This retry logic seems to be kind of flaky
      let offset = 0;
      while (offset < 10) {
        try {
          const challengeSecret = 'OKBS5BUYW2FD32JASDQWLIVC5TNW7V3UZQZTG5OFVSFALQMK4LA';
          const code = generateOTP(challengeSecret, offset++);
          const result = await Auth.confirmSignIn(user, code, 'SOFTWARE_TOKEN_MFA');
          return result;
        } catch {}
      }
    })
    .then((cognitoResponse: any) => {
      const prefix = `${cognitoResponse.keyPrefix}.${cognitoResponse.username}`;
      const { idToken, accessToken, refreshToken, clockDrift } = cognitoResponse.signInUserSession;

      localStorage.setItem(`${prefix}.idToken`, idToken.jwtToken);
      localStorage.setItem(`${prefix}.accessToken`, accessToken.jwtToken);
      localStorage.setItem(`${prefix}.refreshToken`, refreshToken.token);
      localStorage.setItem(`${prefix}.clockDrift`, clockDrift);
      localStorage.setItem(`${cognitoResponse.keyPrefix}.LastAuthUser`, cognitoResponse.username);
      localStorage.setItem('amplify-signin-with-hostedUI', 'false');
      log.snapshot('after');
      log.end();
    });
});
  1. You can then use it like so:
  before(() => {
    cy.loginByCognitoApi(E2E_USER.email, E2E_USER.password);
    cy.visit('/');
  });

If anyone has a less flaky solution for signing in with amplify using TOTP in Cypress I'd like to see it also.