coverage~bigbes/sr-ht-doltede3b0bbauthn/backend.go

Coverage
14.3% 1/7 statements
Δ
Blob
e38e20c
1 package authn
2
3 import (
4 "context"
5 "errors"
6 "strings"
7
8 "sourcecraft.dev/bigbes/sr-ht-core/auth"
9 )
10
11 // ErrInvalidToken is the sentinel wrapped by every *permanent* credential
12 // rejection: a malformed/expired/forged token, a username mismatch, or a
13 // revoked token. Callers (e.g. the remotesapi interceptors) map errors.Is(err,
14 // ErrInvalidToken) to an authentication failure (HTTP 401 / gRPC
15 // Unauthenticated). A resolution error that does NOT wrap ErrInvalidToken is a
16 // *transient* backend failure (meta.sr.ht unreachable, database error) and
17 // should be surfaced as "try again later" (HTTP 500 / gRPC Unavailable), never
18 // as a hard credential rejection — this mirrors core-go's auth.OAuth2, which
19 // distinguishes its temporary-error path from an invalid-token path.
20 var ErrInvalidToken = errors.New("authn: invalid or expired credentials")
21
22 // MetaBackend abstracts the two meta.sr.ht-backed lookups the resolvers need:
23 // mirroring a user into the local "user" table and checking whether a token has
24 // been revoked. The production implementation (coreMetaBackend) delegates to
25 // core-go, which reads the database and config from the request context; tests
26 // swap in an in-memory stub so they need neither Postgres nor the network.
27 type MetaBackend interface {
28 // LookupUser fills out with the user identified by username, mirroring the
29 // profile from meta.sr.ht into the local database on first sight. Mirrors
30 // core-go's auth.LookupUser semantics.
31 LookupUser(ctx context.Context, username string, out *auth.AuthContext) error
32 // IsRevoked reports whether the personal access token with the given sha512
33 // hash (and, for OAuth clients, clientID) has been revoked on meta.sr.ht.
34 // Mirrors core-go's auth.LookupTokenRevocation.
35 IsRevoked(ctx context.Context, username string, hash [64]byte, clientID string) (bool, error)
36 }
37
38 // coreMetaBackend is the production MetaBackend: it forwards to core-go, whose
39 // implementations read database.ForContext / config.ServiceName from ctx and
40 // (on a local miss) fetch the profile from meta.sr.ht over internal GraphQL.
41 type coreMetaBackend struct{}
42
43 0 func (coreMetaBackend) LookupUser(ctx context.Context, username string, out *auth.AuthContext) error {
44 0 return auth.LookupUser(ctx, username, out)
45 0 }
46
47 0 func (coreMetaBackend) IsRevoked(ctx context.Context, username string, hash [64]byte, clientID string) (bool, error) {
48 0 return auth.LookupTokenRevocation(ctx, username, hash, clientID)
49 0 }
50
51 // meta is the backend used by the resolution functions. It defaults to the real
52 // meta.sr.ht implementation; tests reassign it (white-box) and restore it.
53 var meta MetaBackend = coreMetaBackend{}
54
55 // SetMetaBackend swaps the package-level meta backend used by the resolvers and
56 // returns a function that restores the previous one. It is a wiring/test seam:
57 // integration tests living in OTHER packages (e.g. remoteapi) need to inject an
58 // in-memory MetaBackend so they can exercise the full auth stack without a live
59 // meta.sr.ht or the internal-network trust it requires. Production code never
60 // calls it, and it is not safe for concurrent use — a test installs a backend,
61 // runs, and restores it via the returned func (typically with t.Cleanup).
62 0 func SetMetaBackend(b MetaBackend) (restore func()) {
63 0 prev := meta
64 0 meta = b
65 0 return func() { meta = prev }
66 }
67
68 // equalUsername reports whether two SourceHut usernames refer to the same user,
69 // ignoring a leading "~" (the canonical-name sigil) and ASCII case.
70 21 func equalUsername(a, b string) bool {
71 21 return strings.EqualFold(strings.TrimPrefix(a, "~"), strings.TrimPrefix(b, "~"))
72 21 }