- ui/ui.go: add pendingLogin struct and pendingLogins sync.Map to UIServer; add issueTOTPNonce (generates 128-bit random nonce, stores accountID with 90s TTL) and consumeTOTPNonce (single-use, expiry-checked LoadAndDelete); add dummyHash() method - ui/handlers_auth.go: split handleLoginPost into step 1 (password verify → issue nonce) and step 2 (handleTOTPStep, consume nonce → validate TOTP) via a new finishLogin helper; password never transmitted or stored after step 1 - ui/ui_test.go: refactor newTestMux to reuse new newTestUIServer; add TestTOTPNonceIssuedAndConsumed, TestTOTPNonceUnknownRejected, TestTOTPNonceExpired, and TestLoginPostPasswordNotInTOTPForm; 11/11 tests pass - web/templates/fragments/totp_step.html: replace 'name=password' hidden field with 'name=totp_nonce' - db/accounts.go: add GetAccountByID for TOTP step lookup - AUDIT.md: mark F-02 as fixed Security: the plaintext password previously survived two HTTP round-trips and lived in the browser DOM during the TOTP step. The nonce approach means the password is verified once and immediately discarded; only an opaque random token tied to an account ID (never a credential) crosses the wire on step 2. Nonces are single-use and expire after 90 seconds to limit the window if one is captured.
19 lines
856 B
HTML
19 lines
856 B
HTML
{{define "totp_step"}}
|
|
<form id="login-form" method="POST" action="/login"
|
|
hx-post="/login" hx-target="#login-form" hx-swap="outerHTML">
|
|
{{if .Error}}<div class="alert alert-error" role="alert">{{.Error}}</div>{{end}}
|
|
<input type="hidden" name="username" value="{{.Username}}">
|
|
<input type="hidden" name="totp_nonce" value="{{.Nonce}}">
|
|
<input type="hidden" name="totp_step" value="1">
|
|
<div class="form-group">
|
|
<label for="totp_code">Authenticator Code</label>
|
|
<input class="form-control" type="text" id="totp_code" name="totp_code"
|
|
autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]{6}"
|
|
maxlength="6" required autofocus placeholder="6-digit code">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button class="btn btn-primary" type="submit" style="width:100%">Verify</button>
|
|
</div>
|
|
</form>
|
|
{{end}}
|