Add API key authentication to all endpoints
This commit is contained in:
parent
56d8fb4d56
commit
176439c1df
4 changed files with 207 additions and 28 deletions
|
|
@ -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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue