Add API key authentication to all endpoints

This commit is contained in:
Your Name 2026-05-19 20:19:02 -04:00
parent 56d8fb4d56
commit 176439c1df
4 changed files with 207 additions and 28 deletions

View file

@ -15,12 +15,21 @@ from datetime import datetime
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, File, Header, HTTPException, Query, UploadFile
from fastapi import Depends, FastAPI, File, Header, HTTPException, Query, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from pydantic import BaseModel
import config
async def verify_api_key(x_api_key: str = Header(None, alias="X-API-Key")):
"""Verify API key for protected endpoints."""
if not config.API_KEY:
raise HTTPException(status_code=500, detail="API_KEY not configured on server")
if x_api_key != config.API_KEY:
raise HTTPException(status_code=401, detail="Invalid or missing API key")
return x_api_key
import crypto
import database
import importer
@ -92,7 +101,7 @@ async def health_check():
return {"status": "ok"}
@app.get("/api/v1/info")
@app.get("/api/v1/info", dependencies=[Depends(verify_api_key)])
async def server_info():
"""Server configuration info."""
return {
@ -110,21 +119,17 @@ async def get_public_key():
# Upload (existing functionality)
@app.post("/api/v1/recordings/upload")
@app.post("/api/v1/recordings/upload", dependencies=[Depends(verify_api_key)])
async def upload_recording(
file: UploadFile = File(...),
x_api_key: str = Header(None, alias="X-API-Key"),
):
"""
Receives an encrypted recording, decrypts it, and stores it.
The file is expected to be in the SREC encrypted format.
Returns a SHA-256 hash of the received encrypted file for verification.
Requires X-API-Key header if API_KEY is configured.
Requires X-API-Key header.
"""
# Check API key if configured
if config.API_KEY and x_api_key != config.API_KEY:
raise HTTPException(status_code=401, detail="Invalid or missing API key")
if not file.filename:
raise HTTPException(status_code=400, detail="No filename provided")
@ -192,7 +197,7 @@ async def upload_recording(
# Recordings API
@app.get("/api/v1/recordings")
@app.get("/api/v1/recordings", dependencies=[Depends(verify_api_key)])
async def list_recordings(
source: Optional[str] = Query(None, description="Filter by source name"),
date_from: Optional[str] = Query(None, description="Start date (YYYY-MM-DD)"),
@ -219,7 +224,7 @@ async def list_recordings(
}
@app.get("/api/v1/recordings/{recording_id}")
@app.get("/api/v1/recordings/{recording_id}", dependencies=[Depends(verify_api_key)])
async def get_recording(recording_id: int):
"""Get recording details with transcript segments and highlights."""
recording = database.get_recording(recording_id)
@ -228,7 +233,7 @@ async def get_recording(recording_id: int):
return recording
@app.get("/api/v1/recordings/{recording_id}/audio")
@app.get("/api/v1/recordings/{recording_id}/audio", dependencies=[Depends(verify_api_key)])
async def get_recording_audio(recording_id: int):
"""Stream the audio file for a recording."""
recording = database.get_recording(recording_id)
@ -256,7 +261,7 @@ async def get_recording_audio(recording_id: int):
# Import API
@app.post("/api/v1/import/transcriptions")
@app.post("/api/v1/import/transcriptions", dependencies=[Depends(verify_api_key)])
async def import_transcriptions(request: ImportRequest):
"""
Import transcription JSON files from a directory.
@ -286,7 +291,7 @@ async def import_transcriptions(request: ImportRequest):
return result
@app.post("/api/v1/import/audio")
@app.post("/api/v1/import/audio", dependencies=[Depends(verify_api_key)])
async def import_audio(request: ImportAudioRequest):
"""
Import audio files from a directory (without transcriptions).
@ -306,7 +311,7 @@ async def import_audio(request: ImportAudioRequest):
return result
@app.post("/api/v1/import/link-audio")
@app.post("/api/v1/import/link-audio", dependencies=[Depends(verify_api_key)])
async def link_audio(audio_dir: str = Query(..., description="Directory containing audio files")):
"""
Link audio files to existing transcription records.
@ -323,7 +328,7 @@ async def link_audio(audio_dir: str = Query(..., description="Directory containi
# Highlights API
@app.get("/api/v1/highlights")
@app.get("/api/v1/highlights", dependencies=[Depends(verify_api_key)])
async def list_highlights(
recording_id: Optional[int] = Query(None),
tag: Optional[str] = Query(None),
@ -338,7 +343,7 @@ async def list_highlights(
return {"highlights": highlights, "count": len(highlights)}
@app.post("/api/v1/highlights")
@app.post("/api/v1/highlights", dependencies=[Depends(verify_api_key)])
async def create_highlight(highlight: HighlightCreate):
"""Create a new highlight/bookmark."""
# Verify recording exists
@ -358,7 +363,7 @@ async def create_highlight(highlight: HighlightCreate):
return {"id": highlight_id, "message": "Highlight created"}
@app.delete("/api/v1/highlights/{highlight_id}")
@app.delete("/api/v1/highlights/{highlight_id}", dependencies=[Depends(verify_api_key)])
async def delete_highlight(highlight_id: int):
"""Delete a highlight."""
success = database.delete_highlight(highlight_id)
@ -369,7 +374,7 @@ async def delete_highlight(highlight_id: int):
# Sources/Devices API
@app.get("/api/v1/sources")
@app.get("/api/v1/sources", dependencies=[Depends(verify_api_key)])
async def list_sources():
"""Get summary of all recording sources."""
sources = database.list_sources()
@ -378,7 +383,7 @@ async def list_sources():
# Legacy endpoint (for backwards compatibility)
@app.get("/api/v1/recordings/files")
@app.get("/api/v1/recordings/files", dependencies=[Depends(verify_api_key)])
async def list_recording_files(date: Optional[str] = Query(None, description="Filter by date (YYYY-MM-DD)")):
"""
Lists recording files on disk (legacy endpoint).