Skip to content

Commit

Permalink
Merge pull request #4 from roicarrera/feature/otp_login_for_cypress
Browse files Browse the repository at this point in the history
Login with MFA with Cypress
  • Loading branch information
roicarrera authored Oct 21, 2024
2 parents 6450e54 + ab435c4 commit a045724
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 213 deletions.
14 changes: 13 additions & 1 deletion docs/modules/quickstarters/pages/e2e-cypress.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,21 @@ RUN chgrp -R 0 $HOME && \
USER 1001
----

== Configuring Microsoft Account with Google Authenticator

To use the login with MFA function, the following requirements have to be met:

1. A testing account must be created with access to the application under test.

2. The account must have MFA enabled.

3. The MFA method used must be OTP (One Time Password).

4. The secret key generated during the setup process must be used for the OTP generation. In the case of microsoft, it can be retrieved by clicking the "Can't scan image" button during the setup process.

== Cypress Cloud

To use Cypress Cloud within the Cypress QuickSarter, follow these steps:
To use Cypress Cloud within the Cypress QuickStarter, follow these steps:

1. **Create a project in Cypress Cloud.** Access Cypress Cloud by following this link (https://cloud.cypress.io/), and create a project. This project will be used to store your Cypress tests and results.

Expand Down
4 changes: 3 additions & 1 deletion e2e-cypress/Jenkinsfile.template
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ def stageTest(def context) {
// azureClientId = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_ID}' | base64 -d")
// azureClientSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.AZURE_CLIENT_SECRET}' | base64 -d")
// azureTenantId = sh(returnStdout: true, script:"oc get configmaps azure -o jsonpath='{.data.AZURE_TENANT}'") // config map values are not base64 encoded
// authenticatorOTPSecret = sh(returnStdout: true, script:"oc get secret azure -o jsonpath='{.data.OTP_SECRET}' | base64 -d")

withEnv(["TAGVERSION=${context.tagversion}",
"NEXUS_HOST=${context.nexusHost}",
Expand All @@ -65,7 +66,8 @@ def stageTest(def context) {
// "CYPRESS_CLIENT_ID=${azureClientId}",
// "CYPRESS_CLIENT_SECRET=${azureClientSecret}",
// "CYPRESS_USERNAME=${cypressUser}",
// "CYPRESS_PASSWORD=${cypressPassword}"
// "CYPRESS_PASSWORD=${cypressPassword}",
// "OTP_SECRET=${authenticatorOTPSecret}",
"COMMIT_INFO_SHA=${context.gitCommit}",
"BUILD_NUMBER=${context.buildNumber}",
]) {
Expand Down
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-acceptance.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-installation.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress-integration.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
3 changes: 3 additions & 0 deletions e2e-cypress/files/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ export default defineConfig({
return (await import('./plugins/index')).default(on, config);
},
},
// env: {
// otp_secret: process.env.OTP_SECRET
// },
})
1 change: 1 addition & 0 deletions e2e-cypress/files/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"junit-report-merger": "^7.0.0",
"mocha-junit-reporter": "^2.2.1",
"npm-run-all": "^4.1.5",
"otplib": "^12.0.1",
"rimraf": "^6.0.1",
"sharp": "^0.33.5",
"typescript": "^5.5.4"
Expand Down
7 changes: 5 additions & 2 deletions e2e-cypress/files/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
// ***********************************************

import { addGenericLoginCommands } from './generic-login';
import { addMsalv2LoginCommand } from './msalv2-login';
import { addLoginToAADWithMFA, addLoginToAAD, addGetTOTP, addSessionLoginWithMFA } from './login-functions';

addGenericLoginCommands();
addMsalv2LoginCommand();
addGetTOTP();
addSessionLoginWithMFA();
addLoginToAAD();
addLoginToAADWithMFA();
70 changes: 1 addition & 69 deletions e2e-cypress/files/support/e2e.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,4 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup
function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
const log = Cypress.log({
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})
log.snapshot('before')

loginViaAAD(username, password)

log.snapshot('after')
log.end()
})
import './commands';

export const consoleLogs: string[] = [];

Expand Down
120 changes: 120 additions & 0 deletions e2e-cypress/files/support/login-functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
//For more details, you can follow this link:
//https://docs.cypress.io/guides/end-to-end-testing/azure-active-directory-authentication#Microsoft-AAD-Application-Setup

import { authenticator } from 'otplib';

function loginViaAAD(username: string, password: string) {

//Go to your application URL and trigger the login.
cy.visit('')

//If needed, navigate and click on the login button.
//As an example:
//cy.get('button#signIn').click()

//Login to your AAD tenant.
//ATENTION: The redirection can happen at the 'login.microsoftonline.com' and also it might redirect as well to 'login.live.com'
cy.origin(
'https://login.microsoftonline.com',
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.get('input[type="email"]').type(username, {
log: false,
})
cy.get('input[type="submit"]').click()
cy.get('input[type="password"]').type(password, {
log: false,
})
cy.get('input[type="submit"]').click()
}
)

//Depending on the user and how they are registered with Microsoft, the origin may go to live.com
//cy.origin(
// 'login.live.com',
// {
// args: {
// password,
// },
// },
// ({ password }) => {
// cy.get('input[type="password"]').type(password, {
// log: false,
// })
// cy.get('input[type="submit"]').click()
// cy.get('#idBtn_Back').click()
// }
//)
}

//See how to use it at:
//tests/acceptance/acceptance.spec.cy.ts
export function addLoginToAAD() {
Cypress.Commands.add('loginToAAD', (username: string, password: string) => {
cy.session([username], () => {
const log = Cypress.log({
autoEnd: false,
displayName: 'Azure Active Directory Login',
message: [`🔐 Authenticating | ${username}`],
});
log.snapshot('before');

loginViaAAD(username, password);

// Ensure Microsoft has redirected us back to the sample app with our logged in user.
cy.url().should('equal', Cypress.config().baseUrl)

log.snapshot('after');
log.end();
});
});
}

export function addSessionLoginWithMFA() {
Cypress.Commands.add('sessionLoginWithMFA', (username: string, password: string) => {
cy.session('login', () => cy.loginToAADWithMFA(username, password), {
validate: () => cy.getAllLocalStorage().should(validateLocalStorage),
cacheAcrossSpecs: true
})
})
}

export function addLoginToAADWithMFA() {
Cypress.Commands.add('loginToAADWithMFA', (username: string, password: string) => {
loginViaAAD(username, password);
cy.getTOTP().then((otp1) => {
const objOTP = { otp: otp1 }
cy.origin('https://login.microsoftonline.com/', { args: objOTP }, ({ otp }) => {
cy.get("[name='otc']").type(otp)
cy.get('input[type="submit"]').click()
})
})
})
}

export function addGetTOTP() {
Cypress.Commands.add('getTOTP', () => {
return authenticator.generate(Cypress.env('otp_secret'))
})
}

const validateLocalStorage = (localStorage: Record<string, unknown>) =>
Cypress._.some(localStorage, (value: unknown, key: string) =>
key.includes('CognitoIdentityServiceProvider'),
)

declare global {
namespace Cypress {
interface Chainable<> {
loginToAAD(username: string, password: string);
loginToAADWithMFA(username: string, password: string);
sessionLoginWithMFA(username: string, password: string);
getTOTP();
}
}
}
Loading

0 comments on commit a045724

Please sign in to comment.