diff --git a/.github/workflows/deploy-playground-and-test.yml b/.github/workflows/deploy-playground-and-test.yml index d88c8bd94..f71646042 100644 --- a/.github/workflows/deploy-playground-and-test.yml +++ b/.github/workflows/deploy-playground-and-test.yml @@ -182,10 +182,9 @@ jobs: env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_MICROSOFT_EMAIL: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL }} - PLAYWRIGHT_MICROSOFT_EMAIL_LINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED }} - PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED }} - PLAYWRIGHT_MICROSOFT_PASSWORD: ${{ secrets.PLAYWRIGHT_MICROSOFT_PASSWORD }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ github.ref_name }} @@ -211,7 +210,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=complete platform=react run_type=commitly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=complete platform=react run_type=commitly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" @@ -257,7 +256,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=connect platform=react run_type=commitly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=connect platform=react run_type=commitly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 52941c765..891642224 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -90,10 +90,9 @@ jobs: env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_MICROSOFT_EMAIL: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL }} - PLAYWRIGHT_MICROSOFT_EMAIL_LINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED }} - PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED }} - PLAYWRIGHT_MICROSOFT_PASSWORD: ${{ secrets.PLAYWRIGHT_MICROSOFT_PASSWORD }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} @@ -119,7 +118,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=complete platform=react run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=complete platform=react run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" @@ -138,10 +137,9 @@ jobs: env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_MICROSOFT_EMAIL: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL }} - PLAYWRIGHT_MICROSOFT_EMAIL_LINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED }} - PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED }} - PLAYWRIGHT_MICROSOFT_PASSWORD: ${{ secrets.PLAYWRIGHT_MICROSOFT_PASSWORD }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} @@ -167,7 +165,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=complete platform=web-js run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=complete platform=web-js run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" @@ -186,10 +184,9 @@ jobs: env: PLAYWRIGHT_NUM_CORES: 4 PLAYWRIGHT_JWT_TOKEN: ${{ secrets.PLAYWRIGHT_JWT_TOKEN }} - PLAYWRIGHT_MICROSOFT_EMAIL: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL }} - PLAYWRIGHT_MICROSOFT_EMAIL_LINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED }} - PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED: ${{ secrets.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED }} - PLAYWRIGHT_MICROSOFT_PASSWORD: ${{ secrets.PLAYWRIGHT_MICROSOFT_PASSWORD }} + PLAYWRIGHT_GOOGLE_EMAIL: ${{ secrets.PLAYWRIGHT_GOOGLE_EMAIL }} + PLAYWRIGHT_GOOGLE_PASSWORD: ${{ secrets.PLAYWRIGHT_GOOGLE_PASSWORD }} + PLAYWRIGHT_GOOGLE_TOTP_SECRET: ${{ secrets.PLAYWRIGHT_GOOGLE_TOTP_SECRET }} GITHUB_RUN_ID: ${{ github.run_id }} SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }} GITHUB_BRANCH_NAME: ${{ env.BRANCH_NAME_RAW }} @@ -215,7 +212,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=complete platform=web-js-script run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=complete platform=web-js-script run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" @@ -261,7 +258,7 @@ jobs: TIME=$(xmllint --xpath 'string(/testsuites/@time)' test-results/results.xml) PASSED=$((TESTS - FAILURES - ERRORS - SKIPPED)) FAILED=$((FAILURES + ERRORS)) - SUMMARY="[Test Result] application=connect platform=react run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED" + SUMMARY="[Test Result] application=connect platform=react run_type=nightly execution_time=$TIME passed=$PASSED failed=$FAILED link=https://github.com/corbado/javascript/actions/runs/${GITHUB_RUN_ID}" TIMESTAMP=$(date +%s000) LOG_EVENT_JSON="[{\"timestamp\":$TIMESTAMP,\"message\":\"$SUMMARY\"}]" aws logs put-log-events --log-group-name "test-results-board" --log-stream-name "$LOG_STREAM_NAME" --log-events "$LOG_EVENT_JSON" diff --git a/package-lock.json b/package-lock.json index de599e2e2..3dd5c72ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8270,6 +8270,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/notp": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/notp/-/notp-2.0.5.tgz", + "integrity": "sha512-ZsZS0PYUa6ZE4K3yOGerBvaxCp4ePf6ZmkFbPeilcqz2Ui/lmXox7KlRt7XZkXzqUgXhFLkc09ixyVmFLCU3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "license": "MIT" @@ -19844,6 +19854,19 @@ "tslib": "^2.0.3" } }, + "node_modules/node-2fa": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/node-2fa/-/node-2fa-2.0.3.tgz", + "integrity": "sha512-PQldrOhjuoZyoydMvMSctllPN1ZPZ1/NwkEcgYwY9faVqE/OymxR+3awPpbWZxm6acLKqvmNqQmdqTsqYyflFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/notp": "^2.0.0", + "notp": "^2.0.3", + "thirty-two": "1.0.2", + "tslib": "^2.1.0" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "dev": true, @@ -20050,6 +20073,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/notp": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/notp/-/notp-2.0.3.tgz", + "integrity": "sha512-oBig/2uqkjQ5AkBuw4QJYwkEWa/q+zHxI5/I5z6IeP2NT0alpJFsP/trrfCC+9xOAgQSZXssNi962kp5KBmypQ==", + "dev": true, + "engines": { + "node": "> v0.6.0" + } + }, "node_modules/npm-bundled": { "version": "3.0.1", "dev": true, @@ -25361,6 +25393,15 @@ "node": ">=0.8" } }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "dev": true, + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/throat": { "version": "6.0.2", "license": "MIT" @@ -27509,6 +27550,7 @@ "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", "ngrok": "^5.0.0-beta.2", + "node-2fa": "^2.0.3", "playwright-slack-report": "^1.1.72" } }, diff --git a/packages/tests-e2e/.env.complete.example b/packages/tests-e2e/.env.complete.example index 430ecb9cf..7b0d36135 100644 --- a/packages/tests-e2e/.env.complete.example +++ b/packages/tests-e2e/.env.complete.example @@ -3,6 +3,6 @@ PLAYWRIGHT_TEST_URL=http://localhost:3000 DEVELOPERPANEL_API_URL=https://console.cloud.corbado-staging.io BACKEND_API_URL=https://backendapi.cloud.corbado-staging.io FRONTEND_API_URL_SUFFIX= -PLAYWRIGHT_JWT_TOKEN= -PLAYWRIGHT_MICROSOFT_EMAIL= -PLAYWRIGHT_MICROSOFT_PASSWORD= +PLAYWRIGHT_GOOGLE_EMAIL= +PLAYWRIGHT_GOOGLE_PASSWORD= +PLAYWRIGHT_GOOGLE_TOTP_SECRET= diff --git a/packages/tests-e2e/package.json b/packages/tests-e2e/package.json index f302faeb5..aa87efa6b 100644 --- a/packages/tests-e2e/package.json +++ b/packages/tests-e2e/package.json @@ -25,6 +25,7 @@ "@playwright/test": "^1.47.0", "@types/node": "^20.10.5", "ngrok": "^5.0.0-beta.2", + "node-2fa": "^2.0.3", "playwright-slack-report": "^1.1.72" }, "dependencies": { diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/LoginInitBlockModel.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/LoginInitBlockModel.ts index dcf2e741d..0396a3caf 100644 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/LoginInitBlockModel.ts +++ b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/LoginInitBlockModel.ts @@ -2,7 +2,7 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; import type { SocialProviderType } from '../../utils/constants'; -import { repeatSocialLogin, socialLogin } from './socialLogin'; +import { socialLogin } from '../../utils/externalauth'; export class LoginInitBlockModel { page: Page; @@ -39,12 +39,8 @@ export class LoginInitBlockModel { await this.page.getByRole('button', { name: 'Continue' }).click(); } - async submitSocialMicrosoft(email: string, password: string) { - await socialLogin(this.page, email, password); - } - - async resubmitSocialMicrosoft() { - await repeatSocialLogin(this.page); + async submitSocialGoogle(email: string, password: string, secret: string) { + await socialLogin(this.page, email, password, secret); } submitPasskeyButton() { diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/PasskeyAppendBlockModel.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/PasskeyAppendBlockModel.ts index 6c1e457e2..4ac9c0000 100644 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/PasskeyAppendBlockModel.ts +++ b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/PasskeyAppendBlockModel.ts @@ -28,6 +28,17 @@ export class PasskeyAppendBlockModel { } } + async startPasskeyOperation2(complete: boolean) { + const operationTrigger = () => this.page.getByRole('button', { name: 'Create passkey' }).click(); + if (complete) { + await this.virtualAuthenticator.startAndCompletePasskeyOperation(operationTrigger); + } else { + await this.virtualAuthenticator.startAndCancelPasskeyOperation(operationTrigger, () => + expectScreen(this.page, ScreenNames.PasskeyError), + ); + } + } + async retryPasskeyOperation(complete: boolean) { const operationTrigger = () => this.page.getByRole('button', { name: 'Try again' }).click(); if (complete) { diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/SignupInitBlockModel.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/SignupInitBlockModel.ts index d96de9c3a..fceb6120b 100644 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/SignupInitBlockModel.ts +++ b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/SignupInitBlockModel.ts @@ -2,8 +2,8 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; import type { SocialProviderType } from '../../utils/constants'; +import { repeatSocialLogin, socialLogin } from '../../utils/externalauth'; import { getRandomIntegerN } from '../../utils/random'; -import { repeatSocialLogin, socialLogin } from './socialLogin'; export class SignupInitBlockModel { page: Page; @@ -53,14 +53,22 @@ export class SignupInitBlockModel { return this.page.getByRole('button', { name: 'Continue', exact: true }).click(); } - async submitSocialMicrosoft(email: string, password: string) { - await socialLogin(this.page, email, password); + async submitSocialGoogle(email: string, password: string, secret: string) { + await socialLogin(this.page, email, password, secret); } - async resubmitSocialMicrosoft() { + async resubmitSocialGoogle() { await repeatSocialLogin(this.page); } + // async submitSocialMicrosoft(email: string, password: string) { + // await socialLoginDeprecated(this.page, email, password); + // } + // + // async resubmitSocialMicrosoft() { + // await repeatSocialLogin(this.page); + // } + expectErrorMissingUsername(): Promise { return this.#expectError('Please enter a username.'); } diff --git a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/socialLogin.ts b/packages/tests-e2e/src/complete/models/corbado-auth-blocks/socialLogin.ts deleted file mode 100644 index 8362b852c..000000000 --- a/packages/tests-e2e/src/complete/models/corbado-auth-blocks/socialLogin.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { Page } from '@playwright/test'; -import { expect } from '@playwright/test'; - -export const socialLogin = async (page: Page, email: string, password: string) => { - await page.getByTitle(`Continue with Microsoft`).click(); - await expect(page.getByRole('heading', { level: 1 })).toHaveText('Sign in'); - - await page.getByRole('textbox', { name: 'email' }).click(); - await page.getByRole('textbox', { name: 'email' }).fill(email); - await expect(page.getByRole('textbox', { name: 'email' })).toHaveValue(email); - - await page.getByRole('button', { name: 'Next' }).click(); - await expect(page.getByRole('heading', { level: 1 })).toHaveText('Enter password'); - - await page.getByPlaceholder('Password').click(); - await page.getByPlaceholder('Password').fill(password); - await expect(page.getByPlaceholder('Password')).toHaveValue(password); - - await page.getByTestId('textButtonContainer').getByRole('button', { name: 'Sign in' }).click(); - await expect(page.getByRole('heading', { level: 1 })).toHaveText('Stay signed in?'); - - await page.getByRole('button', { name: 'No' }).click(); -}; - -export const repeatSocialLogin = async (page: Page) => { - await page.getByTitle(`Continue with Microsoft`).click(); - await expect(page.getByRole('heading', { level: 1 })).toHaveText('Let this app access your info? (1 of 1 apps)'); - - await page.getByRole('button', { name: 'Accept' }).click(); -}; diff --git a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts index 53833a6f5..b6e880355 100644 --- a/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts +++ b/packages/tests-e2e/src/complete/scenarios/corbado-auth-general/socials.spec.ts @@ -6,6 +6,7 @@ import { ScreenNames, socialOperationTimeout, SocialProviderType, + socialTotalTimeout, } from '../../utils/constants'; import { createProjectNew, @@ -19,6 +20,7 @@ test.describe('social logins', () => { let projectId: string; // Microsoft social login requires longer timeout + test.describe.configure({ timeout: socialTotalTimeout }); test.use({ actionTimeout: socialOperationTimeout, navigationTimeout: socialOperationTimeout, @@ -69,20 +71,22 @@ test.describe('social logins', () => { test('signup with socials should be possible (account does not exist)', async ({ model }) => { await model.load(projectId, true, 'signup-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; - await model.signupInit.submitSocialMicrosoft(email, password); - await model.expectScreen(ScreenNames.PasskeyAppend1); + await model.signupInit.submitSocialGoogle(email, password, secret); + await model.expectScreen(ScreenNames.PasskeyAppend2); }); test.skip('signup with social should be possible (account exists, social has been linked)', async ({ model }) => { await model.load(projectId, true, 'signup-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; - await model.signupInit.submitSocialMicrosoft(email, password); + await model.signupInit.submitSocialGoogle(email, password, secret); await model.expectScreen(ScreenNames.PasskeyAppend1); await model.passkeyAppend.startPasskeyOperation(true); await model.expectScreen(ScreenNames.End); @@ -90,7 +94,7 @@ test.describe('social logins', () => { await model.load(projectId, true, 'signup-init'); - await model.signupInit.resubmitSocialMicrosoft(); + await model.signupInit.resubmitSocialGoogle(); // TODO: should successfully log in, but gets redirected to login-init instead. await model.expectScreen(ScreenNames.End); }); @@ -101,8 +105,9 @@ test.describe('social logins', () => { }) => { await model.load(projectId, true, 'signup-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; await model.signupInit.fillEmail(email); await model.signupInit.submitPrimary(); @@ -112,7 +117,7 @@ test.describe('social logins', () => { await model.load(projectId, true, 'signup-init'); - await model.signupInit.submitSocialMicrosoft(email, password); + await model.signupInit.submitSocialGoogle(email, password, secret); await model.expectScreen(ScreenNames.InitLogin); }); @@ -120,28 +125,30 @@ test.describe('social logins', () => { // redirects to passkey append screen await model.load(projectId, true, 'login-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; - await model.loginInit.submitSocialMicrosoft(email, password); + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; + await model.loginInit.submitSocialGoogle(email, password, secret); - await model.expectScreen(ScreenNames.PasskeyAppend1); + await model.expectScreen(ScreenNames.PasskeyAppend2); }); test('login with social should be possible (account exists, social has been linked)', async ({ model }) => { await model.load(projectId, true, 'signup-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL_LINKED ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; - await model.signupInit.submitSocialMicrosoft(email, password); - await model.expectScreen(ScreenNames.PasskeyAppend1); - await model.passkeyAppend.startPasskeyOperation(true); + await model.signupInit.submitSocialGoogle(email, password, secret); + await model.expectScreen(ScreenNames.PasskeyAppend2); + await model.passkeyAppend.startPasskeyOperation2(true); await model.expectScreen(ScreenNames.End); await model.logout(); await model.load(projectId, true, 'login-init'); - await model.signupInit.resubmitSocialMicrosoft(); + await model.signupInit.resubmitSocialGoogle(); await model.expectScreen(ScreenNames.End); }); @@ -151,8 +158,9 @@ test.describe('social logins', () => { }) => { await model.load(projectId, true, 'signup-init'); - const email = process.env.PLAYWRIGHT_MICROSOFT_EMAIL_UNLINKED ?? ''; - const password = process.env.PLAYWRIGHT_MICROSOFT_PASSWORD ?? ''; + const email = process.env.PLAYWRIGHT_GOOGLE_EMAIL ?? ''; + const password = process.env.PLAYWRIGHT_GOOGLE_PASSWORD ?? ''; + const secret = process.env.PLAYWRIGHT_GOOGLE_TOTP_SECRET ?? ''; await model.signupInit.fillEmail(email); await model.signupInit.submitPrimary(); @@ -162,7 +170,7 @@ test.describe('social logins', () => { await model.load(projectId, true, 'login-init'); - await model.signupInit.submitSocialMicrosoft(email, password); + await model.signupInit.submitSocialGoogle(email, password, secret); // TODO: should redirect to login-init screen, but gets successfully logged in insteaad. await model.expectScreen(ScreenNames.InitLogin); }); diff --git a/packages/tests-e2e/src/complete/utils/constants.ts b/packages/tests-e2e/src/complete/utils/constants.ts index 8784af2bd..e348e74fd 100644 --- a/packages/tests-e2e/src/complete/utils/constants.ts +++ b/packages/tests-e2e/src/complete/utils/constants.ts @@ -56,6 +56,7 @@ export const emailLinkUrlToken = 'UaTwjBJwyDLMGVbR7WHh'; export const totalTimeout = process.env.CI ? 30000 : 40000; export const operationTimeout = process.env.CI ? 5000 : 7000; +export const socialTotalTimeout = 45000; export const socialOperationTimeout = 10000; export const waitAfterLoad = 600; // timeout to reduce flakiness due to repetitive reloads diff --git a/packages/tests-e2e/src/complete/utils/externalauth.ts b/packages/tests-e2e/src/complete/utils/externalauth.ts new file mode 100644 index 000000000..bcdd10c99 --- /dev/null +++ b/packages/tests-e2e/src/complete/utils/externalauth.ts @@ -0,0 +1,114 @@ +import type { Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { generateToken } from 'node-2fa'; + +export const googleLogin = async (page: Page, gmail: string, password: string, secret: string) => { + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Sign in'); + const idBox = page.getByLabel('Email or phone'); + await idBox.click(); + await idBox.fill(gmail); + await page.getByRole('button', { name: 'Next' }).click(); + + await expect(page.getByRole('heading', { level: 1 })).toHaveText('Welcome'); + const pwBox = page.getByLabel('Enter your password'); + await pwBox.click(); + await pwBox.fill(password); + await page.getByRole('button', { name: 'Next' }).click(); + + await expect(page.getByRole('heading', { level: 1 })).toHaveText('2-Step Verification'); + const codeBox = page.getByLabel('Enter code'); + await codeBox.click(); + const firstTOTP = generateTOTP(secret); + await codeBox.fill(firstTOTP); + await page.getByRole('button', { name: 'Next' }).click(); + try { + await page.getByRole('button', { name: 'Compose' }).waitFor(); + return; + } catch { + // TOTP failed, so the code must've been used during previous login. + // Wait for the next code. + const nextTOTP = await generateNextTOTP(firstTOTP, secret); + await codeBox.fill(nextTOTP); + await page.getByRole('button', { name: 'Next' }).click(); + await expect(page.getByRole('button', { name: 'Compose' })).toBeVisible(); + } +}; + +const generateNextTOTP = async (previousTOTP: string, secret: string) => { + const currentTOTP = generateTOTP(secret); + if (currentTOTP !== previousTOTP) { + return currentTOTP; + } + const timestamp = Math.floor(Date.now() / 1000); + console.log(timestamp); + const timeWindow = timestamp % 30; + console.log(timeWindow); + const waitTime = (30 - timeWindow) * 1000 + 1000; + console.log(waitTime); + await new Promise(resolve => setTimeout(resolve, waitTime)); + console.log(Math.floor(Date.now() / 1000)); + return generateTOTP(secret); +}; + +const generateTOTP = (secret: string) => { + console.log('Secret:', secret); + const result = generateToken(secret); + console.log('Result from generateToken:', result); + expect(result).not.toBeNull(); + + return result!.token; +}; + +export const getEmailOtpCode = async ( + page: Page, + projectName = 'Corbado', + loggedIn = false, + gmail: string, + password: string, + secret: string, +) => { + if (loggedIn) { + // Wait for OTP code to arrive + await page.waitForTimeout(7000); + } + + const gmailPage = await page.context().newPage(); + await gmailPage.goto('https://mail.google.com'); + + if (!loggedIn) { + await googleLogin(gmailPage, gmail, password, secret); + } + + const inboxEntry = gmailPage.getByRole('link', { name: `is your ${projectName}` }).first(); + const emailTitle = await inboxEntry.innerText(); + + await gmailPage.close(); + + return emailTitle.split(' ')[0]; +}; + +export const socialLogin = async (page: Page, gmail: string, password: string, secret: string) => { + const gmailPage = await page.context().newPage(); + await gmailPage.goto('https://mail.google.com'); + + await googleLogin(gmailPage, gmail, password, secret); + await gmailPage.close(); + + await page.getByRole('button', { name: 'Continue with Google' }).click(); + await expect(page.getByRole('heading')).toHaveText('Choose an account'); + + await page.getByRole('button', { name: 'Corbado Systemtest' }).click(); + await expect(page.getByRole('heading')).toHaveText('corbado-staging.io wants to access your Google Account'); + + await page.getByRole('button', { name: 'Allow' }).click(); +}; + +export const repeatSocialLogin = async (page: Page) => { + await page.getByRole('button', { name: 'Continue with Google' }).click(); + await expect(page.getByRole('heading')).toHaveText('Choose an account'); + + await page.getByRole('button', { name: 'Corbado Systemtest' }).click(); + await expect(page.getByRole('heading')).toHaveText('corbado-staging.io wants to access your Google Account'); + + await page.getByRole('button', { name: 'Allow' }).click(); +};