Skip to content

Commit 00a638c

Browse files
Kzoepsaspiers
authored andcommitted
fix: replace nested form with formaction button in recovery OTP template (ePDS-8u7.2)
1 parent 9f7c0e2 commit 00a638c

3 files changed

Lines changed: 34 additions & 9 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,9 @@ import { AuthServiceContext } from './context.js'
212212
- During rebases/merges, **never** resolve `.beads/` conflicts manually or with
213213
`merge=ours`. The `merge=beads` driver must be registered; if it isn't,
214214
the merge will fail — fix the driver registration rather than working around it.
215+
- **TODO**: verify whether `merge=beads` is still the right strategy for
216+
`.beads/backup/*.jsonl` now that dolt is the backend, or whether conflicts
217+
there should simply never happen (dolt is source of truth).
215218

216219
## Key Gotchas
217220

packages/auth-service/src/routes/login-page.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ function renderLoginPage(opts: {
214214
const b = opts.branding
215215
const appName = b.client_name || opts.clientName || 'Certified'
216216
const logoHtml = b.logo_uri
217-
? `<img src="${escapeHtml(b.logo_uri)}" alt="${escapeHtml(appName)}" class="client-logo" aria-hidden="true">`
217+
? `<img src="${escapeHtml(b.logo_uri)}" alt="${escapeHtml(appName)}" class="client-logo">`
218218
: ''
219219

220220
const inputProps = buildOtpInputProps(opts.otpLength, opts.otpCharset)
@@ -788,8 +788,13 @@ function renderLoginPage(opts: {
788788
</svg>
789789
</span>
790790
<input type="text" id="code" name="code" class="input-field otp-input" required
791-
maxlength="8" pattern="[0-9]{8}" inputmode="numeric"
792-
autocomplete="one-time-code" placeholder="00000000" ${opts.initialStep === 'otp' ? 'autofocus' : ''}>
791+
maxlength="${opts.otpLength}"
792+
pattern="${inputProps.pattern}"
793+
inputmode="${inputProps.inputmode}"
794+
autocomplete="one-time-code"
795+
autocapitalize="${inputProps.autocapitalize}"
796+
placeholder="${inputProps.placeholder}"
797+
${opts.initialStep === 'otp' ? 'autofocus' : ''}>
793798
</div>
794799
</div>
795800
</div>

packages/auth-service/src/routes/recovery.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export function createRecoveryRouter(
3535

3636
router.get('/auth/recover', (req: Request, res: Response) => {
3737
const requestUri = req.query.request_uri as string | undefined
38+
const clientId = req.query.client_id as string | undefined
3839

3940
if (!requestUri) {
4041
res.status(400).send(renderError('Missing request_uri parameter'))
@@ -44,6 +45,7 @@ export function createRecoveryRouter(
4445
res.type('html').send(
4546
renderRecoveryForm({
4647
requestUri,
48+
clientId,
4749
csrfToken: res.locals.csrfToken,
4850
}),
4951
)
@@ -52,11 +54,13 @@ export function createRecoveryRouter(
5254
router.post('/auth/recover', async (req: Request, res: Response) => {
5355
const email = ((req.body.email as string) || '').trim().toLowerCase()
5456
const requestUri = req.body.request_uri as string
57+
const clientId = (req.body.client_id as string | undefined) || undefined
5558

5659
if (!email || !requestUri) {
5760
res.status(400).send(
5861
renderRecoveryForm({
5962
requestUri: requestUri || '',
63+
clientId,
6064
csrfToken: res.locals.csrfToken,
6165
error: 'Email and request URI are required.',
6266
}),
@@ -68,6 +72,7 @@ export function createRecoveryRouter(
6872
res.status(400).send(
6973
renderRecoveryForm({
7074
requestUri,
75+
clientId,
7176
csrfToken: res.locals.csrfToken,
7277
error: 'Please enter a valid email address.',
7378
}),
@@ -111,6 +116,7 @@ export function createRecoveryRouter(
111116
email,
112117
csrfToken: res.locals.csrfToken,
113118
requestUri,
119+
clientId,
114120
otpLength,
115121
otpCharset,
116122
}),
@@ -122,6 +128,7 @@ export function createRecoveryRouter(
122128
email,
123129
csrfToken: res.locals.csrfToken,
124130
requestUri,
131+
clientId,
125132
error: 'Failed to send code. Please try again.',
126133
otpLength,
127134
otpCharset,
@@ -135,6 +142,7 @@ export function createRecoveryRouter(
135142
email,
136143
csrfToken: res.locals.csrfToken,
137144
requestUri,
145+
clientId,
138146
otpLength,
139147
otpCharset,
140148
}),
@@ -201,12 +209,16 @@ export function createRecoveryRouter(
201209
return router
202210
}
203211

204-
function renderRecoveryForm(opts: {
212+
export function renderRecoveryForm(opts: {
205213
requestUri: string
206214
csrfToken: string
215+
clientId?: string
207216
error?: string
208217
}): string {
209218
const encodedUri = encodeURIComponent(opts.requestUri)
219+
const clientIdParam = opts.clientId
220+
? `&client_id=${encodeURIComponent(opts.clientId)}`
221+
: ''
210222
const errorHtml = opts.error
211223
? `<div class="admonition error" role="alert">
212224
<span class="admonition-icon" aria-hidden="true">
@@ -239,6 +251,7 @@ function renderRecoveryForm(opts: {
239251
<form method="POST" action="/auth/recover">
240252
<input type="hidden" name="csrf" value="${escapeHtml(opts.csrfToken)}">
241253
<input type="hidden" name="request_uri" value="${escapeHtml(opts.requestUri)}">
254+
${opts.clientId ? `<input type="hidden" name="client_id" value="${escapeHtml(opts.clientId)}">` : ''}
242255
<div class="field">
243256
<label class="field-label" for="email">Backup email address</label>
244257
<div class="input-container">
@@ -254,7 +267,7 @@ function renderRecoveryForm(opts: {
254267
</div>
255268
</div>
256269
<div class="form-actions">
257-
<a href="/oauth/authorize?request_uri=${encodedUri}" class="link-btn">Back to sign in</a>
270+
<a href="/oauth/authorize?request_uri=${encodedUri}${clientIdParam}" class="link-btn">Back to sign in</a>
258271
<button type="submit" class="btn-primary">Send Recovery Code</button>
259272
</div>
260273
</form>
@@ -264,16 +277,20 @@ function renderRecoveryForm(opts: {
264277
</html>`
265278
}
266279

267-
function renderOtpForm(opts: {
280+
export function renderOtpForm(opts: {
268281
email: string
269282
csrfToken: string
270283
requestUri: string
271284
otpLength: number
272285
otpCharset: 'numeric' | 'alphanumeric'
286+
clientId?: string
273287
error?: string
274288
}): string {
275289
const maskedEmail = maskEmail(opts.email)
276290
const encodedUri = encodeURIComponent(opts.requestUri)
291+
const clientIdParam = opts.clientId
292+
? `&client_id=${encodeURIComponent(opts.clientId)}`
293+
: ''
277294
const inputProps = buildOtpInputProps(opts.otpLength, opts.otpCharset)
278295
const errorHtml = opts.error
279296
? `<div class="admonition error" role="alert">
@@ -330,13 +347,13 @@ function renderOtpForm(opts: {
330347
</div>
331348
<div class="form-actions">
332349
<div class="otp-links">
333-
<a href="/oauth/authorize?request_uri=${encodedUri}" class="link-btn">Back to sign in</a>
350+
<a href="/oauth/authorize?request_uri=${encodedUri}${clientIdParam}" class="link-btn">Back to sign in</a>
334351
<span class="otp-links-dot">·</span>
335-
<form method="POST" action="/auth/recover" class="inline-form">
352+
<form method="POST" action="/auth/recover" class="inline-form">
336353
<input type="hidden" name="csrf" value="${escapeHtml(opts.csrfToken)}">
337354
<input type="hidden" name="request_uri" value="${escapeHtml(opts.requestUri)}">
338355
<input type="hidden" name="email" value="${escapeHtml(opts.email)}">
339-
<button type="submit" class="link-btn">Resend Code</button>
356+
<button type="submit" class="link-btn" formnovalidate>Resend Code</button>
340357
</form>
341358
</div>
342359
<button type="submit" class="btn-primary">Verify</button>

0 commit comments

Comments
 (0)