import hashlib import hmac import json from fastapi import Request, Security from fastapi.security import HTTPBearer from pigway_frame_flow.errors import ErrorCode as E from pigway_frame_flow.errors import FlowError from pigway_frame_flow.storage.database import encode, now_iso bearer = HTTPBearer(auto_error=False) ALL_SCOPES = [ "devices:read", "images:read", "images:delete", "display:write", "templates:read", "render:write", "jobs:read", "jobs:write", "history:read", "settings:write", "admin", ] def seed_client(db, token): if token: digest = hashlib.sha256(token.encode()).hexdigest() db.execute( "INSERT INTO clients VALUES(?,?,?,?,?,?,?) ON CONFLICT(client_id) DO UPDATE SET token_hash=excluded.token_hash, enabled=1", ("admin", digest, "Administrator", encode(ALL_SCOPES), now_iso(), None, 1), ) else: db.execute("UPDATE clients SET enabled=0 WHERE client_id='admin'") def require(scope): def authorize(request: Request, credentials=Security(bearer)): db = request.app.state.service.db if not credentials: raise FlowError(E.UNAUTHORIZED, "Frame Flow API token required", 401) digest = hashlib.sha256(credentials.credentials.encode()).hexdigest() for row in db.all("SELECT * FROM clients WHERE enabled=1"): if hmac.compare_digest(digest, row["token_hash"]): permissions = json.loads(row["permissions"]) if scope not in permissions and "admin" not in permissions: raise FlowError( E.FORBIDDEN, "Token scope does not allow this action", 403 ) request.state.client_id = row["client_id"] # Avoid writing last_used on every image or poll. if not row["last_used_at"] or ( now_iso()[:16] != row["last_used_at"][:16] ): db.execute( "UPDATE clients SET last_used_at=? WHERE client_id=?", (now_iso(), row["client_id"]), ) return row["client_id"] raise FlowError(E.UNAUTHORIZED, "Invalid Frame Flow API token", 401) return authorize