Link the MFA challenge to lost-authenticator help.
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m1s
Authentication acceptance / provider-contract (push) Successful in 24s
Build and Publish Container Image / build-and-push (push) Successful in 40s

The OTP page now points at the account security page without carrying challenge state, and the render test checks that link. Workplan headings sit above their task blocks so the hub titles match KEY-WP-0038 and KEY-WP-0039. KEY-WP-0039-T02 stays waiting on the Forgejo identity gate.

Assistant: grok
Assistant-Session: 01a13b3b-6856-7900-9d04-bccba2337a30
This commit is contained in:
tegwick 2026-10-14 18:29:33 +02:00
parent 61ee563b1f
commit 205a1c0edc
7 changed files with 55 additions and 33 deletions

View file

@ -760,6 +760,7 @@ var mfaChallengeTemplate = template.Must(template.New("mfa-challenge").Parse(`<!
<input id="mfa_token" name="mfa_token" type="text" inputmode="numeric" autocomplete="one-time-code" required autofocus>
<button type="submit">Verify</button>
</form>
<p><a href="https://users.coulomb.social/security" rel="noreferrer">Code rejected or lost your authenticator?</a></p>
</main>
</body>
</html>`))

View file

@ -531,11 +531,19 @@ func TestAuthorizeCallback_MFARequired_RendersChallengeWithoutToken(t *testing.T
t.Errorf("expected 200 challenge page, got %d (body: %s)", w.Code, w.Body.String())
}
body := w.Body.String()
for _, want := range []string{"Verify sign-in", "alice", "Test Client", `name="mfa_token"`} {
for _, want := range []string{
"Verify sign-in", "alice", "Test Client", `name="mfa_token"`,
`href="https://users.coulomb.social/security"`,
`rel="noreferrer"`,
"Code rejected or lost your authenticator?",
} {
if !strings.Contains(body, want) {
t.Errorf("challenge page missing %q in body: %s", want, body)
}
}
if formEnd := strings.Index(body, "</form>"); formEnd < 0 || !strings.Contains(body[formEnd:], `href="https://users.coulomb.social/security"`) {
t.Error("lost-authenticator help link must follow the OTP form and carry no request state")
}
if mfa.validateCalls != 0 {
t.Errorf("MFA token should not be validated until form submission, got %d calls", mfa.validateCalls)
}