diff --git a/acceptance/tests/login.ts b/acceptance/tests/login.ts index 3ebcade5f78..20d27e69f90 100644 --- a/acceptance/tests/login.ts +++ b/acceptance/tests/login.ts @@ -2,18 +2,23 @@ import {Page} from "@playwright/test"; export async function loginWithPassword(page: Page, username: string, password: string) { await page.goto("/loginname"); - const loginname = page.getByLabel("Loginname"); - await loginname.pressSequentially(username); - await loginname.press("Enter"); - const pw = page.getByLabel("Password"); - await pw.pressSequentially(password); - await pw.press("Enter"); + await loginnameScreen(page, username) + await page.getByTestId("submit-button").click() + await passwordScreen(page, password) + await page.getByTestId("submit-button").click() } +export async function loginnameScreen(page: Page, username: string) { + await page.getByTestId("username-text-input").pressSequentially(username); +} + +export async function passwordScreen(page: Page, password: string) { + await page.getByTestId("password-text-input").pressSequentially(password); +} export async function loginWithPasskey(page: Page, username: string) { await page.goto("/loginname"); - const loginname = page.getByLabel("Loginname"); - await loginname.pressSequentially(username); - await loginname.press("Enter"); + await loginnameScreen(page, username) + await page.getByTestId("submit-button").click() + await page.getByTestId("submit-button").click() } \ No newline at end of file diff --git a/acceptance/tests/password.ts b/acceptance/tests/password.ts index 360ff65e17a..42d7190a10c 100644 --- a/acceptance/tests/password.ts +++ b/acceptance/tests/password.ts @@ -2,11 +2,11 @@ import {Page} from "@playwright/test"; export async function changePassword(page: Page, loginname: string, password: string) { await page.goto('password/change?' + new URLSearchParams({loginName: loginname})); - await changePasswordScreen(page, loginname, password, password) - await page.getByRole('button', {name: 'Continue'}).click(); + await changePasswordScreen(page, password, password) + await page.getByTestId("submit-button").click(); } -async function changePasswordScreen(page: Page, loginname: string, password1: string, password2: string) { - await page.getByLabel('New Password *').pressSequentially(password1); - await page.getByLabel('Confirm Password *').pressSequentially(password2); +async function changePasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId('password-text-input').pressSequentially(password1); + await page.getByTestId('password-confirm-text-input').pressSequentially(password2); } \ No newline at end of file diff --git a/acceptance/tests/register.spec.ts b/acceptance/tests/register.spec.ts index 932bf3134c2..fe6cef15225 100644 --- a/acceptance/tests/register.spec.ts +++ b/acceptance/tests/register.spec.ts @@ -3,11 +3,8 @@ import {registerWithPassword} from './register'; import {loginWithPassword} from "./login"; test("register with password", async ({page}) => { - const firstname = "firstname" - const lastname = "lastname" const username = "register@example.com" const password = "Password1!" - await registerWithPassword(page, firstname, lastname, username, password, password) - await page.getByRole("heading", {name: "Welcome " + lastname + " " + lastname + "!"}).click(); + await registerWithPassword(page, "firstname", "lastname", username, password, password) await loginWithPassword(page, username, password) }); diff --git a/acceptance/tests/register.ts b/acceptance/tests/register.ts index bb403f6999c..9700e9b2a10 100644 --- a/acceptance/tests/register.ts +++ b/acceptance/tests/register.ts @@ -2,30 +2,38 @@ import {Page} from "@playwright/test"; 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(); +} + +async function registerUserScreenPassword(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) await page.getByLabel('Password').click(); - await page.getByRole('button', {name: 'Continue'}).click(); - await registerPasswordScreen(page, password1, password2) - await page.getByRole('button', {name: 'Continue'}).click(); +} + +async function registerPasswordScreen(page: Page, password1: string, password2: string) { + await page.getByTestId('password-text-input').fill(password1); + await page.getByTestId('password-confirm-text-input').fill(password2); } export async function registerWithPasskey(page: Page, firstname: string, lastname: string, email: string) { await page.goto('/register'); + await registerUserScreenPasskey(page, firstname, lastname, email) + await page.getByTestId('submit-button').click(); + await page.getByTestId('submit-button').click(); +} + +async function registerUserScreenPasskey(page: Page, firstname: string, lastname: string, email: string) { await registerUserScreen(page, firstname, lastname, email) await page.getByLabel('Passkey').click(); - await page.getByRole('button', {name: 'Continue'}).click(); - await page.getByRole('button', {name: 'Continue'}).click(); } async function registerUserScreen(page: Page, firstname: string, lastname: string, email: string) { - await page.getByLabel('First name *').pressSequentially(firstname); - await page.getByLabel('Last name *').pressSequentially(lastname); - await page.getByLabel('E-mail *').pressSequentially(email); - await page.getByRole('checkbox').first().check(); - await page.getByRole('checkbox').nth(1).check(); -} - -async function registerPasswordScreen(page: Page, password1: string, password2: string) { - await page.getByLabel('Password *', {exact: true}).fill(password1); - await page.getByLabel('Confirm Password *').fill(password2); + 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(); } \ No newline at end of file diff --git a/acceptance/tests/user.ts b/acceptance/tests/user.ts index 18c2ec55be4..4e0b6b17fac 100644 --- a/acceptance/tests/user.ts +++ b/acceptance/tests/user.ts @@ -1,4 +1,4 @@ -import fetch from 'node-fetch'; +import fetch from "node-fetch"; import {Page} from "@playwright/test"; import {registerWithPasskey} from "./register"; import {loginWithPasskey, loginWithPassword} from "./login"; @@ -20,7 +20,7 @@ class User { this.props = userProps; } - async ensure() { + async ensure(page: Page) { await this.remove() const body = { @@ -72,6 +72,10 @@ class User { return } + public setUserId(userId: string) { + this.user = userId + } + public userId() { return this.user; } @@ -84,6 +88,14 @@ class User { return this.props.password; } + public firstname() { + return this.props.firstName + } + + public lastname() { + return this.props.lastName + } + public fullName() { return this.props.firstName + " " + this.props.lastName } @@ -102,6 +114,73 @@ class User { export class PasswordUser extends User { } +enum OtpType { + time = "time-based", + sms = "sms", + email = "email", +} + +export interface otpUserProps { + email: string; + firstName: string; + lastName: string; + organization: string; + type: OtpType, +} + +export class PasswordUserWithOTP extends User { + private type: OtpType + private code: string + + constructor(props: otpUserProps) { + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: "" + }) + this.type = props.type + } + + async ensure(page: Page) { + await super.ensure(page) + + const body = { + username: this.props.email, + organization: { + orgId: this.props.organization + }, + profile: { + givenName: this.props.firstName, + familyName: this.props.lastName, + }, + email: { + email: this.props.email, + isVerified: true, + }, + password: { + password: this.props.password!, + } + } + + const response = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/human", { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + } + }); + if (response.statusCode >= 400 && response.statusCode != 409) { + const error = 'HTTP Error: ' + response.statusCode + ' - ' + response.statusMessage; + console.error(error); + throw new Error(error); + } + return + } +} + export interface passkeyUserProps { email: string; firstName: string; @@ -109,67 +188,58 @@ export interface passkeyUserProps { organization: string; } -export class PasskeyUser { - private props: passkeyUserProps - +export class PasskeyUser extends User { constructor(props: passkeyUserProps) { - this.props = props + super({ + email: props.email, + firstName: props.firstName, + lastName: props.lastName, + organization: props.organization, + password: "" + }) } - async ensurePasskey(page: Page) { - await registerWithPasskey(page, this.props.firstName, this.props.lastName, this.props.email) + public async ensure(page: Page) { + await this.remove() + await registerWithPasskey(page, this.firstname(), this.lastname(), this.username()) } public async login(page: Page) { - await loginWithPasskey(page, this.props.email) + await loginWithPasskey(page, this.username()) } - public fullName() { - return this.props.firstName + " " + this.props.lastName - } - - async ensurePasskeyRegister() { - const url = new URL(process.env.ZITADEL_API_URL!) - const registerBody = { - domain: url.hostname, + public async remove() { + const resp = await getUserByUsername(this.username()) + if (!resp || !resp.result || !resp.result[0]) { + return } - const userId = "" - const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys", { - method: 'POST', - body: JSON.stringify(registerBody), - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! + this.setUserId(resp.result[0].userId) + await super.remove() + } +} + +async function getUserByUsername(username: string) { + const listUsersBody = { + queries: [{ + userNameQuery: { + userName: username, } - }); - if (registerResponse.statusCode >= 400 && registerResponse.statusCode != 409) { - const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; - console.error(error); - throw new Error(error); - } - const respJson = await registerResponse.json() - return respJson + }] } - - async ensurePasskeyVerify(passkeyId: string, credential: Credential) { - const verifyBody = { - publicKeyCredential: credential, - passkeyName: "passkey", + const jsonBody = JSON.stringify(listUsersBody) + const registerResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users", { + method: 'POST', + body: jsonBody, + headers: { + 'Content-Type': 'application/json', + 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! } - const userId = "" - const verifyResponse = await fetch(process.env.ZITADEL_API_URL! + "/v2/users/" + userId + "/passkeys/" + passkeyId, { - method: 'POST', - body: JSON.stringify(verifyBody), - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Bearer " + process.env.ZITADEL_SERVICE_USER_TOKEN! - } - }); - if (verifyResponse.statusCode >= 400 && verifyResponse.statusCode != 409) { - const error = 'HTTP Error: ' + verifyResponse.statusCode + ' - ' + verifyResponse.statusMessage; - console.error(error); - throw new Error(error); - } - return + }); + if (registerResponse.statusCode >= 400) { + const error = 'HTTP Error: ' + registerResponse.statusCode + ' - ' + registerResponse.statusMessage; + console.error(error); + throw new Error(error); } + const respJson = await registerResponse.json() + return respJson } \ No newline at end of file diff --git a/acceptance/tests/username-passkey.spec.ts b/acceptance/tests/username-passkey.spec.ts index 0cc98851d9d..405a7590b1c 100644 --- a/acceptance/tests/username-passkey.spec.ts +++ b/acceptance/tests/username-passkey.spec.ts @@ -1,5 +1,7 @@ +import {test as base} from "@playwright/test"; import path from 'path'; import dotenv from 'dotenv'; +import {PasskeyUser} from "./user"; // Read from ".env" file. dotenv.config({path: path.resolve(__dirname, '.env.local')}); @@ -86,7 +88,7 @@ const test = base.extend<{ user: PasskeyUser }>({ enabled: false, }); }, -}); +});*/ const test = base.extend<{ user: PasskeyUser }>({ user: async ({page}, use) => { @@ -96,7 +98,7 @@ const test = base.extend<{ user: PasskeyUser }>({ lastName: "last", organization: "", }); - await user.ensurePasskey(page); + await user.ensure(page); await use(user) }, }); @@ -105,4 +107,3 @@ test("username and passkey login", async ({user, page}) => { await user.login(page) await page.getByRole("heading", {name: "Welcome " + user.fullName() + "!"}).click(); }); -*/ \ No newline at end of file diff --git a/acceptance/tests/username-password-changed.spec.ts b/acceptance/tests/username-password-changed.spec.ts index a3e5343672c..e9ade80b122 100644 --- a/acceptance/tests/username-password-changed.spec.ts +++ b/acceptance/tests/username-password-changed.spec.ts @@ -15,7 +15,7 @@ const test = base.extend<{ user: PasswordUser }>({ password: "Password1!", organization: "", }); - await user.ensure(); + await user.ensure(page); await use(user); }, }); diff --git a/acceptance/tests/username-password.spec.ts b/acceptance/tests/username-password.spec.ts index f0bb8bddb3d..e840382319a 100644 --- a/acceptance/tests/username-password.spec.ts +++ b/acceptance/tests/username-password.spec.ts @@ -15,7 +15,7 @@ const test = base.extend<{ user: PasswordUser }>({ password: "Password1!", organization: "", }); - await user.ensure(); + await user.ensure(page); await use(user); }, }); diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index ccdcd79281b..c025bfc607e 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -140,6 +140,7 @@ export function ChangePasswordForm({ })} label="New Password" error={errors.password?.message as string} + data-testid="password-text-input" />