Skip to content

Commit f924cfc

Browse files
committed
Guard ZITADEL username resubmit against password transition
1 parent ea61248 commit f924cfc

1 file changed

Lines changed: 50 additions & 5 deletions

File tree

tests/integration/assertions/browser.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ async function waitForPasswordInputAfterUsernameSubmit(page: Page, usernameSelec
9090
let lastBody = "";
9191

9292
while (Date.now() < deadline) {
93+
if (isPasswordLoginStep(page.url())) {
94+
const selector = await visiblePasswordInputSelector(page, 5000);
95+
if (selector) {
96+
return selector;
97+
}
98+
}
99+
93100
for (const selector of PASSWORD_INPUT_SELECTORS) {
94101
const locator = page.locator(selector).first();
95102
if (await locatorVisible(locator)) {
@@ -108,14 +115,17 @@ async function waitForPasswordInputAfterUsernameSubmit(page: Page, usernameSelec
108115
throw new Error(`ZITADEL username step failed before password input\nbody:\n${bodySnippetForError(lastBody)}`);
109116
}
110117

111-
const parsedUrl = parseBrowserUrl(currentUrl);
112-
const onUsernameStep = parsedUrl?.pathname.toLowerCase().startsWith("/ui/v2/login/loginname") ?? false;
113-
if (onUsernameStep && isIdentityLoginInputPage(currentUrl, targetHost)) {
114-
const usernameInputVisible = await inputVisible(page, USERNAME_INPUT_SELECTORS);
118+
if (isUsernameLoginStep(currentUrl) && isIdentityLoginInputPage(currentUrl, targetHost)) {
115119
const now = Date.now();
116-
if (usernameInputVisible && now - lastResubmit > 5000) {
120+
if (now - lastResubmit > 5000 && await canResubmitUsername(page, usernameSelector)) {
117121
lastResubmit = now;
122+
if (isPasswordLoginStep(page.url())) {
123+
continue;
124+
}
118125
await typeInto(page, usernameSelector, userEmail);
126+
if (isPasswordLoginStep(page.url())) {
127+
continue;
128+
}
119129
await submitIdentityForm(page, usernameSelector, USERNAME_SUBMIT_SELECTORS);
120130
await page.waitForTimeout(1000);
121131
continue;
@@ -134,6 +144,41 @@ async function waitForPasswordInputAfterUsernameSubmit(page: Page, usernameSelec
134144
);
135145
}
136146

147+
async function visiblePasswordInputSelector(page: Page, timeoutMs: number): Promise<string | undefined> {
148+
const deadline = Date.now() + timeoutMs;
149+
while (Date.now() < deadline) {
150+
for (const selector of PASSWORD_INPUT_SELECTORS) {
151+
const locator = page.locator(selector).first();
152+
if (await locatorVisible(locator)) {
153+
return selector;
154+
}
155+
}
156+
157+
await page.waitForTimeout(250);
158+
}
159+
160+
return undefined;
161+
}
162+
163+
async function canResubmitUsername(page: Page, usernameSelector: string): Promise<boolean> {
164+
if (!isUsernameLoginStep(page.url())) {
165+
return false;
166+
}
167+
168+
const locator = page.locator(usernameSelector).first();
169+
return (await locatorVisible(locator)) && (await waitForEditableInput(locator)) && isUsernameLoginStep(page.url());
170+
}
171+
172+
function isUsernameLoginStep(currentUrl: string): boolean {
173+
const parsedUrl = parseBrowserUrl(currentUrl);
174+
return parsedUrl?.pathname.toLowerCase().startsWith("/ui/v2/login/loginname") ?? false;
175+
}
176+
177+
function isPasswordLoginStep(currentUrl: string): boolean {
178+
const parsedUrl = parseBrowserUrl(currentUrl);
179+
return parsedUrl?.pathname.toLowerCase().startsWith("/ui/v2/login/password") ?? false;
180+
}
181+
137182
async function reloadBlankLoginDocumentIfNeeded(page: Page): Promise<boolean> {
138183
const parsed = parseBrowserUrl(page.url());
139184
if (!parsed || !isLoginOrOauthCallbackPlumbingPath(parsed.pathname)) {

0 commit comments

Comments
 (0)