A community based topic aggregation platform built on atproto
1package auth
2
3import (
4 "context"
5 "fmt"
6 "strings"
7
8 indigoIdentity "github.com/bluesky-social/indigo/atproto/identity"
9)
10
11// CombinedKeyFetcher handles JWT public key fetching for both:
12// - DID issuers (did:plc:, did:web:) → resolves via DID document
13// - URL issuers (https://) → fetches via JWKS endpoint (legacy/fallback)
14//
15// For atproto service authentication, the issuer is typically the user's DID,
16// and the signing key is published in their DID document.
17type CombinedKeyFetcher struct {
18 didFetcher *DIDKeyFetcher
19 jwksFetcher JWKSFetcher
20}
21
22// NewCombinedKeyFetcher creates a key fetcher that supports both DID and URL issuers.
23// Parameters:
24// - directory: Indigo's identity directory for DID resolution
25// - jwksFetcher: fallback JWKS fetcher for URL issuers (can be nil if not needed)
26func NewCombinedKeyFetcher(directory indigoIdentity.Directory, jwksFetcher JWKSFetcher) *CombinedKeyFetcher {
27 return &CombinedKeyFetcher{
28 didFetcher: NewDIDKeyFetcher(directory),
29 jwksFetcher: jwksFetcher,
30 }
31}
32
33// FetchPublicKey fetches the public key for verifying a JWT.
34// Routes to the appropriate fetcher based on issuer format:
35// - DID (did:plc:, did:web:) → DIDKeyFetcher
36// - URL (https://) → JWKSFetcher
37func (f *CombinedKeyFetcher) FetchPublicKey(ctx context.Context, issuer, token string) (interface{}, error) {
38 // Check if issuer is a DID
39 if strings.HasPrefix(issuer, "did:") {
40 return f.didFetcher.FetchPublicKey(ctx, issuer, token)
41 }
42
43 // Check if issuer is a URL (https:// or http:// in dev)
44 if strings.HasPrefix(issuer, "https://") || strings.HasPrefix(issuer, "http://") {
45 if f.jwksFetcher == nil {
46 return nil, fmt.Errorf("URL issuer %s requires JWKS fetcher, but none configured", issuer)
47 }
48 return f.jwksFetcher.FetchPublicKey(ctx, issuer, token)
49 }
50
51 return nil, fmt.Errorf("unsupported issuer format: %s (expected DID or URL)", issuer)
52}