Skip to content

Commit 566bb5a

Browse files
committed
🐞 Fix login hints never reaching the login form
Three independent defects each stopped `login_hint__*` parameters from prefilling the Keycloak username field. `parseLoginHints` iterated `URLSearchParams.entries()` and `Map.keys()` with `for ... of`. The portal compiles with `target: "es5"` and no `downlevelIteration`, so both loops became indexed loops over an iterator whose `length` is `undefined`, and never ran a single iteration. Hints were therefore always empty, so the portal took the plain login path and left the hints in the redirect URI. The unit tests did not catch this because `@swc/jest` builds them for `es2022`. `createLoginUrl` and `createRegisterUrl` return `Promise<string>` in keycloak-js 26, and were passed unawaited to `appendLoginHints`, which threw `Invalid URL` and aborted the redirect. TypeScript reports this, but the webpack build runs `ts-loader` with `transpileOnly`. The voting portal login template dropped stock Keycloak's `value="${(login.username!'')}"`, so the username field rendered empty even when Keycloak had resolved the hint.
1 parent 3ff9f0a commit 566bb5a

3 files changed

Lines changed: 16 additions & 22 deletions

File tree

packages/keycloak-extensions/sequent-theme/src/main/resources/theme/sequent.voting-portal/login/login.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
1717
<div class="${properties.kcFormGroupClass!}">
1818
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
1919

20-
<input tabindex="1" id="username" class="${properties.kcInputClass!}" name="username" type="text" autofocus autocomplete="off"
20+
<input tabindex="1" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
2121
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
2222
/>
2323

packages/voting-portal/src/providers/AuthContextProvider.tsx

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -313,28 +313,20 @@ const AuthContextProvider = (props: AuthContextProviderProps) => {
313313
}
314314

315315
if (Object.keys(loginHints).length > 0) {
316-
window.location.assign(
317-
appendLoginHints(
318-
keycloak.createRegisterUrl(registerOptions),
319-
loginHints
320-
)
321-
)
316+
const registerUrl = await keycloak.createRegisterUrl(registerOptions)
317+
window.location.assign(appendLoginHints(registerUrl, loginHints))
322318
return
323319
}
324320

325321
return await keycloak.register(registerOptions)
326322
} else {
327323
if (Object.keys(loginHints).length > 0) {
328-
window.location.assign(
329-
appendLoginHints(
330-
keycloak.createLoginUrl({
331-
...keycloakInitOptions,
332-
// Stock username forms only understand the standard OIDC hint.
333-
loginHint: loginHints.username,
334-
}),
335-
loginHints
336-
)
337-
)
324+
const loginUrl = await keycloak.createLoginUrl({
325+
...keycloakInitOptions,
326+
// Stock username forms only understand the standard OIDC hint.
327+
loginHint: loginHints.username,
328+
})
329+
window.location.assign(appendLoginHints(loginUrl, loginHints))
338330
return
339331
}
340332

packages/voting-portal/src/utils/loginHints.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ export const parseLoginHints = (search: string): ParsedLoginHints => {
6969
const searchParams = new URLSearchParams(search)
7070
const hints = new Map<string, string>()
7171

72-
for (const [parameterName, value] of searchParams.entries()) {
72+
// The portal is compiled targeting ES5, where `for ... of` over an iterator
73+
// (as opposed to an array) is transpiled into a no-op indexed loop.
74+
searchParams.forEach((value, parameterName) => {
7375
if (!parameterName.startsWith(LOGIN_HINT_PREFIX)) {
74-
continue
76+
return
7577
}
7678

7779
const hintName = parameterName.slice(LOGIN_HINT_PREFIX.length)
@@ -85,11 +87,11 @@ export const parseLoginHints = (search: string): ParsedLoginHints => {
8587
if (hints.size > MAX_LOGIN_HINT_COUNT) {
8688
throw new InvalidLoginHintsError()
8789
}
88-
}
90+
})
8991

90-
for (const hintName of hints.keys()) {
92+
hints.forEach((_value, hintName) => {
9193
searchParams.delete(`${LOGIN_HINT_PREFIX}${hintName}`)
92-
}
94+
})
9395

9496
const remainingSearch = searchParams.toString()
9597
return {

0 commit comments

Comments
 (0)