git@cosmocr.at/gitweb.git @ main · summary refs log
main / internal / server / auth.go /
internal/server/auth.go · 2.3 KiB · raw
view: Source ☐ force
 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
}