cli/auth: add CellClientFactory — one subject, one token per jurisdiction · Entire
cli/auth: add CellClientFactory — one subject, one token per jurisdiction
8f05352→main·
Soph·1w ago·2 files·+123 added/-10 removed
NewEntireAPICellClient resolved the stored login subject (discovery + login refresh) and ran the RFC 8693 exchange on every call. For a single-cell command that's fine, but a multi-cell fan-out (one request per cell hosting the caller's repos, the BFF's code-search pattern) would pay all of it once per cell — even though identity tokens are per-jurisdiction, not per-cell: every cell in a jurisdiction accepts the same token.
CellClientFactory resolves the subject once at construction and caches minted identity tokens by jurisdiction; ClientFor(target) reuses them across cells. NewEntireAPICellClient stays as the single-cell wrapper (factory of one), so existing callers are unchanged.
Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Sessions
935f91398e25View transcript
Changes
2
cmd/entire/cli/auth
Mcell_data_api.go+61/-10
Mcell_data_api_test.go+62
11 unmodified lines
12
13
14
15
16
17
18
79 unmodified lines
98
99
100
100
101
102
103
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
109
137
138
139
140
141
142
143
144
145
114
146
147
148
149
2 unmodified lines
152
153
154
123
155
156
157
158
1 unmodified line
160
161
162
131
132
163
164
134
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
11 unmodified lines
"os",
"regexp",
"strings",
"sync",
"time",
"github.com/entireio/cli/cmd/entire/cli/api";
79 unmodified lines
// - otherwise the data host is a BFF/apex: resolve the caller's home-cell
// apiUrl from the cluster catalog (home-jurisdiction fallback).
func NewEntireAPICellClient(ctx context.Context, insecureHTTP bool, target *CellTarget) (*api.Client, error) {
// NewEntireAPICellClient deliberately does NOT consult ENTIRE_TOKEN: it
// resolves the active stored login context (like every other cell/data-API
// command). Only `entire auth token --jurisdiction` (JurisdictionToken) adds
// the env-token path.
factory, err := NewEntireAPICellClientFactory(ctx, insecureHTTP)
if err != nil {
return nil, err
}
return factory.ClientFor(ctx, target)
}
// CellClientFactory builds entire-api cell clients from a single resolved
// exchange subject, minting at most one jurisdictional identity token per
// jurisdiction. Identity tokens are per-jurisdiction, not per-cell — every cell
// in a jurisdiction accepts the same token — so a caller dialing several cells
// in one operation (multi-cell fan-out over the caller's repos) should build
// one factory and reuse it for every cell, instead of paying discovery + login
// refresh + RFC 8693 exchange once per cell via NewEntireAPICellClient.
//
// A factory is safe for concurrent use, and holds credentials resolved at
// construction time — build it per operation, don't store it long-term. Like
// NewEntireAPICellClient it deliberately does NOT consult ENTIRE_TOKEN.
type CellClientFactory struct {
subject cellSubject
mu sync.Mutex
tokens map[string]string // jurisdiction -> minted identity token
}
// NewEntireAPICellClientFactory resolves the exchange subject (active stored
// login context) once, for building clients aimed at several cells. See
// NewEntireAPICellClient for the single-cell convenience wrapper.
func NewEntireAPICellClientFactory(ctx context.Context, insecureHTTP bool) (*CellClientFactory, error) {
subject, err := resolveStoredCellSubject(ctx, insecureHTTP)
if err != nil {
return nil, err
}
return &CellClientFactory{subject: subject, tokens: make(map[string]string)}, nil
}
jurisdiction, err := targetJurisdiction(target, subject.loginJWT)
// ClientFor returns an authenticated client for the given cell target (nil
// falls back to home-jurisdiction routing), reusing an already-minted identity
// token when the target's jurisdiction matches an earlier call.
func (f *CellClientFactory) ClientFor(ctx context.Context, target *CellTarget) (*api.Client, error) {
jurisdiction, err := targetJurisdiction(target, f.subject.loginJWT)
if err != nil {
return nil, err
}
coreURL := jurisdictionCoreURL(jurisdiction, subject.dataOrigin, subject.discoveredCore)
coreURL := jurisdictionCoreURL(jurisdiction, f.subject.dataOrigin, f.subject.discoveredCore)
if err := requireSafeExchangeURL("entire-core", coreURL); err != nil {
return nil, err
}
// which is signed by the discovered login core — so list there, not at the
// templated jurisdiction core (coreURL), which in a multi-core setup could
// differ and reject the token. coreURL still governs the token exchange below.
cellBaseURL, err := resolveTargetCellBaseURL(ctx, target, subject.dataOrigin, jurisdiction, subject.discoveredCore, subject.loginJWT, subject.httpClient)
cellBaseURL, err := resolveTargetCellBaseURL(ctx, target, f.subject.dataOrigin, jurisdiction, f.subject.discoveredCore, f.subject.loginJWT, f.subject.httpClient)
if err != nil {
return nil, err
}
return nil, err
}
audience := jurisdictionAudience(jurisdiction, subject.dataOrigin, subject.discoveredCore)
token, err := exchangeJurisdictionToken(ctx, coreURL, subject.loginJWT, audience, subject.httpClient)
token, err := f.tokenFor(ctx, jurisdiction, coreURL)
if err != nil {
return nil, fmt.Errorf("exchange jurisdictional identity token: %w", err)
return nil, err
}
return api.NewClientWithBaseURL(token, cellBaseURL), nil
}
// tokenFor returns the cached identity token for jurisdiction, minting it on
// first use. The mutex is held across the mint: concurrent callers for the
// same jurisdiction wait for one exchange instead of duplicating it, at the
// cost of serializing cross-jurisdiction mints (fine for the handful of
// jurisdictions a fan-out touches).
func (f *CellClientFactory) tokenFor(ctx context.Context, jurisdiction, coreURL string) (string, error) {
f.mu.Lock()
defer f.mu.Unlock()
if token, ok := f.tokens[jurisdiction]; ok {
return token, nil
}
audience := jurisdictionAudience(jurisdiction, f.subject.dataOrigin, f.subject.discoveredCore)
token, err := exchangeJurisdictionToken(ctx, coreURL, f.subject.loginJWT, audience, f.subject.httpClient)
if err != nil {
return "", fmt.Errorf("exchange jurisdictional identity token: %w", err)
}
f.tokens[jurisdiction] = token
return token, nil
}
// JurisdictionToken mints and returns a jurisdictional identity token
// (scope=openid, aud=jurisdiction host) for `jurisdiction`, for authenticating
// against that jurisdiction's entire-api cells (e.g.