// Package authn resolves the SourceHut caller for a dolt.sr.ht request across // the three authentication flows the service accepts: // // - the unified-login cookie (web UI), via OptionalCookieMiddleware; // - a meta.sr.ht personal access token (dolt clone/push --user + Basic auth), // via ResolveBasic; // - a dolt Ed25519 keypair (dolt login / Bearer EdDSA JWT), via ResolveDoltJWT. // // It reuses core-go's token/cookie primitives (auth.DecodeBearerToken, // auth.LookupUser, auth.LookupTokenRevocation, crypto.DecryptWithoutExpiration) // and dolt's creds.PubKeyToKIDStr for key-id derivation, and produces // *auth.AuthContext values that AsCoreCaller maps onto the pure core.Caller // domain type for the access-control matrix in package core. // // Production wiring: the cookie middleware and the resolvers read the meta.sr.ht // user database and config from the request context, so the caller must install // config.Middleware and database.Middleware upstream (see cmd/doltsrht). The // meta-lookup and token-revocation calls are funnelled through the package-level // MetaBackend so tests can stub them without a database or network. package authn import ( "context" "git.sr.ht/~sircmpwn/core-go/auth" "go.bigb.es/sourcehut-dolt/core" ) // AuthMethodDoltKey labels an AuthContext resolved from a dolt Ed25519 keypair // JWT. core-go has no such method; we never call auth.AuthContext.Access (which // would panic on an unknown method) — access is decided by core.Allowed — so a // private label is safe and keeps dolt-key auth distinguishable from OAuth2. const AuthMethodDoltKey = "DOLT_KEY" type contextKey struct{ name string } var callerCtxKey = &contextKey{"authn.caller"} // WithCaller returns a copy of ctx carrying the resolved caller. A nil ac is // stored as-is and reads back as anonymous via CallerFromContext. func WithCaller(ctx context.Context, ac *auth.AuthContext) context.Context { return context.WithValue(ctx, callerCtxKey, ac) } // CallerFromContext returns the caller stored by WithCaller, or nil for an // anonymous request. Unlike core-go's auth.ForContext it never panics on a // missing value: an unauthenticated request is a normal, expected state for // dolt.sr.ht (public browsing and public clones). func CallerFromContext(ctx context.Context) *auth.AuthContext { ac, _ := ctx.Value(callerCtxKey).(*auth.AuthContext) return ac } // AsCoreCaller maps a resolved *auth.AuthContext onto the pure core.Caller // domain type consumed by core.Allowed. A nil ac (anonymous) maps to a nil // caller. Suspended is derived from the meta UserType, which independently // gates every write operation. func AsCoreCaller(ac *auth.AuthContext) *core.Caller { if ac == nil { return nil } return &core.Caller{ UserID: ac.UserID, Username: ac.Username, UserType: core.UserType(ac.UserType), Suspended: ac.UserType == auth.USER_TYPE_SUSPENDED, } }