simplify authentication

This commit is contained in:
Alisdair MacLeod
2026-01-30 14:08:52 +00:00
parent e95cfa1a00
commit f882c36e0a
6 changed files with 38 additions and 40 deletions

View File

@@ -1,13 +1,15 @@
<!doctype html>
{{ range . }}
{{ if eq .Key "pin" }}
{{ range $method, $value := .Methods }}
{{ if eq $method "pin" }}
<form>
<input name={{ . }} />
<label for={{ $value }}>PIN:</label>
<input name={{ $value }} id={{ $value }} />
<button type=submit></button>
</form>
{{ else if eq .Key "password" }}
{{ else if eq $method "password" }}
<form>
<input name={{ . }} />
<label for={{ $value }}>Password:</label>
<input name={{ $value }} id={{ $value }}/>
<button type=submit></button>
</form>
{{ end }}

View File

@@ -25,7 +25,7 @@ func (Link) Type() Method {
return MethodLink
}
func (l Link) Authenticate(r *http.Request) (string, bool, any) {
func (l Link) Authenticate(r *http.Request) (string, string) {
email := r.FormValue(linkFormId)
res, err := l.client.Authenticate(r.Context(), &proto.AuthenticateRequest{
@@ -40,15 +40,15 @@ func (l Link) Authenticate(r *http.Request) (string, bool, any) {
})
if err != nil {
// TODO: log error here
return "", false, linkFormId
return "", linkFormId
}
if res.GetSuccess() {
// Use the email address as the user identifier.
return email, true, nil
return email, ""
}
return "", false, linkFormId
return "", linkFormId
}
func (l Link) Middleware(next http.Handler) http.Handler {

View File

@@ -53,10 +53,8 @@ type Scheme interface {
// an empty string should indicate an unauthenticated request which
// will be rejected; optionally, it can also return any data that should
// be included in a UI template when prompting the user to authenticate.
// If the request is authenticated, then a user id should be returned
// along with a boolean indicating whether a redirect is needed to clean
// up authentication artifacts from the URLs query.
Authenticate(*http.Request) (userid string, needsRedirect bool, promptData any)
// If the request is authenticated, then a user id should be returned.
Authenticate(*http.Request) (userid string, promptData string)
// Middleware is applied within the outer auth middleware, but they will
// be applied after authentication if no scheme has authenticated a
// request.
@@ -119,32 +117,30 @@ func (mw *Middleware) Protect(next http.Handler) http.Handler {
}
// Try to authenticate with each scheme.
methods := make(map[Method]any)
methods := make(map[string]string)
for _, s := range schemes {
userid, needsRedirect, promptData := s.Authenticate(r)
userid, promptData := s.Authenticate(r)
if userid != "" {
mw.createSession(w, r, userid, s.Type())
if needsRedirect {
// Clean the path and redirect to the naked URL.
// This is intended to prevent leaking potentially
// sensitive query parameters for some authentication
// methods such as OIDC.
http.Redirect(w, r, r.URL.Path, http.StatusFound)
return
}
ctx := withAuthMethod(r.Context(), s.Type())
ctx = withAuthUser(ctx, userid)
next.ServeHTTP(w, r.WithContext(ctx))
// Clean the path and redirect to the naked URL.
// This is intended to prevent leaking potentially
// sensitive query parameters for authentication
// methods.
http.Redirect(w, r, r.URL.Path, http.StatusFound)
return
}
methods[s.Type()] = promptData
methods[s.Type().String()] = promptData
}
// The handler is passed through the scheme middlewares,
// if none of them intercept the request, then this handler will
// be called and present the user with the authentication page.
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := tmpl.Execute(w, methods); err != nil {
if err := tmpl.Execute(w, struct {
Methods map[string]string
}{
Methods: methods,
}); err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
}
}))

View File

@@ -83,18 +83,18 @@ func (*OIDC) Type() Method {
return MethodOIDC
}
func (o *OIDC) Authenticate(r *http.Request) (string, bool, any) {
func (o *OIDC) Authenticate(r *http.Request) (string, string) {
// Try Authorization: Bearer <token> header
if auth := r.Header.Get("Authorization"); strings.HasPrefix(auth, "Bearer ") {
if userID := o.validateToken(r.Context(), strings.TrimPrefix(auth, "Bearer ")); userID != "" {
return userID, false, nil
return userID, ""
}
}
// Try _auth_token query parameter (from OIDC callback redirect)
if token := r.URL.Query().Get("_auth_token"); token != "" {
if userID := o.validateToken(r.Context(), token); userID != "" {
return userID, true, nil // Redirect needed to clean up URL
return userID, ""
}
}
@@ -109,7 +109,7 @@ func (o *OIDC) Authenticate(r *http.Request) (string, bool, any) {
o.states[state] = &oidcState{OriginalURL: fmt.Sprintf("https://%s%s", r.Host, r.URL), CreatedAt: time.Now()}
o.statesMux.Unlock()
return "", false, o.oauthConfig.AuthCodeURL(state)
return "", o.oauthConfig.AuthCodeURL(state)
}
// Middleware returns an http.Handler that handles OIDC callback and flow initiation.

View File

@@ -33,7 +33,7 @@ func (Password) Type() Method {
// If authentication fails, the required HTTP form ID is returned
// so that it can be injected into a request from the UI so that
// authentication may be successful.
func (p Password) Authenticate(r *http.Request) (string, bool, any) {
func (p Password) Authenticate(r *http.Request) (string, string) {
password := r.FormValue(passwordFormId)
res, err := p.client.Authenticate(r.Context(), &proto.AuthenticateRequest{
@@ -47,14 +47,14 @@ func (p Password) Authenticate(r *http.Request) (string, bool, any) {
})
if err != nil {
// TODO: log error here
return "", false, passwordFormId
return "", passwordFormId
}
if res.GetSuccess() {
return passwordUserId, true, nil
return passwordUserId, ""
}
return "", false, passwordFormId
return "", passwordFormId
}
func (p Password) Middleware(next http.Handler) http.Handler {

View File

@@ -33,7 +33,7 @@ func (Pin) Type() Method {
// If authentication fails, the required HTTP form ID is returned
// so that it can be injected into a request from the UI so that
// authentication may be successful.
func (p Pin) Authenticate(r *http.Request) (string, bool, any) {
func (p Pin) Authenticate(r *http.Request) (string, string) {
pin := r.FormValue(pinFormId)
res, err := p.client.Authenticate(r.Context(), &proto.AuthenticateRequest{
@@ -47,14 +47,14 @@ func (p Pin) Authenticate(r *http.Request) (string, bool, any) {
})
if err != nil {
// TODO: log error here
return "", false, pinFormId
return "", pinFormId
}
if res.GetSuccess() {
return pinUserId, true, nil
return pinUserId, ""
}
return "", false, pinFormId
return "", pinFormId
}
func (p Pin) Middleware(next http.Handler) http.Handler {