Initial scaffold: navi-backend + navi-traffic (extraction #1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Johnson 2026-05-21 22:11:10 -06:00
commit dfd1d38405
15 changed files with 491 additions and 0 deletions

View file

View file

@ -0,0 +1,76 @@
"""Helpers for the uniform ``/api/admin/<service>/info`` endpoint (handoff §4.5).
Every navi-backend service exposes one admin-info endpoint with the same shape so
a future admin panel can fan out to all of them. These helpers keep each
service's handler down to a few lines.
"""
import time
import requests
def mask_key(value):
"""Mask a secret for display, matching recon's ``api_keys_admin._mask_key``.
Pattern: ``first4 + '...' + last4`` (e.g. ``"tk_1...CDEF"``). Values of 8
chars or fewer are fully masked as ``'****'`` so short strings don't reveal
their endpoints. Returns ``None`` for empty/None input.
"""
if not value:
return None
if len(value) <= 8:
return '****'
return value[:4] + '...' + value[-4:]
def time_dependency(name, url, method='HEAD', timeout=5):
"""Health-check an upstream dependency.
Returns ``{name, status, latency_ms}`` (plus ``error`` on failure), where
``status`` is ``"ok"`` for any response below HTTP 500, else ``"error"``.
"""
start = time.monotonic()
try:
resp = requests.request(method, url, timeout=timeout)
latency_ms = round((time.monotonic() - start) * 1000, 1)
if resp.status_code < 500:
return {'name': name, 'status': 'ok', 'latency_ms': latency_ms}
return {
'name': name,
'status': 'error',
'latency_ms': latency_ms,
'error': f'HTTP {resp.status_code}',
}
except Exception as exc:
latency_ms = round((time.monotonic() - start) * 1000, 1)
return {
'name': name,
'status': 'error',
'latency_ms': latency_ms,
'error': str(exc),
}
def build_info_response(service, version, port, config, env, dependencies,
filesystem, runtime):
"""Assemble the uniform admin-info dict (handoff §4.5).
- ``service`` short name, e.g. ``"navi-traffic"``
- ``version`` git SHA (or semver if tagged)
- ``port`` listening port
- ``config`` loaded config dict, secrets masked
- ``env`` list of ``{name, value}`` with values masked via mask_key
- ``dependencies`` list of time_dependency() results
- ``filesystem`` list of /mnt/nav paths with existence + read checks
- ``runtime`` ``{uptime_s, request_count, last_error_at}``
"""
return {
'service': service,
'version': version,
'port': port,
'config': config,
'env': env,
'dependencies': dependencies,
'filesystem': filesystem,
'runtime': runtime,
}

29
backend/shared/auth.py Normal file
View file

@ -0,0 +1,29 @@
"""Authentication helpers shared across every navi-backend service.
Auth is enforced at the Caddy/Authentik edge (forward_auth). By the time a
request reaches a service it carries an ``X-Authentik-Username`` header iff the
user is authenticated. These helpers read that header they do not perform
auth themselves, they assert that the edge already did.
"""
from functools import wraps
from flask import request, jsonify
def get_user_id(req):
"""Return the Authentik-supplied username for a request, or None if absent."""
return req.headers.get('X-Authentik-Username')
def require_auth(fn):
"""Reject requests with no ``X-Authentik-Username`` header (401 JSON).
Use on endpoints that must never be reachable unauthenticated even if the
edge is misconfigured or bypassed (e.g. the internal nginx :8440 path).
"""
@wraps(fn)
def wrapper(*args, **kwargs):
if not get_user_id(request):
return jsonify({'error': 'authentication required'}), 401
return fn(*args, **kwargs)
return wrapper