61 lines
2.3 KiB
Python
61 lines
2.3 KiB
Python
from repositories.pocketbase_client import pb_client
|
|
from repositories.tenant_repository import tenant_repository
|
|
from repositories.seat_repository import seat_repository
|
|
|
|
|
|
def get_seat_from_request(request):
|
|
"""
|
|
Resolves both the tenant_id AND the specific active seat record for a
|
|
dashboard request from its `Authorization: Bearer {jwt}` header. The
|
|
JWT itself is issued and verified by PocketBase (the dashboard
|
|
authenticates directly against PocketBase) — this backend doesn't
|
|
hold the JWT signing secret, so it validates the token by asking
|
|
PocketBase to refresh it, then resolves the returned email to a
|
|
tenant and seat.
|
|
|
|
Needed by seat-scoped features (the trip watchlist's per-seat cap)
|
|
where "which tenant" isn't enough — every other authenticated route
|
|
only ever needed tenant_id, so the seat record this lookup already
|
|
finds was previously discarded (see get_tenant_id_from_request()
|
|
below, now a thin wrapper around this).
|
|
|
|
Data flow:
|
|
request.headers['Authorization'] → strip "Bearer " prefix →
|
|
PocketBase POST /api/collections/users/auth-refresh →
|
|
{ record: { email } } → seat lookup by email across known tenants
|
|
(via domain) → (tenant_id, seat) returned, or (None, None) if
|
|
unresolvable
|
|
"""
|
|
auth_header = request.headers.get('Authorization', '')
|
|
if not auth_header.startswith('Bearer '):
|
|
return None, None
|
|
token = auth_header[len('Bearer '):]
|
|
|
|
record = pb_client.auth_refresh(token)
|
|
if not record or not record.get('email'):
|
|
return None, None
|
|
|
|
email = record['email']
|
|
domain = email.split('@', 1)[1].lower() if '@' in email else None
|
|
if not domain:
|
|
return None, None
|
|
|
|
tenant = tenant_repository.get_by_domain(domain)
|
|
if not tenant:
|
|
return None, None
|
|
|
|
# Confirm this email actually holds an active seat on the tenant —
|
|
# a verified PocketBase login alone isn't sufficient authorization,
|
|
# the email must also be a registered, active seat.
|
|
seat = seat_repository.get_active(tenant['id'], email)
|
|
if not seat:
|
|
return None, None
|
|
|
|
return tenant['id'], seat
|
|
|
|
|
|
def get_tenant_id_from_request(request):
|
|
"""Back-compat wrapper — most routes only need the tenant_id, not the seat."""
|
|
tenant_id, _ = get_seat_from_request(request)
|
|
return tenant_id
|