mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
New services/navi_contacts/ on :8423 — two blueprints, behavior-identical
ports of recon's contacts + address_book code.
Routes:
contacts (10, all @require_auth, per-user via X-Authentik-Username):
GET/POST /api/contacts; GET /api/contacts/nearby; GET /api/contacts/deleted;
GET/PATCH/DELETE /api/contacts/<id>;
POST /api/contacts/<id>/restore; POST /api/contacts/<id>/restore-as;
DELETE /api/contacts/<id>/purge
address_book (2, public):
GET /api/address_book/lookup?q= ; GET /api/address_book/list
Data ownership (per Matt's rule — DBs live in navi-backend territory,
auto-create on first run; only massive tilesets stay external):
- contacts.db: env NAVI_CONTACTS_DB (default /var/lib/navi-backend/contacts.db).
ContactsDB auto-creates the schema (table + 5 indexes incl. the partial-
unique Home/Work index) on first open via CREATE ... IF NOT EXISTS — this
is recon's own behavior, ported verbatim. WAL + busy_timeout=5000 preserved.
- address_book.yaml: vendored into config/address_book.yaml (read-only, like
the deployment profiles in extraction #2); path via NAVI_ADDRESS_BOOK_YAML.
Tests (28 new; recon had none for contacts): full ContactsDB CRUD, soft-delete/
restore/restore-as/purge, Home/Work 409 (create + restore conflict), nearby
proximity, search/category filter, per-user partitioning, auth-required, and
DB auto-create; plus address_book ported from recon's test (exact/partial/
case-insensitive/alias/miss/empty/list/hot-reload/missing-file). Full suite 38.
Timestamp fix (diverges from recon on purpose): restore_as builds updated_at
with Python's strftime. recon uses the bare '%Y-%m-%dT%H:%M:%fZ' there — but
Python's %f is microseconds-only (no seconds), so that yields malformed ISO
strings like "...T15:30:123456Z". recon's own update()/soft_delete() use the
correct '%Y-%m-%dT%H:%M:%S.%fZ'. This port uses the correct format in all three
places and adds a regression guard (strptime) in test_restore_as_relabels.
This is a PRE-EXISTING recon bug; we fix it here. The recon-side restore_as
retires with extraction #7 (Jinja /nav-i + /deleted-contacts removal), so the
recon refactor doesn't need to touch it.
Also: shared/auth.py require_auth now sets request.user_id (recon's contract —
the contacts routes read it). Backward-compatible: navi-traffic/navi-config
admin endpoints don't use it.
Deploy artifacts: systemd unit (:8423) + nginx snippet with two ^~ blocks.
NOTE the nginx prefixes are `^~ /api/contacts` and `^~ /api/address_book`
WITHOUT a trailing slash, so the bare `/api/contacts` (list/create) is matched
too — a trailing-slash prefix would miss it and fall through to recon.
See ../recon_refactor/extraction-3-phase-a.md for the route/schema/ownership
analysis (which also corrects the handoff: contacts is /opt/recon/data/
contacts.db, NOT /mnt/nav/navi.db).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
1.3 KiB
Python
34 lines
1.3 KiB
Python
"""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):
|
|
user_id = get_user_id(request)
|
|
if not user_id:
|
|
return jsonify({'error': 'authentication required'}), 401
|
|
# Expose the validated identity to the handler (recon's contract;
|
|
# contacts routes read request.user_id). Harmless for endpoints that
|
|
# don't use it (navi-traffic/navi-config admin).
|
|
request.user_id = user_id
|
|
return fn(*args, **kwargs)
|
|
return wrapper
|