feat(web): public /invite/{token} set-password + account-creation flow
The Stage-1 onboarding path: an invited user opens their emailed link, sets a password, and Tapir creates their Dex local-password account so they can log in. Mounted on root OUTSIDE Auth.Middleware — the visitor has no Dex session yet; the token in the path is the capability. handleInviteForm previews the token (no consume) and shows the form, or a clear "expired / already used" page. handleInviteSubmit validates the password BEFORE consuming the token (a typo is retryable), then claims the invite exactly once, bcrypt-hashes (cost 12), and creates the Dex account — mapping ErrPasswordExists -> "log in instead" and ErrForbidden -> "contact the administrator". Off-cluster (App.Dex nil) it degrades to a "deployed-only" message without burning the token. On success it sets an account_created flash and redirects to /auth/login. Welcome sub-text now states access is invite-only. Handlers depend on narrow ports (InvitationStore, DexPasswordCreator) so tests use fakes; cmdServe wires the store + an in-cluster dex.PasswordClient. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package web
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Test-only handles to the unexported invite handlers so the external web_test
|
||||
// package can mount them on an httptest mux (and get PathValue routing) without
|
||||
// standing up the full Router + auth stack. export_test.go compiles only under
|
||||
// `go test`, so these never widen the package's real API.
|
||||
func (a *App) HandleInviteFormForTest(w http.ResponseWriter, r *http.Request) {
|
||||
a.handleInviteForm(w, r)
|
||||
}
|
||||
func (a *App) HandleInviteSubmitForTest(w http.ResponseWriter, r *http.Request) {
|
||||
a.handleInviteSubmit(w, r)
|
||||
}
|
||||
Reference in New Issue
Block a user