resolveAgentAccessToken verifies an e2a-minted access_token JWT and resolves it to an agent-scoped principal. Returns (nil, false, nil) when the bearer is not one of our JWTs (so the caller can fall through to API-key/OAuth paths); returns an error only when it IS our JWT but fails verification or l
(r *http.Request, bearer string)
| 157 | // not one of our JWTs (so the caller can fall through to API-key/OAuth paths); |
| 158 | // returns an error only when it IS our JWT but fails verification or lookup. |
| 159 | func (a *API) resolveAgentAccessToken(r *http.Request, bearer string) (*identity.Principal, bool, error) { |
| 160 | if !a.agentAuthReady() || !looksLikeJWT(bearer) { |
| 161 | return nil, false, nil |
| 162 | } |
| 163 | claims, err := a.signer.VerifyToken(bearer, agentauth.TypAccessToken, a.agentAuthIssuer()) |
| 164 | if err != nil { |
| 165 | // It parses as a JWT but isn't a valid e2a access token — reject |
| 166 | // rather than fall through (a tampered/expired token is a 401, not an |
| 167 | // API-key probe). |
| 168 | return nil, true, errors.New("invalid agent access token") |
| 169 | } |
| 170 | ag, err := a.store.GetAgentByID(r.Context(), claims.Subject) |
| 171 | if err != nil || ag == nil { |
| 172 | return nil, true, errors.New("agent not found for access token") |
| 173 | } |
| 174 | // Kill switch, re-checked per request (the agent row is already loaded, so |
| 175 | // this is free): a bumped assertion_version invalidates outstanding access |
| 176 | // tokens immediately rather than only starving new mints — revocation is |
| 177 | // instant, not bounded by the 15-min token TTL. |
| 178 | if ag.AssertionVersion != claims.AssertionVersion { |
| 179 | return nil, true, errors.New("access token revoked (stale assertion_version)") |
| 180 | } |
| 181 | user, err := a.store.GetUserByID(r.Context(), ag.UserID) |
| 182 | if err != nil || user == nil { |
| 183 | return nil, true, errors.New("owner not found for access token") |
| 184 | } |
| 185 | return &identity.Principal{User: user, Scope: identity.ScopeAgent, AgentID: ag.ID}, true, nil |
| 186 | } |
| 187 | |
| 188 | // looksLikeJWT is a cheap pre-filter: a compact JWS is three base64url segments |
| 189 | // separated by dots and (for our RS256 tokens) starts with the "eyJ" header. |
no test coverage detected