feat(portal): M1 auth gate — signed magic-URL session + get_current_client
backend/portal_auth.py: stdlib HMAC-signed session cookie carrying the access-
token id (re-validated against the DB each request, so revoke kills live
sessions), hash_token, resolve_token, and the get_current_client dependency
(raises PortalAuthError). SECRET_KEY env (insecure dev default + warning).
routers/portal.py: /portal/enter/{token} mints the cookie -> /portal; /logout;
/access; /portal home stub. main.py registers the router + a PortalAuthError
handler (HTML access page for pages, 401 JSON for /portal/api/*).
Portal shell templates (base, access_required, overview stub), branded dark.
Verified: cookie round-trip + tamper/garbage rejection, token resolution
(valid/bad), get_current_client (valid/no-cookie/revoked) — 8/8 against a temp DB.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
{% extends "portal/base.html" %}
|
||||
{% block title %}Your locations{% endblock %}
|
||||
{% block content %}
|
||||
<h1 class="text-2xl font-semibold mb-1">Your monitoring locations</h1>
|
||||
<p class="text-gray-400 text-sm mb-6">Live sound levels for your active locations.</p>
|
||||
|
||||
{# M1 task 4 fleshes this out into location tiles + a map. #}
|
||||
{% if locations %}
|
||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{% for loc in locations %}
|
||||
<a href="/portal/location/{{ loc.id }}" class="block rounded-xl border border-slate-700 bg-slate-800/50 p-4 hover:border-seismo-orange transition-colors">
|
||||
<div class="font-semibold">{{ loc.name }}</div>
|
||||
<div class="text-xs text-gray-400 mt-1">{{ loc.address or loc.project_name }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-slate-700 bg-slate-800/50 p-8 text-center text-gray-400">
|
||||
No active monitoring locations yet.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user