~bigbes/sr-ht-compare

ref: 9473e2606cd90cb8d3b78ee87511c671157439f4 sr-ht-compare/authz/identity.go -rw-r--r-- 2.0 KiB
9473e260 — bigbes gitx: repository access and ref-to-ref diffs on go-git a month ago
                                                                                
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
package authz

import (
	"context"
	"encoding/json"
	"net/http"
	"strings"

	"git.sr.ht/~sircmpwn/core-go/crypto"
)

// CookieName is the SourceHut unified-login session cookie. Its value is a
// Fernet token encrypted with the instance [sr.ht] network-key.
const CookieName = "sr.ht.unified-login.v1"

// UsernameFromRequest extracts the authenticated username from the unified-login
// cookie, or returns "" for an anonymous viewer. Any failure — missing cookie,
// undecryptable token, malformed JSON — is treated as anonymous rather than an
// error: this service never rejects a request on identity grounds, it only lets
// git.sr.ht decide what an anonymous viewer may see.
func UsernameFromRequest(r *http.Request) string {
	c, err := r.Cookie(CookieName)
	if err != nil {
		return ""
	}
	// InitCrypto must have run (server.New does it); the network-key here is
	// the same Fernet key meta.sr.ht used to seal the cookie.
	payload := crypto.DecryptWithoutExpiration([]byte(c.Value))
	if payload == nil {
		return ""
	}
	var claims struct {
		Name string `json:"name"`
	}
	if err := json.Unmarshal(payload, &claims); err != nil {
		return ""
	}
	// Cookies carry the bare username; strip a leading "~" defensively in case
	// a caller stored the canonical "~user" form.
	return strings.TrimPrefix(claims.Name, "~")
}

type ctxKey int

const usernameKey ctxKey = iota

// Middleware stores the cookie-derived username in the request context. It
// never writes a 401: an anonymous viewer flows through with an empty username
// and git.sr.ht enforces visibility downstream.
func Middleware() func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ctx := context.WithValue(r.Context(), usernameKey, UsernameFromRequest(r))
			next.ServeHTTP(w, r.WithContext(ctx))
		})
	}
}

// ForContext returns the username stored by Middleware, or "" if absent.
func ForContext(ctx context.Context) string {
	username, _ := ctx.Value(usernameKey).(string)
	return username
}