~bigbes/sr-ht-spec

ref: f8a7a1743ad9704ff46f9e9319f8cc22b75bbdef sr-ht-spec/service/token.go -rw-r--r-- 2.9 KiB
f8a7a174 — Eugene Blikh feat(graph): wire the proposals read to service.ListProposals (Phase 3) 26 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
73
74
75
package service

import (
	"context"
	"errors"
	"fmt"

	"sourcecraft.dev/bigbes/sr-ht-spec/authn"
	"sourcecraft.dev/bigbes/sr-ht-spec/db"
)

// AgentTokenLookup is the one db/ method the token adapter needs. It is an
// interface rather than a *db.Store so the error mapping below — the part that
// actually carries a contract — can be tested against a fake instead of a
// Postgres instance. *db.Store satisfies it.
type AgentTokenLookup interface {
	AgentTokenByHash(ctx context.Context, hash []byte) (*db.AgentToken, error)
}

// TokenStore adapts db/'s agent_token queries to authn.TokenStore. It exists
// because authn must not import db: authn declares the sliver of persistence it
// needs, and service/ — the layer that is allowed to know about both — wires
// them together.
//
// The whole of the adaptation is the error contract, and it is not cosmetic.
// authn's contract is that "no such token" is an error satisfying
// errors.Is(err, authn.ErrUnknownToken) and that everything else is transient.
// db/ spells the same condition ErrNotFound, which authn has never heard of, so
// an unmapped pass-through would make an unknown token look like a Postgres
// outage: a 503 telling an agent to retry a credential that will never work.
type TokenStore struct {
	lookup AgentTokenLookup
}

// NewTokenStore wires a db.Store in as authn's TokenStore.
func NewTokenStore(lookup AgentTokenLookup) *TokenStore {
	return &TokenStore{lookup: lookup}
}

// TokenStore returns the adapter the resolver authenticates agents through.
func (s *Service) TokenStore() *TokenStore { return s.tokens }

// LookupAgentToken implements authn.TokenStore.
//
// A revoked row is returned rather than refused: authn is what turns it into a
// refusal, so the refusal can say "revoked" instead of "unknown" and an
// operator can tell a token they deliberately killed from one that never
// existed. Any other failure is returned wrapped and unclassified, which authn
// reads as transient — the fail-closed direction, since a store outage must
// never read as a valid credential.
func (t *TokenStore) LookupAgentToken(ctx context.Context, hash []byte) (authn.AgentToken, error) {
	if t.lookup == nil {
		return authn.AgentToken{}, errors.New("service: TokenStore has no backing store")
	}
	row, err := t.lookup.AgentTokenByHash(ctx, hash)
	if err != nil {
		if errors.Is(err, db.ErrNotFound) {
			return authn.AgentToken{}, fmt.Errorf("%w: no agent_token row matches the presented token",
				authn.ErrUnknownToken)
		}
		return authn.AgentToken{}, fmt.Errorf("service: look up agent token: %w", err)
	}
	if row == nil {
		// db/ never returns (nil, nil); a store that did would otherwise
		// authenticate a nil row as a valid token.
		return authn.AgentToken{}, errors.New("service: agent token lookup returned no row and no error")
	}
	return authn.AgentToken{
		ID:      int64(row.ID),
		Name:    row.Name,
		Hash:    row.Hash,
		Created: row.Created,
		Revoked: row.Revoked,
	}, nil
}