| 1 |
|
// Package authn resolves the SourceHut caller for a dolt.sr.ht request across |
| 2 |
|
// the four authentication flows the service accepts: |
| 3 |
|
// |
| 4 |
|
// - the unified-login cookie (web UI), via OptionalCookieMiddleware; |
| 5 |
|
// - a meta.sr.ht personal access token (dolt clone/push --user + Basic auth), |
| 6 |
|
// via ResolveBasic; |
| 7 |
|
// - a dolt Ed25519 keypair (dolt login / Bearer EdDSA JWT), via ResolveDoltJWT; |
| 8 |
|
// - an Authorization: Bearer credential on the /mcp surface, via ResolveBearer: |
| 9 |
|
// either a tokens.sr.ht working token, verified through sr-ht-ecore's shared |
| 10 |
|
// validator and scoped by core.GrantRead, or a meta PAT presented without a |
| 11 |
|
// username (bearer.go, docs/DESIGN.mcp.md §4). |
| 12 |
|
// |
| 13 |
|
// It reuses core-go's token/cookie primitives (auth.DecodeBearerToken, |
| 14 |
|
// auth.LookupUser, auth.LookupTokenRevocation, crypto.DecryptWithoutExpiration) |
| 15 |
|
// and dolt's creds.PubKeyToKIDStr for key-id derivation, and produces |
| 16 |
|
// *auth.AuthContext values that AsCoreCaller maps onto the pure core.Caller |
| 17 |
|
// domain type for the access-control matrix in package core. |
| 18 |
|
// |
| 19 |
|
// Production wiring: the cookie middleware and the resolvers read the meta.sr.ht |
| 20 |
|
// user database and config from the request context, so the caller must install |
| 21 |
|
// config.Middleware and database.Middleware upstream (see cmd/doltsrht). The |
| 22 |
|
// meta-lookup and token-revocation calls are funnelled through the package-level |
| 23 |
|
// MetaBackend so tests can stub them without a database or network. |
| 24 |
|
package authn |
| 25 |
|
|
| 26 |
|
import ( |
| 27 |
|
"context" |
| 28 |
|
|
| 29 |
|
"sourcecraft.dev/bigbes/sr-ht-core/auth" |
| 30 |
|
|
| 31 |
|
"sourcecraft.dev/bigbes/sr-ht-dolt/core" |
| 32 |
|
) |
| 33 |
|
|
| 34 |
|
// AuthMethodDoltKey labels an AuthContext resolved from a dolt Ed25519 keypair |
| 35 |
|
// JWT. core-go has no such method; we never call auth.AuthContext.Access (which |
| 36 |
|
// would panic on an unknown method) — access is decided by core.Allowed — so a |
| 37 |
|
// private label is safe and keeps dolt-key auth distinguishable from OAuth2. |
| 38 |
|
const AuthMethodDoltKey = "DOLT_KEY" |
| 39 |
|
|
| 40 |
|
type contextKey struct{ name string } |
| 41 |
|
|
| 42 |
|
var callerCtxKey = &contextKey{"authn.caller"} |
| 43 |
|
|
| 44 |
|
// WithCaller returns a copy of ctx carrying the resolved caller. A nil ac is |
| 45 |
|
// stored as-is and reads back as anonymous via CallerFromContext. |
| 46 |
4 |
func WithCaller(ctx context.Context, ac *auth.AuthContext) context.Context { |
| 47 |
4 |
return context.WithValue(ctx, callerCtxKey, ac) |
| 48 |
4 |
} |
| 49 |
|
|
| 50 |
|
// CallerFromContext returns the caller stored by WithCaller, or nil for an |
| 51 |
|
// anonymous request. Unlike core-go's auth.ForContext it never panics on a |
| 52 |
|
// missing value: an unauthenticated request is a normal, expected state for |
| 53 |
|
// dolt.sr.ht (public browsing and public clones). |
| 54 |
12 |
func CallerFromContext(ctx context.Context) *auth.AuthContext { |
| 55 |
12 |
ac, _ := ctx.Value(callerCtxKey).(*auth.AuthContext) |
| 56 |
12 |
return ac |
| 57 |
12 |
} |
| 58 |
|
|
| 59 |
|
// AsCoreCaller maps a resolved *auth.AuthContext onto the pure core.Caller |
| 60 |
|
// domain type consumed by core.Allowed. A nil ac (anonymous) maps to a nil |
| 61 |
|
// caller. Suspended is derived from the meta UserType, which independently |
| 62 |
|
// gates every write operation. |
| 63 |
6 |
func AsCoreCaller(ac *auth.AuthContext) *core.Caller { |
| 64 |
6 |
if ac == nil { |
| 65 |
1 |
return nil |
| 66 |
1 |
} |
| 67 |
5 |
return &core.Caller{ |
| 68 |
5 |
UserID: ac.UserID, |
| 69 |
5 |
Username: ac.Username, |
| 70 |
5 |
UserType: core.UserType(ac.UserType), |
| 71 |
5 |
Suspended: ac.UserType == auth.USER_TYPE_SUSPENDED, |
| 72 |
5 |
} |
| 73 |
|
} |