Accept managed collector service tokens on the collector API
A token scoped to the new collector.ingest permission is a collector service token: the collector endpoints accept it via X-API-Key or Bearer alongside the env fleet keys (which remain the fallback), giving the fleet credential rotation, revocation, and last-used visibility from the API Tokens page. Containment holds both ways: a collector token authorizes nothing else, and no other credential gains collector access. Shared token validation refactored out of the auth shim; a Collector service token quick-preset in the create modal; integration guide documents minting, rotation via site-config.json, and the service-identity pattern. 765 tests pass; live acceptance matrix verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,8 +47,13 @@ def _extract_pat_secret():
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_pat(secret):
|
||||
"""Validate a PAT secret. Return (token, user) or None."""
|
||||
def resolve_api_token(secret):
|
||||
"""Validate a PAT secret. Return (token, user) or None.
|
||||
|
||||
Shared validator: hash lookup, active token, unexpired, active owner. The
|
||||
before_request shim and the collector API (which does not decode JWT) both
|
||||
call this so the checks live in one place.
|
||||
"""
|
||||
from shopdb.core.models import User
|
||||
|
||||
token = ApiToken.query.filter_by(
|
||||
@@ -61,7 +66,7 @@ def _resolve_pat(secret):
|
||||
return token, user
|
||||
|
||||
|
||||
def _touch_lastused(token):
|
||||
def touch_apitoken_lastused(token):
|
||||
"""Throttled lastusedat write. Independent commit; nothing else is pending
|
||||
this early in the request, so it cannot clobber route work."""
|
||||
now = _utcnow()
|
||||
@@ -79,7 +84,7 @@ def install_apitoken_auth(app):
|
||||
secret = _extract_pat_secret()
|
||||
if secret is None:
|
||||
return
|
||||
resolved = _resolve_pat(secret)
|
||||
resolved = resolve_api_token(secret)
|
||||
if resolved is None:
|
||||
# The caller clearly meant to use a PAT (shopdb_pat_ prefix) but it
|
||||
# is unknown, revoked, or expired. Reject with a clear 401 instead
|
||||
@@ -105,7 +110,7 @@ def install_apitoken_auth(app):
|
||||
g.apitokenid = token.tokenid
|
||||
g.apitokenuser = user
|
||||
|
||||
_touch_lastused(token)
|
||||
touch_apitoken_lastused(token)
|
||||
|
||||
# Mint a request-scoped JWT for the owner and swap it into the header
|
||||
# so the whole downstream auth stack authenticates as that user.
|
||||
|
||||
Reference in New Issue
Block a user