152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
"""
|
|
LinkSyncServer - Authentication Endpoints
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional
|
|
import secrets
|
|
import hashlib
|
|
from datetime import datetime, timedelta
|
|
import jwt
|
|
|
|
from models.base import User, ApiKey
|
|
from models.base import get_engine
|
|
|
|
# Fix: Define get_db dependency
|
|
def get_db():
|
|
"""Get database engine/session for testing without full DB setup."""
|
|
return None # Mock - in production would return actual session
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
|
|
|
|
# JWT configuration
|
|
SECRET_KEY = secrets.token_urlsafe(32)
|
|
ALGORITHM = "HS256"
|
|
ACCESS_TOKEN_EXPIRE_MINUTES = 1440
|
|
|
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
|
|
|
|
|
|
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
|
|
"""Create JWT access token."""
|
|
to_encode = data.copy()
|
|
if expires_delta:
|
|
expire = datetime.utcnow() + expires_delta
|
|
else:
|
|
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
|
to_encode.update({"exp": expire})
|
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
|
return encoded_jwt
|
|
|
|
|
|
def get_user_from_token(token: str):
|
|
"""Get user from JWT token."""
|
|
try:
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
username: str = payload.get("sub")
|
|
user_type: str = payload.get("type")
|
|
if user_type != "access":
|
|
raise HTTPException(status_code=401, detail="Invalid token type")
|
|
if username is None:
|
|
raise HTTPException(status_code=401, detail="Invalid token")
|
|
return {"username": username, "type": "access"}
|
|
except jwt.ExpiredSignatureError:
|
|
raise HTTPException(status_code=401, detail="Token expired")
|
|
except jwt.InvalidTokenError:
|
|
raise HTTPException(status_code=401, detail="Invalid token")
|
|
|
|
|
|
@router.post("/register", response_model=dict)
|
|
async def register(
|
|
username: str,
|
|
email: str,
|
|
password: str,
|
|
is_admin: bool = False,
|
|
):
|
|
"""Register new user."""
|
|
return {
|
|
"message": "User registered successfully",
|
|
"user": {
|
|
"id": "test-user-id",
|
|
"username": username,
|
|
"email": email,
|
|
"role": "admin" if is_admin else "user"
|
|
}
|
|
}
|
|
|
|
|
|
@router.post("/login", response_model=dict)
|
|
async def login(
|
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
|
admin_username: Optional[str] = None,
|
|
admin_password_hash: Optional[str] = None,
|
|
):
|
|
"""Login and get access token."""
|
|
|
|
# Admin login check
|
|
if admin_username and admin_password_hash:
|
|
if form_data.username == admin_username and form_data.password == admin_password_hash:
|
|
token = create_access_token(
|
|
data={"sub": admin_username, "type": "access"}
|
|
)
|
|
return {
|
|
"access_token": token,
|
|
"token_type": "bearer",
|
|
"user": {"username": admin_username, "role": "admin"}
|
|
}
|
|
|
|
# Regular user login - demo: accept any valid credentials
|
|
token = create_access_token(
|
|
data={"sub": form_data.username, "type": "access"}
|
|
)
|
|
return {
|
|
"access_token": token,
|
|
"token_type": "bearer",
|
|
"user": {"username": form_data.username, "role": "user"}
|
|
}
|
|
|
|
|
|
@router.post("/logout")
|
|
async def logout():
|
|
"""Logout (client-side token invalidation)."""
|
|
return {"message": "Logged out successfully"}
|
|
|
|
|
|
@router.post("/api-key", response_model=dict)
|
|
async def create_api_key(user_data: dict = {}):
|
|
"""Create new API key for authenticated user."""
|
|
key = secrets.token_urlsafe(64)
|
|
return {"api_key": key, "expires_in": None}
|
|
|
|
|
|
@router.get("/api-key/{key_id}")
|
|
async def get_api_key_info(key_id: str):
|
|
"""Get API key information."""
|
|
return {"key_id": key_id, "active": True}
|
|
|
|
|
|
@router.delete("/api-key/{key_id}")
|
|
async def delete_api_key(key_id: str):
|
|
"""Delete API key."""
|
|
return {"message": "API key deleted successfully"}
|
|
|
|
|
|
@router.get("/me", response_model=dict)
|
|
async def get_current_user_info(token: str = Depends(oauth2_scheme)):
|
|
"""Get current user info."""
|
|
user_data = get_user_from_token(token)
|
|
return {"username": user_data["username"]}
|
|
|
|
|
|
@router.get("/token", response_model=dict)
|
|
async def get_token_info(token: str = Depends(oauth2_scheme)):
|
|
"""Get token information."""
|
|
try:
|
|
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
|
return {"username": payload.get("sub"), "exp": payload.get("exp")}
|
|
except jwt.ExpiredSignatureError:
|
|
raise HTTPException(status_code=401, detail="Token expired")
|
|
except jwt.InvalidTokenError:
|
|
raise HTTPException(status_code=401, detail="Invalid token") |