feat(web): registration gate + per-request user-id seam (ADR-012)
Multi-user web surface. Two layered middlewares: Auth.Middleware (Dex session required) wraps registrationGate, which resolves the authenticated subject -> tapir user_id once per request via the new web.Identity port and stashes it. A subject with no tapir user is redirected to GET /register (display name + accept-terms); POST /register calls RegisterUser then redirects to /. /register is inside the auth guard but exempt from the gate (/auth/* and /healthz too). Current-user seam: CurrentUserID(r) (string, bool) returns the resolved id from the request context. The list/detail/action handlers now scope by it, replacing the single configured App.UserID (removed). App gains an Identity field; *store.Store satisfies both Store and Identity. cmd/tapir wires Identity: st and drops UserID. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,35 @@ templ DetailPage(r store.SummaryRow) {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPage is the explicit registration step (ADR-012): an authenticated Dex
|
||||
// subject with no tapir user picks a display name and accepts the terms to create
|
||||
// their account. errMsg, when set, reports a validation problem on the prior POST.
|
||||
templ RegisterPage(email, errMsg string) {
|
||||
@Layout("Tapir — Register") {
|
||||
<article class="register">
|
||||
<h1>Complete your registration</h1>
|
||||
if email != "" {
|
||||
<p class="meta">Signed in as { email }.</p>
|
||||
}
|
||||
<p>Choose a display name to finish setting up your Tapir account.</p>
|
||||
if errMsg != "" {
|
||||
<p class="error" role="alert">{ errMsg }</p>
|
||||
}
|
||||
<form method="post" action="/register" class="register-form">
|
||||
<label>
|
||||
Display name
|
||||
<input type="text" name="display_name" required autofocus/>
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="accept_terms" value="yes" required/>
|
||||
I accept the terms of use
|
||||
</label>
|
||||
<button type="submit" class="btn">Register</button>
|
||||
</form>
|
||||
</article>
|
||||
}
|
||||
}
|
||||
|
||||
// ActionButtons is the toggle group fragment returned by POST /v/{id}/action.
|
||||
// Each button submits its verb; HTMX swaps this element in place (outerHTML),
|
||||
// and without JS the form POSTs and the handler redirects back to the detail
|
||||
|
||||
Reference in New Issue
Block a user