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 }