Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:37:31

0001 """Per-user bearer tokens for headless clients (docs/live-data-access.md).
0002 
0003 A token is ``swfr_`` followed by 43 URL-safe characters. Only its SHA-256
0004 is stored; the plaintext is shown once, on the account tokens page.
0005 ``TokenAuthMiddleware`` turns a valid ``Authorization: Bearer swfr_...``
0006 header into the token's user before the login wall runs, so a token caller
0007 passes the wall and reaches swf-monitor as that user through X-Remote-User.
0008 Tokens authenticate the MCP relay, TeamComms and stage-out ingest. The token
0009 itself never crosses the tunnel.
0010 """
0011 
0012 import hashlib
0013 import secrets
0014 from datetime import timedelta
0015 
0016 from django.http import JsonResponse
0017 from django.utils import timezone
0018 
0019 TOKEN_PREFIX = 'swfr_'
0020 # path_info prefixes where a token authenticates: the MCP relay, and the
0021 # stage-out sweep ingest, whose caller is the production sweeper rather
0022 # than a person (remote_app/views.py, stageout_sweep_pass).
0023 TOKEN_PATHS = ('/mcp/', '/api/stageout/', '/teamcomms/')
0024 LAST_USED_GRANULARITY = timedelta(minutes=1)
0025 
0026 
0027 def _hash(raw):
0028     return hashlib.sha256(raw.encode()).hexdigest()
0029 
0030 
0031 def issue_token(user, label='', *, teamcomms_ai=False, teamcomms_service_kind=''):
0032     """Create a token for ``user``; returns (plaintext, ApiToken)."""
0033     from .models import ApiToken
0034     if teamcomms_service_kind not in {'', 'program', 'connector'} or (teamcomms_ai and teamcomms_service_kind):
0035         raise ValueError('Select an AI client or a service identity, not both')
0036     raw = TOKEN_PREFIX + secrets.token_urlsafe(32)
0037     token = ApiToken.objects.create(
0038         user=user, label=(label or '')[:100],
0039         prefix=raw[len(TOKEN_PREFIX):len(TOKEN_PREFIX) + 8],
0040         key_hash=_hash(raw),
0041         teamcomms_ai=teamcomms_ai,
0042         teamcomms_service_kind=teamcomms_service_kind,
0043     )
0044     return raw, token
0045 
0046 
0047 def resolve_token(raw):
0048     """The active user behind a plaintext token, else None."""
0049     from .models import ApiToken
0050     if not raw.startswith(TOKEN_PREFIX):
0051         return None
0052     token = (ApiToken.objects.select_related('user')
0053              .filter(key_hash=_hash(raw), revoked__isnull=True).first())
0054     if token is None or not token.user.is_active:
0055         return None
0056     now = timezone.now()
0057     if token.last_used is None or now - token.last_used > LAST_USED_GRANULARITY:
0058         ApiToken.objects.filter(pk=token.pk).update(last_used=now)
0059     return token.user
0060 
0061 
0062 class TokenAuthMiddleware:
0063     """Authenticate ``Authorization: Bearer swfr_...`` as the token's user.
0064 
0065     Placed after AuthenticationMiddleware and before the login wall, and
0066     active only under TOKEN_PATHS. A bearer of another form is left alone
0067     for the upstream to judge; an swf-remote token that is unknown or
0068     revoked is refused here with 401.
0069     """
0070 
0071     def __init__(self, get_response):
0072         self.get_response = get_response
0073 
0074     def __call__(self, request):
0075         auth = request.META.get('HTTP_AUTHORIZATION', '')
0076         if (auth.startswith('Bearer ' + TOKEN_PREFIX)
0077                 and request.path_info.startswith(TOKEN_PATHS)):
0078             user = resolve_token(auth[7:].strip())
0079             if user is None:
0080                 return JsonResponse({'error': 'invalid or revoked token'}, status=401)
0081             request.user = user
0082             request.token_auth = True
0083         return self.get_response(request)