~bigbes/sr-ht-dolt

ref: 9660c7204a2b9500e56c7bb2ff47316d50d5fc99 sr-ht-dolt/authn/backend.go -rw-r--r-- 3.5 KiB
9660c720 — Eugene Blikh pages: read forms through FormValues 9 days 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
64
65
66
67
68
69
70
71
72
package authn

import (
	"context"
	"errors"
	"strings"

	"sourcecraft.dev/bigbes/sr-ht-core/auth"
)

// ErrInvalidToken is the sentinel wrapped by every *permanent* credential
// rejection: a malformed/expired/forged token, a username mismatch, or a
// revoked token. Callers (e.g. the remotesapi interceptors) map errors.Is(err,
// ErrInvalidToken) to an authentication failure (HTTP 401 / gRPC
// Unauthenticated). A resolution error that does NOT wrap ErrInvalidToken is a
// *transient* backend failure (meta.sr.ht unreachable, database error) and
// should be surfaced as "try again later" (HTTP 500 / gRPC Unavailable), never
// as a hard credential rejection — this mirrors core-go's auth.OAuth2, which
// distinguishes its temporary-error path from an invalid-token path.
var ErrInvalidToken = errors.New("authn: invalid or expired credentials")

// MetaBackend abstracts the two meta.sr.ht-backed lookups the resolvers need:
// mirroring a user into the local "user" table and checking whether a token has
// been revoked. The production implementation (coreMetaBackend) delegates to
// core-go, which reads the database and config from the request context; tests
// swap in an in-memory stub so they need neither Postgres nor the network.
type MetaBackend interface {
	// LookupUser fills out with the user identified by username, mirroring the
	// profile from meta.sr.ht into the local database on first sight. Mirrors
	// core-go's auth.LookupUser semantics.
	LookupUser(ctx context.Context, username string, out *auth.AuthContext) error
	// IsRevoked reports whether the personal access token with the given sha512
	// hash (and, for OAuth clients, clientID) has been revoked on meta.sr.ht.
	// Mirrors core-go's auth.LookupTokenRevocation.
	IsRevoked(ctx context.Context, username string, hash [64]byte, clientID string) (bool, error)
}

// coreMetaBackend is the production MetaBackend: it forwards to core-go, whose
// implementations read database.ForContext / config.ServiceName from ctx and
// (on a local miss) fetch the profile from meta.sr.ht over internal GraphQL.
type coreMetaBackend struct{}

func (coreMetaBackend) LookupUser(ctx context.Context, username string, out *auth.AuthContext) error {
	return auth.LookupUser(ctx, username, out)
}

func (coreMetaBackend) IsRevoked(ctx context.Context, username string, hash [64]byte, clientID string) (bool, error) {
	return auth.LookupTokenRevocation(ctx, username, hash, clientID)
}

// meta is the backend used by the resolution functions. It defaults to the real
// meta.sr.ht implementation; tests reassign it (white-box) and restore it.
var meta MetaBackend = coreMetaBackend{}

// SetMetaBackend swaps the package-level meta backend used by the resolvers and
// returns a function that restores the previous one. It is a wiring/test seam:
// integration tests living in OTHER packages (e.g. remoteapi) need to inject an
// in-memory MetaBackend so they can exercise the full auth stack without a live
// meta.sr.ht or the internal-network trust it requires. Production code never
// calls it, and it is not safe for concurrent use — a test installs a backend,
// runs, and restores it via the returned func (typically with t.Cleanup).
func SetMetaBackend(b MetaBackend) (restore func()) {
	prev := meta
	meta = b
	return func() { meta = prev }
}

// equalUsername reports whether two SourceHut usernames refer to the same user,
// ignoring a leading "~" (the canonical-name sigil) and ASCII case.
func equalUsername(a, b string) bool {
	return strings.EqualFold(strings.TrimPrefix(a, "~"), strings.TrimPrefix(b, "~"))
}