package server import ( "net/http" "strings" "gitweb/internal/auth" "gitweb/internal/store" ) // authenticate identifies the caller from HTTP Basic auth. It returns the // verified upspin user name, or "" for an anonymous request (no credentials). // // The second result is false when credentials were supplied but invalid; in // that case a 401 challenge has already been written and the caller must stop. // // The Basic-auth password is either a vault-issued SSO bearer token (from the // passkey login flow) or a personal access token. Vault tokens are self- // describing, so the username field is ignored for them; for PATs the username // is the upspin user whose token file is consulted. func (s *Server) authenticate(w http.ResponseWriter, r *http.Request) (string, bool) { // Browser session: a gitweb_session cookie carries an issuer-signed SSO // token from the passkey login flow. It is verified statelessly (same as a // Basic-auth token), so a browser never needs to send Basic credentials. An // expired or tampered cookie is treated as anonymous — never a hard failure — // and cleared, so the next request re-prompts login rather than 401-looping. if s.vault != nil { if c, err := r.Cookie(sessionCookie); err == nil && c.Value != "" { if sub, err := s.vault.Verify(c.Value); err == nil { return sub, true } clearCookie(w, sessionCookie) } } user, pass, ok := r.BasicAuth() if !ok || pass == "" { return "", true // anonymous } if s.vault != nil && auth.LooksLikeVaultToken(pass) { sub, err := s.vault.Verify(pass) if err != nil { s.challenge(w) return "", false } return sub, true } // Personal access token: the username names the token file's owner. if !store.ValidUser(user) { s.challenge(w) return "", false } valid, err := s.pat.Verify(user, pass) if err != nil { s.serverErr(w, err) return "", false } if !valid { s.challenge(w) return "", false } return user, true } // parseUpspinPath splits a URL path "/user@domain/rest..." into the upspin user // and a cleaned remainder beginning with "/". ok is false if the user element // is missing or malformed. func parseUpspinPath(urlPath string) (user, clean string, ok bool) { raw := strings.TrimPrefix(urlPath, "/") user, rest, _ := strings.Cut(raw, "/") if !store.ValidUser(user) { return "", "", false } return user, cleanRest(rest), true }