1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
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
}
|