Compare commits
4 Commits
security
...
b8e7860970
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8e7860970 | ||
|
|
10ce121448 | ||
|
|
ba48ff5c1c | ||
|
|
1032c08a40 |
@@ -9,8 +9,9 @@ Working mpv
|
||||
- [x] security, using tokens
|
||||
- [x] works in docker
|
||||
- [x] make it use ftp server
|
||||
- [ ] using cookies and temporary tokens
|
||||
- [ ] deploy on gogs webhooks
|
||||
- [ ] crypt files
|
||||
- [ ] make it connect to db
|
||||
- [ ] multiuser system
|
||||
- [ ] files ownhership, owner and groups, roles
|
||||
- [ ] archive
|
||||
|
||||
64
app/api.py
64
app/api.py
@@ -1,4 +1,4 @@
|
||||
from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, Security
|
||||
from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, Security, Response, Cookie
|
||||
from fastapi.responses import FileResponse, PlainTextResponse, StreamingResponse
|
||||
from fastapi.security import APIKeyHeader
|
||||
from sqlalchemy import exists
|
||||
@@ -6,6 +6,7 @@ import hashlib
|
||||
from ftplib import FTP
|
||||
from io import BytesIO
|
||||
from . import db
|
||||
from .session_manager import SessionManager
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import hmac
|
||||
@@ -19,6 +20,7 @@ api_key_header = APIKeyHeader(name="X-API-Key")
|
||||
FTP_URL = os.getenv("FTP_URL")
|
||||
FTP_LOGIN = os.getenv("FTP_LOGIN")
|
||||
FTP_PASSWORD = os.getenv("FTP_PASSWORD")
|
||||
CACHE_DIR = "cache"
|
||||
|
||||
def verify_api_key(api_key: str = Security(api_key_header)):
|
||||
api_key_hashed = hashlib.sha256(api_key.encode()).hexdigest()
|
||||
@@ -32,13 +34,40 @@ def compute_hash(data: bytes, algorithm="sha256") -> str:
|
||||
h.update(data)
|
||||
return h.hexdigest()
|
||||
|
||||
app = FastAPI()
|
||||
def set_cookie(response: Response, name: str, value: str, max_age: int) -> None:
|
||||
response.set_cookie(
|
||||
key=name,
|
||||
value=value,
|
||||
httponly=True,
|
||||
secure=True,
|
||||
samesite="Strict",
|
||||
max_age=max_age,
|
||||
)
|
||||
|
||||
TOKEN_TTL = 60*15
|
||||
session_manager = SessionManager(TOKEN_TTL)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "hiii from sfs"}
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
def login(response: Response):
|
||||
user_id = "user123"
|
||||
token = session_manager.create(user_id)
|
||||
set_cookie(response, "token", token, TOKEN_TTL)
|
||||
return {"message": "logged in"}
|
||||
|
||||
def get_current_user(token: str = Cookie(None)) -> str:
|
||||
return session_manager.validate(token)
|
||||
|
||||
@app.get("/protected")
|
||||
def protected_route(user: str = Depends(get_current_user)):
|
||||
return {"message": f"Hello {user}, you are authenticated!"}
|
||||
|
||||
@app.post("/file")
|
||||
async def save_file(file: UploadFile = File(...), api_key: str = Depends(verify_api_key)):
|
||||
contents = await file.read()
|
||||
@@ -61,29 +90,38 @@ async def save_file(file: UploadFile = File(...), api_key: str = Depends(verify_
|
||||
|
||||
ftp.storbinary(f"STOR {FILES_DIR}/{file_url}", buffer)
|
||||
ftp.quit()
|
||||
|
||||
cached = os.path.join(CACHE_DIR, file_url)
|
||||
if os.path.exists(cached):
|
||||
os.remove(cached)
|
||||
|
||||
return {"status": "saved", "filename": file_url}
|
||||
except Exception as e:
|
||||
db.remove_file(file_url)
|
||||
|
||||
return {"status": "error", "message": f"Could not save file {file_url}: {e}"}
|
||||
else:
|
||||
return {"status": "file_exists", "filename": existed_url}
|
||||
|
||||
@app.get("/file/{filename}")
|
||||
async def get_file(filename: str, raw: bool = False, api_key: str = Depends(verify_api_key)):
|
||||
try:
|
||||
ftp = FTP(FTP_URL)
|
||||
ftp.login(FTP_LOGIN, FTP_PASSWORD)
|
||||
buffer = BytesIO()
|
||||
ftp.retrbinary(f"RETR {FILES_DIR}/{filename}", buffer.write)
|
||||
ftp.quit()
|
||||
buffer.seek(0)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=f"File not found: {e}")
|
||||
local_path = os.path.join(CACHE_DIR, filename)
|
||||
|
||||
if not os.path.exists(local_path):
|
||||
try:
|
||||
ftp = FTP(FTP_URL)
|
||||
ftp.login(FTP_LOGIN, FTP_PASSWORD)
|
||||
with open(local_path, "wb") as f:
|
||||
ftp.retrbinary(f"RETR {FILES_DIR}/{filename}", f.write)
|
||||
ftp.quit()
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=404, detail=f"File not found: {e}")
|
||||
|
||||
if raw:
|
||||
return PlainTextResponse(buffer.getvalue().decode("utf-8", errors="ignore"))
|
||||
with open(local_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
return PlainTextResponse(f.read())
|
||||
|
||||
return StreamingResponse(buffer, media_type="application/octet-stream", headers={"Content-Disposition": f'attachment; filename="{filename}"'})
|
||||
return FileResponse(local_path, filename=filename)
|
||||
|
||||
|
||||
from ftplib import FTP
|
||||
|
||||
35
app/session_manager.py
Normal file
35
app/session_manager.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from fastapi import HTTPException
|
||||
from datetime import datetime, timedelta
|
||||
import secrets
|
||||
from typing import Dict, Optional
|
||||
|
||||
class SessionManager:
|
||||
def __init__(self, ttl: int):
|
||||
self.ttl = ttl
|
||||
self._tokens: Dict[str, Dict[str, datetime]] = {}
|
||||
|
||||
def create(self, user_id: str) -> str:
|
||||
token = secrets.token_urlsafe(32)
|
||||
self._tokens[token] = {
|
||||
"user": user_id,
|
||||
"expires": datetime.utcnow() + timedelta(seconds=self.ttl),
|
||||
}
|
||||
return token
|
||||
|
||||
def validate(self, token: Optional[str]) -> str:
|
||||
self.cleanup()
|
||||
if not token or token not in self._tokens:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
token_data = self._tokens[token]
|
||||
if token_data["expires"] < datetime.utcnow():
|
||||
del self._tokens[token]
|
||||
raise HTTPException(status_code=401, detail="Session expired")
|
||||
|
||||
return token_data["user"]
|
||||
|
||||
def cleanup(self) -> None:
|
||||
now = datetime.utcnow()
|
||||
expired = [t for t, data in self._tokens.items() if data["expires"] < now]
|
||||
for t in expired:
|
||||
del self._tokens[t]
|
||||
Reference in New Issue
Block a user