mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-07 07:16:54 +00:00
chore: reproducible pipeline with dev containers (#10305)
# Which Problems Are Solved - The previous monorepo in monorepo structure for the login app and its related packages was fragmented, complicated and buggy. - The process for building and testing the login container was inconsistent between local development and CI. - Lack of clear documentation as well as easy and reliable ways for non-frontend developers to reproduce and fix failing PR checks locally. # How the Problems Are Solved - Consolidated the login app and its related npm packages by moving the main package to `apps/login/apps/login` and merging `apps/login/packages/integration` and `apps/login/packages/acceptance` into the main `apps/login` package. - Migrated from Docker Compose-based test setups to dev container-based setups, adding support for multiple dev container configurations: - `.devcontainer/base` - `.devcontainer/turbo-lint-unit` - `.devcontainer/turbo-lint-unit-debug` - `.devcontainer/login-integration` - `.devcontainer/login-integration-debug` - Added npm scripts to run the new dev container setups, enabling exact reproduction of GitHub PR checks locally, and updated the pipeline to use these containers. - Cleaned up Dockerfiles and docker-bake.hcl files to only build the production image for the login app. - Cleaned up compose files to focus on dev environments in dev containers. - Updated `CONTRIBUTING.md` with guidance on running and debugging PR checks locally using the new dev container approach. - Introduced separate Dockerfiles for the login app to distinguish between using published client packages and building clients from local protos. - Ensured the login container is always built in the pipeline for use in integration and acceptance tests. - Updated Makefile and GitHub Actions workflows to use `--frozen-lockfile` for installing pnpm packages, ensuring reproducible installs. - Disabled GitHub release creation by the changeset action. - Refactored the `/build` directory structure for clarity and maintainability. - Added a `clean` command to `docks/package.json`. - Experimentally added `knip` to the `zitadel-client` package for improved linting of dependencies and exports. # Additional Changes - Fixed Makefile commands for consistency and reliability. - Improved the structure and clarity of the `/build` directory to support seamless integration of the login build. - Enhanced documentation and developer experience for running and debugging CI checks locally. # Additional Context - See updated `CONTRIBUTING.md` for new local development and debugging instructions. - These changes are a prerequisite for further improvements to the CI pipeline and local development workflow. - Closes #10276
This commit is contained in:
7
apps/login/acceptance/tests/admin.spec.ts
Normal file
7
apps/login/acceptance/tests/admin.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { test } from "@playwright/test";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
|
||||
test("admin login", async ({ page }) => {
|
||||
await loginWithPassword(page, process.env["ZITADEL_ADMIN_USER"], "Password1!");
|
||||
await loginScreenExpect(page, "ZITADEL Admin");
|
||||
});
|
||||
12
apps/login/acceptance/tests/code-screen.ts
Normal file
12
apps/login/acceptance/tests/code-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
|
||||
const codeTextInput = "code-text-input";
|
||||
|
||||
export async function codeScreen(page: Page, code: string) {
|
||||
await page.getByTestId(codeTextInput).pressSequentially(code);
|
||||
}
|
||||
|
||||
export async function codeScreenExpect(page: Page, code: string) {
|
||||
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify OTP code");
|
||||
}
|
||||
17
apps/login/acceptance/tests/code.ts
Normal file
17
apps/login/acceptance/tests/code.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { codeScreen } from "./code-screen";
|
||||
import { getOtpFromSink } from "./sink";
|
||||
|
||||
export async function otpFromSink(page: Page, key: string) {
|
||||
const c = await getOtpFromSink(key);
|
||||
await code(page, c);
|
||||
}
|
||||
|
||||
export async function code(page: Page, code: string) {
|
||||
await codeScreen(page, code);
|
||||
await page.getByTestId("submit-button").click();
|
||||
}
|
||||
|
||||
export async function codeResend(page: Page) {
|
||||
await page.getByTestId("resend-button").click();
|
||||
}
|
||||
12
apps/login/acceptance/tests/email-verify-screen.ts
Normal file
12
apps/login/acceptance/tests/email-verify-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
|
||||
const codeTextInput = "code-text-input";
|
||||
|
||||
export async function emailVerifyScreen(page: Page, code: string) {
|
||||
await page.getByTestId(codeTextInput).pressSequentially(code);
|
||||
}
|
||||
|
||||
export async function emailVerifyScreenExpect(page: Page, code: string) {
|
||||
await expect(page.getByTestId(codeTextInput)).toHaveValue(code);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Could not verify email");
|
||||
}
|
||||
69
apps/login/acceptance/tests/email-verify.spec.ts
Normal file
69
apps/login/acceptance/tests/email-verify.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { emailVerify, emailVerifyResend } from "./email-verify";
|
||||
import { emailVerifyScreenExpect } from "./email-verify-screen";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
import { getCodeFromSink } from "./sink";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: false,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("user email not verified, verify", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
await emailVerify(page, c);
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("user email not verified, resend, verify", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
// auto-redirect on /verify
|
||||
await emailVerifyResend(page);
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await emailVerify(page, c);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("user email not verified, resend, old code", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
const c = await getCodeFromSink(user.getUsername());
|
||||
await emailVerifyResend(page);
|
||||
// wait for resend of the code
|
||||
await page.waitForTimeout(2000);
|
||||
await emailVerify(page, c);
|
||||
await emailVerifyScreenExpect(page, c);
|
||||
});
|
||||
|
||||
test("user email not verified, wrong code", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
// auto-redirect on /verify
|
||||
const code = "wrong";
|
||||
await emailVerify(page, code);
|
||||
await emailVerifyScreenExpect(page, code);
|
||||
});
|
||||
15
apps/login/acceptance/tests/email-verify.ts
Normal file
15
apps/login/acceptance/tests/email-verify.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { emailVerifyScreen } from "./email-verify-screen";
|
||||
|
||||
export async function startEmailVerify(page: Page, loginname: string) {
|
||||
await page.goto("./verify");
|
||||
}
|
||||
|
||||
export async function emailVerify(page: Page, code: string) {
|
||||
await emailVerifyScreen(page, code);
|
||||
await page.getByTestId("submit-button").click();
|
||||
}
|
||||
|
||||
export async function emailVerifyResend(page: Page) {
|
||||
await page.getByTestId("resend-button").click();
|
||||
}
|
||||
102
apps/login/acceptance/tests/idp-apple.spec.ts
Normal file
102
apps/login/acceptance/tests/idp-apple.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
// Note for all tests, in case Apple doesn't deliver all relevant information per default
|
||||
// We should add an action in the needed cases
|
||||
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Apple IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given an Apple IDP is configured on the organization
|
||||
// Given the user has an Apple added as auth method
|
||||
// User authenticates with Apple
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Apple IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given an Apple IDP is configured on the organization
|
||||
// Given the user has an Apple added as auth method
|
||||
// User is redirected to Apple
|
||||
// User authenticates with Apple and gets an error
|
||||
// User is redirect back to login
|
||||
// An error is shown to the user "Something went wrong in Apple Login"
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Apple IDP, no user linked, user link successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Apple is configure on the organization as only authencation method
|
||||
// Given idp Apple is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Apple
|
||||
// User authenticates in Apple with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
99
apps/login/acceptance/tests/idp-generic-jwt.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-generic-jwt.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic JWT IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic JWT IDP is configured on the organization
|
||||
// Given the user has Generic JWT IDP added as auth method
|
||||
// User authenticates with the Generic JWT IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic JWT IDP is configured on the organization
|
||||
// Given the user has Generic JWT IDP added as auth method
|
||||
// User is redirected to the Generic JWT IDP
|
||||
// User authenticates with the Generic JWT IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic JWT IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic JWT is configure on the organization as only authencation method
|
||||
// Given idp Generic JWT is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic JWT
|
||||
// User authenticates in Generic JWT with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
99
apps/login/acceptance/tests/idp-generic-oauth.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-generic-oauth.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic OAuth IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic OAuth IDP is configured on the organization
|
||||
// Given the user has Generic OAuth IDP added as auth method
|
||||
// User authenticates with the Generic OAuth IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic OAuth IDP is configured on the organization
|
||||
// Given the user has Generic OAuth IDP added as auth method
|
||||
// User is redirected to the Generic OAuth IDP
|
||||
// User authenticates with the Generic OAuth IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic OAuth IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OAuth is configure on the organization as only authencation method
|
||||
// Given idp Generic OAuth is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OAuth
|
||||
// User authenticates in Generic OAuth with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
101
apps/login/acceptance/tests/idp-generic-oidc.spec.ts
Normal file
101
apps/login/acceptance/tests/idp-generic-oidc.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
// Note, we should use a provider such as Google to test this, where we know OIDC standard is properly implemented
|
||||
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Generic OIDC IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Generic OIDC IDP is configured on the organization
|
||||
// Given the user has Generic OIDC IDP added as auth method
|
||||
// User authenticates with the Generic OIDC IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Generic OIDC IDP is configured on the organization
|
||||
// Given the user has Generic OIDC IDP added as auth method
|
||||
// User is redirected to the Generic OIDC IDP
|
||||
// User authenticates with the Generic OIDC IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Generic OIDC IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Generic OIDC is configure on the organization as only authencation method
|
||||
// Given idp Generic OIDC is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Generic OIDC
|
||||
// User authenticates in Generic OIDC with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
103
apps/login/acceptance/tests/idp-github-enterprise.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-github-enterprise.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitHub Enterprise IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitHub Enterprise IDP is configured on the organization
|
||||
// Given the user has GitHub Enterprise IDP added as auth method
|
||||
// User authenticates with the GitHub Enterprise IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitHub Enterprise IDP is configured on the organization
|
||||
// Given the user has GitHub Enterprise IDP added as auth method
|
||||
// User is redirected to the GitHub Enterprise IDP
|
||||
// User authenticates with the GitHub Enterprise IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with GitHub Enterprise IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub Enterprise is configure on the organization as only authencation method
|
||||
// Given idp GitHub Enterprise is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub Enterprise
|
||||
// User authenticates in GitHub Enterprise with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
103
apps/login/acceptance/tests/idp-github.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-github.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitHub IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitHub IDP is configured on the organization
|
||||
// Given the user has GitHub IDP added as auth method
|
||||
// User authenticates with the GitHub IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitHub IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitHub IDP is configured on the organization
|
||||
// Given the user has GitHub IDP added as auth method
|
||||
// User is redirected to the GitHub IDP
|
||||
// User authenticates with the GitHub IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with GitHub IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp GitHub is configure on the organization as only authencation method
|
||||
// Given idp GitHub is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to GitHub
|
||||
// User authenticates in GitHub with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
103
apps/login/acceptance/tests/idp-gitlab-self-hosted.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-gitlab-self-hosted.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitLab Self-Hosted IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitLab Self-Hosted IDP is configured on the organization
|
||||
// Given the user has GitLab Self-Hosted IDP added as auth method
|
||||
// User authenticates with the GitLab Self-Hosted IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitLab Self-Hosted IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitLab Self-Hosted IDP is configured on the organization
|
||||
// Given the user has GitLab Self-Hosted IDP added as auth method
|
||||
// User is redirected to the GitLab Self-Hosted IDP
|
||||
// User authenticates with the GitLab Self-Hosted IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Gitlab Self-Hosted IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab Self-Hosted is configure on the organization as only authencation method
|
||||
// Given idp Gitlab Self-Hosted is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab Self-Hosted
|
||||
// User authenticates in Gitlab Self-Hosted with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
103
apps/login/acceptance/tests/idp-gitlab.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-gitlab.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with GitLab IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a GitLab IDP is configured on the organization
|
||||
// Given the user has GitLab IDP added as auth method
|
||||
// User authenticates with the GitLab IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with GitLab IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the GitLab IDP is configured on the organization
|
||||
// Given the user has GitLab IDP added as auth method
|
||||
// User is redirected to the GitLab IDP
|
||||
// User authenticates with the GitLab IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Gitlab IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Gitlab is configure on the organization as only authencation method
|
||||
// Given idp Gitlab is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Gitlab
|
||||
// User authenticates in Gitlab with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
99
apps/login/acceptance/tests/idp-google.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-google.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Google IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Google IDP is configured on the organization
|
||||
// Given the user has Google IDP added as auth method
|
||||
// User authenticates with the Google IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Google IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Google IDP is configured on the organization
|
||||
// Given the user has Google IDP added as auth method
|
||||
// User is redirected to the Google IDP
|
||||
// User authenticates with the Google IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Google IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Google is configure on the organization as only authencation method
|
||||
// Given idp Google is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Google
|
||||
// User authenticates in Google with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
99
apps/login/acceptance/tests/idp-ldap.spec.ts
Normal file
99
apps/login/acceptance/tests/idp-ldap.spec.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with LDAP IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a LDAP IDP is configured on the organization
|
||||
// Given the user has LDAP IDP added as auth method
|
||||
// User authenticates with the LDAP IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with LDAP IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the LDAP IDP is configured on the organization
|
||||
// Given the user has LDAP IDP added as auth method
|
||||
// User is redirected to the LDAP IDP
|
||||
// User authenticates with the LDAP IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with LDAP IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp LDAP is configure on the organization as only authencation method
|
||||
// Given idp LDAP is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to LDAP
|
||||
// User authenticates in LDAP with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
102
apps/login/acceptance/tests/idp-microsoft.spec.ts
Normal file
102
apps/login/acceptance/tests/idp-microsoft.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
// Note for all tests, in case Microsoft doesn't deliver all relevant information per default
|
||||
// We should add an action in the needed cases
|
||||
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with Microsoft IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a Microsoft IDP is configured on the organization
|
||||
// Given the user has Microsoft IDP added as auth method
|
||||
// User authenticates with the Microsoft IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the Microsoft IDP is configured on the organization
|
||||
// Given the user has Microsoft IDP added as auth method
|
||||
// User is redirected to the Microsoft IDP
|
||||
// User authenticates with the Microsoft IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with Microsoft IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp Microsoft is configure on the organization as only authencation method
|
||||
// Given idp Microsoft is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to Microsoft
|
||||
// User authenticates in Microsoft with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
103
apps/login/acceptance/tests/idp-saml.spec.ts
Normal file
103
apps/login/acceptance/tests/idp-saml.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with SAML IDP", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given a SAML IDP is configured on the organization
|
||||
// Given the user has SAML IDP added as auth method
|
||||
// User authenticates with the SAML IDP
|
||||
// User is redirected back to login
|
||||
// User is redirected to the app
|
||||
});
|
||||
|
||||
test("login with SAML IDP - error", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the SAML IDP is configured on the organization
|
||||
// Given the user has SAML IDP added as auth method
|
||||
// User is redirected to the SAML IDP
|
||||
// User authenticates with the SAML IDP and gets an error
|
||||
// User is redirected back to login
|
||||
// An error is shown to the user "Something went wrong"
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user existing - auto register", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML
|
||||
// User is redirect to ZITADEL login
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user existing - auto register not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with account creation alloweed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// User will see the registration page with pre filled user information
|
||||
// User fills missing information
|
||||
// User clicks register button
|
||||
// User is created in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user existing - auto register enabled - manual creation disabled, creation not possible", async ({
|
||||
page,
|
||||
}) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with account creation not allowed, and automatic creation enabled
|
||||
// Given no user exists yet
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML
|
||||
// User is redirect to ZITADEL login
|
||||
// Because of missing informaiton on the user auto creation is not possible
|
||||
// Error message is shown, that registration of the user was not possible due to missing information
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user linked - auto link", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com exists
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User is linked with existing user in ZITADEL
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user linked, linking not possible", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with manually account linking not allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User will get an error message that account linking wasn't possible
|
||||
});
|
||||
|
||||
test("login with SAML IDP, no user linked, linking successful", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given idp SAML is configure on the organization as only authencation method
|
||||
// Given idp SAML is configure with manually account linking allowed, and linking set to existing email
|
||||
// Given ZITADEL Action is added to autofill missing user information
|
||||
// Given user with email address user@zitadel.com doesn't exists
|
||||
// User is automatically redirected to SAML
|
||||
// User authenticates in SAML with user@zitadel.com
|
||||
// User is redirect to ZITADEL login
|
||||
// User with email address user@zitadel.com can not be found
|
||||
// User is prompted to link the account manually
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import test from "@playwright/test";
|
||||
|
||||
test("login with mfa setup, mfa setup prompt", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has enabled at least one mfa types
|
||||
// Given the user has a password but no mfa registered
|
||||
// User authenticates with login name and password
|
||||
// User is prompted to setup a mfa, mfa providers are listed, the user can choose the provider
|
||||
});
|
||||
|
||||
test("login with mfa setup, no mfa setup prompt", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has set "multifactor init check time" to 0
|
||||
// Given the organization has enabled mfa types
|
||||
// Given the user has a password but no mfa registered
|
||||
// User authenticates with loginname and password
|
||||
// user is directly loged in and not prompted to setup mfa
|
||||
});
|
||||
|
||||
test("login with mfa setup, force mfa for local authenticated users", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has enabled force mfa for local authentiacted users
|
||||
// Given the organization has enabled all possible mfa types
|
||||
// Given the user has a password but no mfa registered
|
||||
// User authenticates with loginname and password
|
||||
// User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider
|
||||
});
|
||||
|
||||
test("login with mfa setup, force mfa - local user", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has enabled force mfa for local authentiacted users
|
||||
// Given the organization has enabled all possible mfa types
|
||||
// Given the user has a password but no mfa registered
|
||||
// User authenticates with loginname and password
|
||||
// User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider
|
||||
});
|
||||
|
||||
test("login with mfa setup, force mfa - external user", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has enabled force mfa
|
||||
// Given the organization has enabled all possible mfa types
|
||||
// Given the user has an idp but no mfa registered
|
||||
// enter login name
|
||||
// redirect to configured external idp
|
||||
// User is prompted to setup a mfa, all possible mfa providers are listed, the user can choose the provider
|
||||
});
|
||||
|
||||
test("login with mfa setup, force mfa - local user, wrong password", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given the organization has a password lockout policy set to 1 on the max password attempts
|
||||
// Given the user has only a password as auth methos
|
||||
// enter login name
|
||||
// enter wrong password
|
||||
// User will get an error "Wrong password"
|
||||
// enter password
|
||||
// User will get an error "Max password attempts reached - user is locked. Please reach out to your administrator"
|
||||
});
|
||||
41
apps/login/acceptance/tests/login.ts
Normal file
41
apps/login/acceptance/tests/login.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
import { code, otpFromSink } from "./code";
|
||||
import { loginname } from "./loginname";
|
||||
import { password } from "./password";
|
||||
import { totp } from "./zitadel";
|
||||
|
||||
export async function startLogin(page: Page) {
|
||||
await page.goto(`./loginname`);
|
||||
}
|
||||
|
||||
export async function loginWithPassword(page: Page, username: string, pw: string) {
|
||||
await startLogin(page);
|
||||
await loginname(page, username);
|
||||
await password(page, pw);
|
||||
}
|
||||
|
||||
export async function loginWithPasskey(page: Page, authenticatorId: string, username: string) {
|
||||
await startLogin(page);
|
||||
await loginname(page, username);
|
||||
// await passkey(page, authenticatorId);
|
||||
}
|
||||
|
||||
export async function loginScreenExpect(page: Page, fullName: string) {
|
||||
await expect(page).toHaveURL(/.*signedin.*/);
|
||||
await expect(page.getByRole("heading")).toContainText(fullName);
|
||||
}
|
||||
|
||||
export async function loginWithPasswordAndEmailOTP(page: Page, username: string, password: string, email: string) {
|
||||
await loginWithPassword(page, username, password);
|
||||
await otpFromSink(page, email);
|
||||
}
|
||||
|
||||
export async function loginWithPasswordAndPhoneOTP(page: Page, username: string, password: string, phone: string) {
|
||||
await loginWithPassword(page, username, password);
|
||||
await otpFromSink(page, phone);
|
||||
}
|
||||
|
||||
export async function loginWithPasswordAndTOTP(page: Page, username: string, password: string, secret: string) {
|
||||
await loginWithPassword(page, username, password);
|
||||
await code(page, totp(secret));
|
||||
}
|
||||
12
apps/login/acceptance/tests/loginname-screen.ts
Normal file
12
apps/login/acceptance/tests/loginname-screen.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
|
||||
const usernameTextInput = "username-text-input";
|
||||
|
||||
export async function loginnameScreen(page: Page, username: string) {
|
||||
await page.getByTestId(usernameTextInput).pressSequentially(username);
|
||||
}
|
||||
|
||||
export async function loginnameScreenExpect(page: Page, username: string) {
|
||||
await expect(page.getByTestId(usernameTextInput)).toHaveValue(username);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("User not found in the system");
|
||||
}
|
||||
7
apps/login/acceptance/tests/loginname.ts
Normal file
7
apps/login/acceptance/tests/loginname.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { loginnameScreen } from "./loginname-screen";
|
||||
|
||||
export async function loginname(page: Page, username: string) {
|
||||
await loginnameScreen(page, username);
|
||||
await page.getByTestId("submit-button").click();
|
||||
}
|
||||
109
apps/login/acceptance/tests/passkey.ts
Normal file
109
apps/login/acceptance/tests/passkey.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
import { CDPSession } from "playwright-core";
|
||||
|
||||
interface session {
|
||||
client: CDPSession;
|
||||
authenticatorId: string;
|
||||
}
|
||||
|
||||
async function client(page: Page): Promise<session> {
|
||||
const cdpSession = await page.context().newCDPSession(page);
|
||||
await cdpSession.send("WebAuthn.enable", { enableUI: false });
|
||||
const result = await cdpSession.send("WebAuthn.addVirtualAuthenticator", {
|
||||
options: {
|
||||
protocol: "ctap2",
|
||||
transport: "internal",
|
||||
hasResidentKey: true,
|
||||
hasUserVerification: true,
|
||||
isUserVerified: true,
|
||||
automaticPresenceSimulation: true,
|
||||
},
|
||||
});
|
||||
return { client: cdpSession, authenticatorId: result.authenticatorId };
|
||||
}
|
||||
|
||||
export async function passkeyRegister(page: Page): Promise<string> {
|
||||
const session = await client(page);
|
||||
|
||||
await passkeyNotExisting(session.client, session.authenticatorId);
|
||||
await simulateSuccessfulPasskeyRegister(session.client, session.authenticatorId, () =>
|
||||
page.getByTestId("submit-button").click(),
|
||||
);
|
||||
await passkeyRegistered(session.client, session.authenticatorId);
|
||||
|
||||
return session.authenticatorId;
|
||||
}
|
||||
|
||||
export async function passkey(page: Page, authenticatorId: string) {
|
||||
const cdpSession = await page.context().newCDPSession(page);
|
||||
await cdpSession.send("WebAuthn.enable", { enableUI: false });
|
||||
|
||||
const signCount = await passkeyExisting(cdpSession, authenticatorId);
|
||||
|
||||
await simulateSuccessfulPasskeyInput(cdpSession, authenticatorId, () => page.getByTestId("submit-button").click());
|
||||
|
||||
await passkeyUsed(cdpSession, authenticatorId, signCount);
|
||||
}
|
||||
|
||||
async function passkeyNotExisting(client: CDPSession, authenticatorId: string) {
|
||||
const result = await client.send("WebAuthn.getCredentials", { authenticatorId });
|
||||
expect(result.credentials).toHaveLength(0);
|
||||
}
|
||||
|
||||
async function passkeyRegistered(client: CDPSession, authenticatorId: string) {
|
||||
const result = await client.send("WebAuthn.getCredentials", { authenticatorId });
|
||||
expect(result.credentials).toHaveLength(1);
|
||||
await passkeyUsed(client, authenticatorId, 0);
|
||||
}
|
||||
|
||||
async function passkeyExisting(client: CDPSession, authenticatorId: string): Promise<number> {
|
||||
const result = await client.send("WebAuthn.getCredentials", { authenticatorId });
|
||||
expect(result.credentials).toHaveLength(1);
|
||||
return result.credentials[0].signCount;
|
||||
}
|
||||
|
||||
async function passkeyUsed(client: CDPSession, authenticatorId: string, signCount: number) {
|
||||
const result = await client.send("WebAuthn.getCredentials", { authenticatorId });
|
||||
expect(result.credentials).toHaveLength(1);
|
||||
expect(result.credentials[0].signCount).toBeGreaterThan(signCount);
|
||||
}
|
||||
|
||||
async function simulateSuccessfulPasskeyRegister(
|
||||
client: CDPSession,
|
||||
authenticatorId: string,
|
||||
operationTrigger: () => Promise<void>,
|
||||
) {
|
||||
// initialize event listeners to wait for a successful passkey input event
|
||||
const operationCompleted = new Promise<void>((resolve) => {
|
||||
client.on("WebAuthn.credentialAdded", () => {
|
||||
console.log("Credential Added!");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// perform a user action that triggers passkey prompt
|
||||
await operationTrigger();
|
||||
|
||||
// wait to receive the event that the passkey was successfully registered or verified
|
||||
await operationCompleted;
|
||||
}
|
||||
|
||||
async function simulateSuccessfulPasskeyInput(
|
||||
client: CDPSession,
|
||||
authenticatorId: string,
|
||||
operationTrigger: () => Promise<void>,
|
||||
) {
|
||||
// initialize event listeners to wait for a successful passkey input event
|
||||
const operationCompleted = new Promise<void>((resolve) => {
|
||||
client.on("WebAuthn.credentialAsserted", () => {
|
||||
console.log("Credential Asserted!");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// perform a user action that triggers passkey prompt
|
||||
await operationTrigger();
|
||||
|
||||
// wait to receive the event that the passkey was successfully registered or verified
|
||||
await operationCompleted;
|
||||
}
|
||||
98
apps/login/acceptance/tests/password-screen.ts
Normal file
98
apps/login/acceptance/tests/password-screen.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { expect, Page } from "@playwright/test";
|
||||
import { getCodeFromSink } from "./sink";
|
||||
|
||||
const codeField = "code-text-input";
|
||||
const passwordField = "password-text-input";
|
||||
const passwordChangeField = "password-change-text-input";
|
||||
const passwordChangeConfirmField = "password-change-confirm-text-input";
|
||||
const passwordSetField = "password-set-text-input";
|
||||
const passwordSetConfirmField = "password-set-confirm-text-input";
|
||||
const lengthCheck = "length-check";
|
||||
const symbolCheck = "symbol-check";
|
||||
const numberCheck = "number-check";
|
||||
const uppercaseCheck = "uppercase-check";
|
||||
const lowercaseCheck = "lowercase-check";
|
||||
const equalCheck = "equal-check";
|
||||
|
||||
const matchText = "Matches";
|
||||
const noMatchText = "Doesn't match";
|
||||
|
||||
export async function changePasswordScreen(page: Page, password1: string, password2: string) {
|
||||
await page.getByTestId(passwordChangeField).pressSequentially(password1);
|
||||
await page.getByTestId(passwordChangeConfirmField).pressSequentially(password2);
|
||||
}
|
||||
|
||||
export async function passwordScreen(page: Page, password: string) {
|
||||
await page.getByTestId(passwordField).pressSequentially(password);
|
||||
}
|
||||
|
||||
export async function passwordScreenExpect(page: Page, password: string) {
|
||||
await expect(page.getByTestId(passwordField)).toHaveValue(password);
|
||||
await expect(page.getByTestId("error").locator("div")).toContainText("Failed to authenticate.");
|
||||
}
|
||||
|
||||
export async function changePasswordScreenExpect(
|
||||
page: Page,
|
||||
password1: string,
|
||||
password2: string,
|
||||
length: boolean,
|
||||
symbol: boolean,
|
||||
number: boolean,
|
||||
uppercase: boolean,
|
||||
lowercase: boolean,
|
||||
equals: boolean,
|
||||
) {
|
||||
await expect(page.getByTestId(passwordChangeField)).toHaveValue(password1);
|
||||
await expect(page.getByTestId(passwordChangeConfirmField)).toHaveValue(password2);
|
||||
|
||||
await checkComplexity(page, length, symbol, number, uppercase, lowercase, equals);
|
||||
}
|
||||
|
||||
async function checkComplexity(
|
||||
page: Page,
|
||||
length: boolean,
|
||||
symbol: boolean,
|
||||
number: boolean,
|
||||
uppercase: boolean,
|
||||
lowercase: boolean,
|
||||
equals: boolean,
|
||||
) {
|
||||
await checkContent(page, lengthCheck, length);
|
||||
await checkContent(page, symbolCheck, symbol);
|
||||
await checkContent(page, numberCheck, number);
|
||||
await checkContent(page, uppercaseCheck, uppercase);
|
||||
await checkContent(page, lowercaseCheck, lowercase);
|
||||
await checkContent(page, equalCheck, equals);
|
||||
}
|
||||
|
||||
async function checkContent(page: Page, testid: string, match: boolean) {
|
||||
if (match) {
|
||||
await expect(page.getByTestId(testid)).toContainText(matchText);
|
||||
} else {
|
||||
await expect(page.getByTestId(testid)).toContainText(noMatchText);
|
||||
}
|
||||
}
|
||||
|
||||
export async function resetPasswordScreen(page: Page, username: string, password1: string, password2: string) {
|
||||
const c = await getCodeFromSink(username);
|
||||
await page.getByTestId(codeField).pressSequentially(c);
|
||||
await page.getByTestId(passwordSetField).pressSequentially(password1);
|
||||
await page.getByTestId(passwordSetConfirmField).pressSequentially(password2);
|
||||
}
|
||||
|
||||
export async function resetPasswordScreenExpect(
|
||||
page: Page,
|
||||
password1: string,
|
||||
password2: string,
|
||||
length: boolean,
|
||||
symbol: boolean,
|
||||
number: boolean,
|
||||
uppercase: boolean,
|
||||
lowercase: boolean,
|
||||
equals: boolean,
|
||||
) {
|
||||
await expect(page.getByTestId(passwordSetField)).toHaveValue(password1);
|
||||
await expect(page.getByTestId(passwordSetConfirmField)).toHaveValue(password2);
|
||||
|
||||
await checkComplexity(page, length, symbol, number, uppercase, lowercase, equals);
|
||||
}
|
||||
29
apps/login/acceptance/tests/password.ts
Normal file
29
apps/login/acceptance/tests/password.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { changePasswordScreen, passwordScreen, resetPasswordScreen } from "./password-screen";
|
||||
|
||||
const passwordSubmitButton = "submit-button";
|
||||
const passwordResetButton = "reset-button";
|
||||
|
||||
export async function startChangePassword(page: Page, loginname: string) {
|
||||
await page.goto("./password/change?" + new URLSearchParams({ loginName: loginname }));
|
||||
}
|
||||
|
||||
export async function changePassword(page: Page, password: string) {
|
||||
await changePasswordScreen(page, password, password);
|
||||
await page.getByTestId(passwordSubmitButton).click();
|
||||
}
|
||||
|
||||
export async function password(page: Page, password: string) {
|
||||
await passwordScreen(page, password);
|
||||
await page.getByTestId(passwordSubmitButton).click();
|
||||
}
|
||||
|
||||
export async function startResetPassword(page: Page) {
|
||||
await page.getByTestId(passwordResetButton).click();
|
||||
}
|
||||
|
||||
export async function resetPassword(page: Page, username: string, password: string) {
|
||||
await startResetPassword(page);
|
||||
await resetPasswordScreen(page, username, password, password);
|
||||
await page.getByTestId(passwordSubmitButton).click();
|
||||
}
|
||||
27
apps/login/acceptance/tests/register-screen.ts
Normal file
27
apps/login/acceptance/tests/register-screen.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
const passwordField = "password-text-input";
|
||||
const passwordConfirmField = "password-confirm-text-input";
|
||||
|
||||
export async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) {
|
||||
await registerUserScreen(page, firstname, lastname, email);
|
||||
await page.getByTestId("password-radio").click();
|
||||
}
|
||||
|
||||
export async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) {
|
||||
await registerUserScreen(page, firstname, lastname, email);
|
||||
await page.getByTestId("passkey-radio").click();
|
||||
}
|
||||
|
||||
export async function registerPasswordScreen(page: Page, password1: string, password2: string) {
|
||||
await page.getByTestId(passwordField).pressSequentially(password1);
|
||||
await page.getByTestId(passwordConfirmField).pressSequentially(password2);
|
||||
}
|
||||
|
||||
export async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) {
|
||||
await page.getByTestId("firstname-text-input").pressSequentially(firstname);
|
||||
await page.getByTestId("lastname-text-input").pressSequentially(lastname);
|
||||
await page.getByTestId("email-text-input").pressSequentially(email);
|
||||
await page.getByTestId("privacy-policy-checkbox").check();
|
||||
await page.getByTestId("tos-checkbox").check();
|
||||
}
|
||||
183
apps/login/acceptance/tests/register.spec.ts
Normal file
183
apps/login/acceptance/tests/register.spec.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect } from "./login";
|
||||
import { registerWithPasskey, registerWithPassword } from "./register";
|
||||
import { removeUserByUsername } from "./zitadel";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
test("register with password", async ({ page }) => {
|
||||
const username = faker.internet.email();
|
||||
const password = "Password1!";
|
||||
const firstname = faker.person.firstName();
|
||||
const lastname = faker.person.lastName();
|
||||
|
||||
await registerWithPassword(page, firstname, lastname, username, password, password);
|
||||
await loginScreenExpect(page, firstname + " " + lastname);
|
||||
|
||||
// wait for projection of user
|
||||
await page.waitForTimeout(10000);
|
||||
await removeUserByUsername(username);
|
||||
});
|
||||
|
||||
test("register with passkey", async ({ page }) => {
|
||||
const username = faker.internet.email();
|
||||
const firstname = faker.person.firstName();
|
||||
const lastname = faker.person.lastName();
|
||||
|
||||
await registerWithPasskey(page, firstname, lastname, username);
|
||||
await loginScreenExpect(page, firstname + " " + lastname);
|
||||
|
||||
// wait for projection of user
|
||||
await page.waitForTimeout(10000);
|
||||
await removeUserByUsername(username);
|
||||
});
|
||||
|
||||
test("register with username and password - only password enabled", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and "password"
|
||||
// User is redirected to app (default redirect url)
|
||||
});
|
||||
|
||||
test("register with username and password - wrong password not enough characters", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given password policy is set to 8 characters and must include number, symbol, lower and upper letter
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and a password thats to short
|
||||
// Error is shown "Password doesn't match the policy - it must have at least 8 characters"
|
||||
});
|
||||
|
||||
test("register with username and password - wrong password number missing", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given password policy is set to 8 characters and must include number, symbol, lower and upper letter
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and a password without a number
|
||||
// Error is shown "Password doesn't match the policy - number missing"
|
||||
});
|
||||
|
||||
test("register with username and password - wrong password upper case missing", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given password policy is set to 8 characters and must include number, symbol, lower and upper letter
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and a password without an upper case
|
||||
// Error is shown "Password doesn't match the policy - uppercase letter missing"
|
||||
});
|
||||
|
||||
test("register with username and password - wrong password lower case missing", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given password policy is set to 8 characters and must include number, symbol, lower and upper letter
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and a password without an lower case
|
||||
// Error is shown "Password doesn't match the policy - lowercase letter missing"
|
||||
});
|
||||
|
||||
test("register with username and password - wrong password symboo missing", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is not enabled
|
||||
// Given password policy is set to 8 characters and must include number, symbol, lower and upper letter
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// Only password is shown as an option - no passkey
|
||||
// User enters "firstname", "lastname", "username" and a password without an symbol
|
||||
// Error is shown "Password doesn't match the policy - symbol missing"
|
||||
});
|
||||
|
||||
test("register with username and password - password and passkey enabled", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is enabled
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// User enters "firstname", "lastname", "username"
|
||||
// Password and passkey are shown as authentication option
|
||||
// User clicks password
|
||||
// User enters password
|
||||
// User is redirected to app (default redirect url)
|
||||
});
|
||||
|
||||
test("register with username and passkey - password and passkey enabled", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given on the default organization passkey is enabled
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration page
|
||||
// User enters "firstname", "lastname", "username"
|
||||
// Password and passkey are shown as authentication option
|
||||
// User clicks passkey
|
||||
// Passkey is opened automatically
|
||||
// User verifies passkey
|
||||
// User is redirected to app (default redirect url)
|
||||
});
|
||||
|
||||
test("register with username and password - registration disabled", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization no idp is configured and enabled
|
||||
// Given user doesn't exist
|
||||
// Button "register new user" is not available
|
||||
});
|
||||
|
||||
test("register with username and password - multiple registration options", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given on the default organization "username and password is allowed" is enabled
|
||||
// Given on the default organization "username registeration allowed" is enabled
|
||||
// Given on the default organization one idp is configured and enabled
|
||||
// Given user doesn't exist
|
||||
// Click on button "register new user"
|
||||
// User is redirected to registration options
|
||||
// Local User and idp button are shown
|
||||
// User clicks idp button
|
||||
// User enters "firstname", "lastname", "username" and "password"
|
||||
// User clicks next
|
||||
// User is redirected to app (default redirect url)
|
||||
});
|
||||
39
apps/login/acceptance/tests/register.ts
Normal file
39
apps/login/acceptance/tests/register.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { emailVerify } from "./email-verify";
|
||||
import { passkeyRegister } from "./passkey";
|
||||
import { registerPasswordScreen, registerUserScreenPasskey, registerUserScreenPassword } from "./register-screen";
|
||||
import { getCodeFromSink } from "./sink";
|
||||
|
||||
export async function registerWithPassword(
|
||||
page: Page,
|
||||
firstname: string,
|
||||
lastname: string,
|
||||
email: string,
|
||||
password1: string,
|
||||
password2: string,
|
||||
) {
|
||||
await page.goto("./register");
|
||||
await registerUserScreenPassword(page, firstname, lastname, email);
|
||||
await page.getByTestId("submit-button").click();
|
||||
await registerPasswordScreen(page, password1, password2);
|
||||
await page.getByTestId("submit-button").click();
|
||||
await verifyEmail(page, email);
|
||||
}
|
||||
|
||||
export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string): Promise<string> {
|
||||
await page.goto("./register");
|
||||
await registerUserScreenPasskey(page, firstname, lastname, email);
|
||||
await page.getByTestId("submit-button").click();
|
||||
|
||||
// wait for projection of user
|
||||
await page.waitForTimeout(10000);
|
||||
const authId = await passkeyRegister(page);
|
||||
|
||||
await verifyEmail(page, email);
|
||||
return authId;
|
||||
}
|
||||
|
||||
async function verifyEmail(page: Page, email: string) {
|
||||
const c = await getCodeFromSink(email);
|
||||
await emailVerify(page, c);
|
||||
}
|
||||
5
apps/login/acceptance/tests/select-account.ts
Normal file
5
apps/login/acceptance/tests/select-account.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Page } from "@playwright/test";
|
||||
|
||||
export async function selectNewAccount(page: Page) {
|
||||
await page.getByRole("link", { name: "Add another account" }).click();
|
||||
}
|
||||
43
apps/login/acceptance/tests/sink.ts
Normal file
43
apps/login/acceptance/tests/sink.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Gaxios, GaxiosResponse } from "gaxios";
|
||||
|
||||
const awaitNotification = new Gaxios({
|
||||
url: process.env.SINK_NOTIFICATION_URL,
|
||||
method: "POST",
|
||||
retryConfig: {
|
||||
httpMethodsToRetry: ["POST"],
|
||||
statusCodesToRetry: [[404, 404]],
|
||||
retry: Number.MAX_SAFE_INTEGER, // totalTimeout limits the number of retries
|
||||
totalTimeout: 10000, // 10 seconds
|
||||
onRetryAttempt: (error) => {
|
||||
console.warn(`Retrying request to sink notification service: ${error.message}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export async function getOtpFromSink(recipient: string): Promise<any> {
|
||||
return awaitNotification.request({ data: { recipient } }).then((response) => {
|
||||
expectSuccess(response);
|
||||
const otp = response?.data?.args?.otp;
|
||||
if (!otp) {
|
||||
throw new Error(`Response does not contain an otp property: ${JSON.stringify(response.data, null, 2)}`);
|
||||
}
|
||||
return otp;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getCodeFromSink(recipient: string): Promise<any> {
|
||||
return awaitNotification.request({ data: { recipient } }).then((response) => {
|
||||
expectSuccess(response);
|
||||
const code = response?.data?.args?.code;
|
||||
if (!code) {
|
||||
throw new Error(`Response does not contain a code property: ${JSON.stringify(response.data, null, 2)}`);
|
||||
}
|
||||
return code;
|
||||
});
|
||||
}
|
||||
|
||||
function expectSuccess(response: GaxiosResponse): void {
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`Expected HTTP status 200, but got: ${response.status} - ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
177
apps/login/acceptance/tests/user.ts
Normal file
177
apps/login/acceptance/tests/user.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { Page } from "@playwright/test";
|
||||
import { registerWithPasskey } from "./register";
|
||||
import { activateOTP, addTOTP, addUser, eventualNewUser, getUserByUsername, removeUser } from "./zitadel";
|
||||
|
||||
export interface userProps {
|
||||
email: string;
|
||||
isEmailVerified?: boolean;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
organization: string;
|
||||
password: string;
|
||||
passwordChangeRequired?: boolean;
|
||||
phone: string;
|
||||
isPhoneVerified?: boolean;
|
||||
}
|
||||
|
||||
class User {
|
||||
private readonly props: userProps;
|
||||
private user: string;
|
||||
|
||||
constructor(userProps: userProps) {
|
||||
this.props = userProps;
|
||||
}
|
||||
|
||||
async ensure(page: Page) {
|
||||
const response = await addUser(this.props);
|
||||
|
||||
this.setUserId(response.userId);
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
await removeUser(this.getUserId());
|
||||
}
|
||||
|
||||
public setUserId(userId: string) {
|
||||
this.user = userId;
|
||||
}
|
||||
|
||||
public getUserId() {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
public getUsername() {
|
||||
return this.props.email;
|
||||
}
|
||||
|
||||
public getPassword() {
|
||||
return this.props.password;
|
||||
}
|
||||
|
||||
public getFirstname() {
|
||||
return this.props.firstName;
|
||||
}
|
||||
|
||||
public getLastname() {
|
||||
return this.props.lastName;
|
||||
}
|
||||
|
||||
public getPhone() {
|
||||
return this.props.phone;
|
||||
}
|
||||
|
||||
public getFullName() {
|
||||
return `${this.props.firstName} ${this.props.lastName}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PasswordUser extends User {
|
||||
async ensure(page: Page) {
|
||||
await super.ensure(page);
|
||||
await eventualNewUser(this.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
export enum OtpType {
|
||||
sms = "sms",
|
||||
email = "email",
|
||||
}
|
||||
|
||||
export interface otpUserProps {
|
||||
email: string;
|
||||
isEmailVerified?: boolean;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
organization: string;
|
||||
password: string;
|
||||
passwordChangeRequired?: boolean;
|
||||
phone: string;
|
||||
isPhoneVerified?: boolean;
|
||||
type: OtpType;
|
||||
}
|
||||
|
||||
export class PasswordUserWithOTP extends User {
|
||||
private type: OtpType;
|
||||
|
||||
constructor(props: otpUserProps) {
|
||||
super({
|
||||
email: props.email,
|
||||
firstName: props.firstName,
|
||||
lastName: props.lastName,
|
||||
organization: props.organization,
|
||||
password: props.password,
|
||||
phone: props.phone,
|
||||
isEmailVerified: props.isEmailVerified,
|
||||
isPhoneVerified: props.isPhoneVerified,
|
||||
passwordChangeRequired: props.passwordChangeRequired,
|
||||
});
|
||||
this.type = props.type;
|
||||
}
|
||||
|
||||
async ensure(page: Page) {
|
||||
await super.ensure(page);
|
||||
await activateOTP(this.getUserId(), this.type);
|
||||
await eventualNewUser(this.getUserId());
|
||||
}
|
||||
}
|
||||
|
||||
export class PasswordUserWithTOTP extends User {
|
||||
private secret: string;
|
||||
|
||||
async ensure(page: Page) {
|
||||
await super.ensure(page);
|
||||
this.secret = await addTOTP(this.getUserId());
|
||||
await eventualNewUser(this.getUserId());
|
||||
}
|
||||
|
||||
public getSecret(): string {
|
||||
return this.secret;
|
||||
}
|
||||
}
|
||||
|
||||
export interface passkeyUserProps {
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
organization: string;
|
||||
phone: string;
|
||||
isEmailVerified?: boolean;
|
||||
isPhoneVerified?: boolean;
|
||||
}
|
||||
|
||||
export class PasskeyUser extends User {
|
||||
private authenticatorId: string;
|
||||
|
||||
constructor(props: passkeyUserProps) {
|
||||
super({
|
||||
email: props.email,
|
||||
firstName: props.firstName,
|
||||
lastName: props.lastName,
|
||||
organization: props.organization,
|
||||
password: "",
|
||||
phone: props.phone,
|
||||
isEmailVerified: props.isEmailVerified,
|
||||
isPhoneVerified: props.isPhoneVerified,
|
||||
});
|
||||
}
|
||||
|
||||
public async ensure(page: Page) {
|
||||
const authId = await registerWithPasskey(page, this.getFirstname(), this.getLastname(), this.getUsername());
|
||||
this.authenticatorId = authId;
|
||||
|
||||
// wait for projection of user
|
||||
await page.waitForTimeout(10000);
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
const resp: any = await getUserByUsername(this.getUsername());
|
||||
if (!resp || !resp.result || !resp.result[0]) {
|
||||
return;
|
||||
}
|
||||
await removeUser(resp.result[0].userId);
|
||||
}
|
||||
|
||||
public getAuthenticatorId(): string {
|
||||
return this.authenticatorId;
|
||||
}
|
||||
}
|
||||
43
apps/login/acceptance/tests/username-passkey.spec.ts
Normal file
43
apps/login/acceptance/tests/username-passkey.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPasskey } from "./login";
|
||||
import { PasskeyUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasskeyUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasskeyUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username and passkey login", async ({ user, page }) => {
|
||||
await loginWithPasskey(page, user.getAuthenticatorId(), user.getUsername());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username and passkey login, multiple auth methods", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given passkey and password is enabled on the organization of the user
|
||||
// Given the user has password and passkey registered
|
||||
// enter username
|
||||
// passkey popup is directly shown
|
||||
// user aborts passkey authentication
|
||||
// user switches to password authentication
|
||||
// user enters password
|
||||
// user is redirected to app
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
import { changePassword } from "./password";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: true,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username and password login, change required", async ({ user, page }) => {
|
||||
const changedPw = "ChangedPw1!";
|
||||
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await page.waitForTimeout(10000);
|
||||
await changePassword(page, changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
|
||||
await loginWithPassword(page, user.getUsername(), changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPassword } from "./login";
|
||||
import { changePassword, startChangePassword } from "./password";
|
||||
import { changePasswordScreen, changePasswordScreenExpect } from "./password-screen";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username and password changed login", async ({ user, page }) => {
|
||||
const changedPw = "ChangedPw1!";
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
|
||||
// wait for projection of token
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
await startChangePassword(page, user.getUsername());
|
||||
await changePassword(page, changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
|
||||
await loginWithPassword(page, user.getUsername(), changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("password change not with desired complexity", async ({ user, page }) => {
|
||||
const changedPw1 = "change";
|
||||
const changedPw2 = "chang";
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await startChangePassword(page, user.getUsername());
|
||||
await changePasswordScreen(page, changedPw1, changedPw2);
|
||||
await changePasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false);
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { code, codeResend, otpFromSink } from "./code";
|
||||
import { codeScreenExpect } from "./code-screen";
|
||||
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndEmailOTP } from "./login";
|
||||
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUserWithOTP({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
type: OtpType.email,
|
||||
});
|
||||
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test.skip("DOESN'T WORK: username, password and email otp login, enter code manually", async ({ user, page }) => {
|
||||
// Given email otp is enabled on the organization of the user
|
||||
// Given the user has only email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives an email with a verification code
|
||||
// User enters the code into the ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
await loginWithPasswordAndEmailOTP(page, user.getUsername(), user.getPassword(), user.getUsername());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username, password and email otp login, click link in email", async ({ page }) => {
|
||||
base.skip();
|
||||
// Given email otp is enabled on the organization of the user
|
||||
// Given the user has only email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives an email with a verification code
|
||||
// User clicks link in the email
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test.skip("DOESN'T WORK: username, password and email otp login, resend code", async ({ user, page }) => {
|
||||
// Given email otp is enabled on the organization of the user
|
||||
// Given the user has only email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives an email with a verification code
|
||||
// User clicks resend code
|
||||
// User receives a new email with a verification code
|
||||
// User enters the new code in the ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await codeResend(page);
|
||||
await otpFromSink(page, user.getUsername());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username, password and email otp login, wrong code", async ({ user, page }) => {
|
||||
// Given email otp is enabled on the organization of the user
|
||||
// Given the user has only email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives an email with a verification code
|
||||
// User enters a wrong code
|
||||
// Error message - "Invalid code" is shown
|
||||
const c = "wrongcode";
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await code(page, c);
|
||||
await codeScreenExpect(page, c);
|
||||
});
|
||||
|
||||
test("username, password and email otp login, multiple mfa options", async ({ page }) => {
|
||||
base.skip();
|
||||
// Given email otp and sms otp is enabled on the organization of the user
|
||||
// Given the user has email and sms otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives an email with a verification code
|
||||
// User clicks button to use sms otp as second factor
|
||||
// User receives a sms with a verification code
|
||||
// User enters code in ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
@@ -0,0 +1,71 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { code } from "./code";
|
||||
import { codeScreenExpect } from "./code-screen";
|
||||
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndPhoneOTP } from "./login";
|
||||
import { OtpType, PasswordUserWithOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUserWithOTP({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number({ style: "international" }),
|
||||
isPhoneVerified: true,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
type: OtpType.sms,
|
||||
});
|
||||
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test.skip("DOESN'T WORK: username, password and sms otp login, enter code manually", async ({ user, page }) => {
|
||||
// Given sms otp is enabled on the organization of the user
|
||||
// Given the user has only sms otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives a sms with a verification code
|
||||
// User enters the code into the ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test.skip("DOESN'T WORK: username, password and sms otp login, resend code", async ({ user, page }) => {
|
||||
// Given sms otp is enabled on the organization of the user
|
||||
// Given the user has only sms otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives a sms with a verification code
|
||||
// User clicks resend code
|
||||
// User receives a new sms with a verification code
|
||||
// User is redirected to the app (default redirect url)
|
||||
await loginWithPasswordAndPhoneOTP(page, user.getUsername(), user.getPassword(), user.getPhone());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username, password and sms otp login, wrong code", async ({ user, page }) => {
|
||||
// Given sms otp is enabled on the organization of the user
|
||||
// Given the user has only sms otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// User receives a sms with a verification code
|
||||
// User enters a wrong code
|
||||
// Error message - "Invalid code" is shown
|
||||
const c = "wrongcode";
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await code(page, c);
|
||||
await codeScreenExpect(page, c);
|
||||
});
|
||||
52
apps/login/acceptance/tests/username-password-set.spec.ts
Normal file
52
apps/login/acceptance/tests/username-password-set.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPassword, startLogin } from "./login";
|
||||
import { loginname } from "./loginname";
|
||||
import { resetPassword, startResetPassword } from "./password";
|
||||
import { resetPasswordScreen, resetPasswordScreenExpect } from "./password-screen";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username and password set login", async ({ user, page }) => {
|
||||
const changedPw = "ChangedPw1!";
|
||||
await startLogin(page);
|
||||
await loginname(page, user.getUsername());
|
||||
await resetPassword(page, user.getUsername(), changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
|
||||
await loginWithPassword(page, user.getUsername(), changedPw);
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("password set not with desired complexity", async ({ user, page }) => {
|
||||
const changedPw1 = "change";
|
||||
const changedPw2 = "chang";
|
||||
await startLogin(page);
|
||||
await loginname(page, user.getUsername());
|
||||
await startResetPassword(page);
|
||||
await resetPasswordScreen(page, user.getUsername(), changedPw1, changedPw2);
|
||||
await resetPasswordScreenExpect(page, changedPw1, changedPw2, false, false, false, false, true, false);
|
||||
});
|
||||
71
apps/login/acceptance/tests/username-password-totp.spec.ts
Normal file
71
apps/login/acceptance/tests/username-password-totp.spec.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { code } from "./code";
|
||||
import { codeScreenExpect } from "./code-screen";
|
||||
import { loginScreenExpect, loginWithPassword, loginWithPasswordAndTOTP } from "./login";
|
||||
import { PasswordUserWithTOTP } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUserWithTOTP; sink: any }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUserWithTOTP({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number({ style: "international" }),
|
||||
isPhoneVerified: true,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username, password and totp login", async ({ user, page }) => {
|
||||
// Given totp is enabled on the organization of the user
|
||||
// Given the user has only totp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// Screen for entering the code is shown directly
|
||||
// User enters the code into the ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
await loginWithPasswordAndTOTP(page, user.getUsername(), user.getPassword(), user.getSecret());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username, password and totp otp login, wrong code", async ({ user, page }) => {
|
||||
// Given totp is enabled on the organization of the user
|
||||
// Given the user has only totp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// Screen for entering the code is shown directly
|
||||
// User enters a wrond code
|
||||
// Error message - "Invalid code" is shown
|
||||
const c = "wrongcode";
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await code(page, c);
|
||||
await codeScreenExpect(page, c);
|
||||
});
|
||||
|
||||
test("username, password and totp login, multiple mfa options", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given totp and email otp is enabled on the organization of the user
|
||||
// Given the user has totp and email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// Screen for entering the code is shown directly
|
||||
// Button to switch to email otp is shown
|
||||
// User clicks button to use email otp instead
|
||||
// User receives an email with a verification code
|
||||
// User enters code in ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
26
apps/login/acceptance/tests/username-password-u2f.spec.ts
Normal file
26
apps/login/acceptance/tests/username-password-u2f.spec.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { test } from "@playwright/test";
|
||||
|
||||
test("username, password and u2f login", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given u2f is enabled on the organization of the user
|
||||
// Given the user has only u2f configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// Popup for u2f is directly opened
|
||||
// User verifies u2f
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
|
||||
test("username, password and u2f login, multiple mfa options", async ({ page }) => {
|
||||
test.skip();
|
||||
// Given u2f and semailms otp is enabled on the organization of the user
|
||||
// Given the user has u2f and email otp configured as second factor
|
||||
// User enters username
|
||||
// User enters password
|
||||
// Popup for u2f is directly opened
|
||||
// User aborts u2f verification
|
||||
// User clicks button to use email otp as second factor
|
||||
// User receives an email with a verification code
|
||||
// User enters code in ui
|
||||
// User is redirected to the app (default redirect url)
|
||||
});
|
||||
157
apps/login/acceptance/tests/username-password.spec.ts
Normal file
157
apps/login/acceptance/tests/username-password.spec.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { test as base } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
import path from "path";
|
||||
import { loginScreenExpect, loginWithPassword, startLogin } from "./login";
|
||||
import { loginname } from "./loginname";
|
||||
import { loginnameScreenExpect } from "./loginname-screen";
|
||||
import { password } from "./password";
|
||||
import { passwordScreenExpect } from "./password-screen";
|
||||
import { PasswordUser } from "./user";
|
||||
|
||||
// Read from ".env" file.
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
const test = base.extend<{ user: PasswordUser }>({
|
||||
user: async ({ page }, use) => {
|
||||
const user = new PasswordUser({
|
||||
email: faker.internet.email(),
|
||||
isEmailVerified: true,
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
organization: "",
|
||||
phone: faker.phone.number(),
|
||||
isPhoneVerified: false,
|
||||
password: "Password1!",
|
||||
passwordChangeRequired: false,
|
||||
});
|
||||
await user.ensure(page);
|
||||
await use(user);
|
||||
await user.cleanup();
|
||||
},
|
||||
});
|
||||
|
||||
test("username and password login", async ({ user, page }) => {
|
||||
await loginWithPassword(page, user.getUsername(), user.getPassword());
|
||||
await loginScreenExpect(page, user.getFullName());
|
||||
});
|
||||
|
||||
test("username and password login, unknown username", async ({ page }) => {
|
||||
const username = "unknown";
|
||||
await startLogin(page);
|
||||
await loginname(page, username);
|
||||
await loginnameScreenExpect(page, username);
|
||||
});
|
||||
|
||||
test("username and password login, wrong password", async ({ user, page }) => {
|
||||
await startLogin(page);
|
||||
await loginname(page, user.getUsername());
|
||||
await password(page, "wrong");
|
||||
await passwordScreenExpect(page, "wrong");
|
||||
});
|
||||
|
||||
test("username and password login, wrong username, ignore unknown usernames", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user doesn't exist but ignore unknown usernames setting is set to true
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// enter password
|
||||
// redirect to loginname page --> error message username or password wrong
|
||||
});
|
||||
|
||||
test("username and password login, initial password change", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user is created and has changePassword set to true
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// enter password
|
||||
// create new password
|
||||
});
|
||||
|
||||
test("username and password login, reset password hidden", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given the organization has enabled "Password reset hidden" in the login policy
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// password reset link should not be shown on password screen
|
||||
});
|
||||
|
||||
test("username and password login, reset password - enter code manually", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user has forgotten password and clicks the forgot password button
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// click password forgotten
|
||||
// enter code from email
|
||||
// user is redirected to app (default redirect url)
|
||||
});
|
||||
|
||||
test("username and password login, reset password - click link", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user has forgotten password and clicks the forgot password button, and then the link in the email
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// click password forgotten
|
||||
// click link in email
|
||||
// set new password
|
||||
// redirect to app (default redirect url)
|
||||
});
|
||||
|
||||
test("username and password login, reset password, resend code", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user has forgotten password and clicks the forgot password button and then resend code
|
||||
// Given username password login is enabled on the users organization
|
||||
// enter login name
|
||||
// click password forgotten
|
||||
// click resend code
|
||||
// enter code from second email
|
||||
// user is redirected to app (default redirect url)
|
||||
});
|
||||
|
||||
test("email login enabled", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given no other user with the same email address exists
|
||||
// enter email address "test@zitadel.com " in login screen
|
||||
// user will get to password screen
|
||||
});
|
||||
|
||||
test("email login disabled", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given no other user with the same email address exists
|
||||
// enter email address "test@zitadel.com" in login screen
|
||||
// user will see error message "user not found"
|
||||
});
|
||||
|
||||
test("email login enabled - multiple users", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists
|
||||
// enter email address "test@zitadel.com" in login screen
|
||||
// user will see error message "user not found"
|
||||
});
|
||||
|
||||
test("phone login enabled", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given no other user with the same phon number exists
|
||||
// enter phone number "0711111111" in login screen
|
||||
// user will get to password screen
|
||||
});
|
||||
|
||||
test("phone login disabled", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given no other user with the same phone number exists
|
||||
// enter phone number "0711111111" in login screen
|
||||
// user will see error message "user not found"
|
||||
});
|
||||
|
||||
test("phone login enabled - multiple users", async ({ user, page }) => {
|
||||
test.skip();
|
||||
// Given user with the username "testuser", email test@zitadel.com and phone number 0711111111 exists
|
||||
// Given a second user with the username "testuser2", email test@zitadel.com and phone number 0711111111 exists
|
||||
// enter phone number "0711111111" in login screen
|
||||
// user will see error message "user not found"
|
||||
});
|
||||
6
apps/login/acceptance/tests/welcome.ts
Normal file
6
apps/login/acceptance/tests/welcome.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { test } from "@playwright/test";
|
||||
|
||||
test("login is accessible", async ({ page }) => {
|
||||
await page.goto("./");
|
||||
await page.getByRole("heading", { name: "Welcome back!" }).isVisible();
|
||||
});
|
||||
190
apps/login/acceptance/tests/zitadel.ts
Normal file
190
apps/login/acceptance/tests/zitadel.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { Authenticator } from "@otplib/core";
|
||||
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
|
||||
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two"; // use your chosen base32 plugin
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
import { request } from "gaxios";
|
||||
import path from "path";
|
||||
import { OtpType, userProps } from "./user";
|
||||
|
||||
dotenv.config({ path: path.resolve(__dirname, "../../login/.env.test.local") });
|
||||
|
||||
export async function addUser(props: userProps) {
|
||||
const body = {
|
||||
username: props.email,
|
||||
organization: {
|
||||
orgId: props.organization,
|
||||
},
|
||||
profile: {
|
||||
givenName: props.firstName,
|
||||
familyName: props.lastName,
|
||||
},
|
||||
email: {
|
||||
email: props.email,
|
||||
isVerified: true,
|
||||
},
|
||||
phone: {
|
||||
phone: props.phone,
|
||||
isVerified: true,
|
||||
},
|
||||
password: {
|
||||
password: props.password,
|
||||
changeRequired: props.passwordChangeRequired ?? false,
|
||||
},
|
||||
};
|
||||
if (!props.isEmailVerified) {
|
||||
delete body.email.isVerified;
|
||||
}
|
||||
if (!props.isPhoneVerified) {
|
||||
delete body.phone.isVerified;
|
||||
}
|
||||
|
||||
return await listCall(`${process.env.ZITADEL_API_URL}/v2/users/human`, body);
|
||||
}
|
||||
|
||||
export async function removeUserByUsername(username: string) {
|
||||
const resp = await getUserByUsername(username);
|
||||
if (!resp || !resp.result || !resp.result[0]) {
|
||||
return;
|
||||
}
|
||||
await removeUser(resp.result[0].userId);
|
||||
}
|
||||
|
||||
export async function removeUser(id: string) {
|
||||
await deleteCall(`${process.env.ZITADEL_API_URL}/v2/users/${id}`);
|
||||
}
|
||||
|
||||
async function deleteCall(url: string) {
|
||||
try {
|
||||
const response = await axios.delete(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status >= 400 && response.status !== 404) {
|
||||
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
||||
console.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error making request:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserByUsername(username: string): Promise<any> {
|
||||
const listUsersBody = {
|
||||
queries: [
|
||||
{
|
||||
userNameQuery: {
|
||||
userName: username,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return await listCall(`${process.env.ZITADEL_API_URL}/v2/users`, listUsersBody);
|
||||
}
|
||||
|
||||
async function listCall(url: string, data: any): Promise<any> {
|
||||
try {
|
||||
const response = await axios.post(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status >= 400) {
|
||||
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
||||
console.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error making request:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function activateOTP(userId: string, type: OtpType) {
|
||||
let url = "otp_";
|
||||
switch (type) {
|
||||
case OtpType.sms:
|
||||
url = url + "sms";
|
||||
break;
|
||||
case OtpType.email:
|
||||
url = url + "email";
|
||||
break;
|
||||
}
|
||||
|
||||
await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/${url}`, {});
|
||||
}
|
||||
|
||||
async function pushCall(url: string, data: any) {
|
||||
try {
|
||||
const response = await axios.post(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status >= 400) {
|
||||
const error = `HTTP Error: ${response.status} - ${response.statusText}`;
|
||||
console.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error making request:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function addTOTP(userId: string): Promise<string> {
|
||||
const response = await listCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp`, {});
|
||||
const code = totp(response.secret);
|
||||
await pushCall(`${process.env.ZITADEL_API_URL}/v2/users/${userId}/totp/verify`, { code: code });
|
||||
return response.secret;
|
||||
}
|
||||
|
||||
export function totp(secret: string) {
|
||||
const authenticator = new Authenticator({
|
||||
createDigest,
|
||||
createRandomBytes,
|
||||
keyDecoder,
|
||||
keyEncoder,
|
||||
});
|
||||
// google authenticator usage
|
||||
const token = authenticator.generate(secret);
|
||||
|
||||
// check if token can be used
|
||||
if (!authenticator.verify({ token: token, secret: secret })) {
|
||||
const error = `Generated token could not be verified`;
|
||||
console.error(error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function eventualNewUser(id: string) {
|
||||
return request({
|
||||
url: `${process.env.ZITADEL_API_URL}/v2/users/${id}`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.ZITADEL_ADMIN_TOKEN}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
retryConfig: {
|
||||
statusCodesToRetry: [[404, 404]],
|
||||
retry: Number.MAX_SAFE_INTEGER, // totalTimeout limits the number of retries
|
||||
totalTimeout: 10000, // 10 seconds
|
||||
onRetryAttempt: (error) => {
|
||||
console.warn(`Retrying to query new user ${id}: ${error.message}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user