feat: add web UI with login, CRUD, admin, and API key management
- Add login page with JWT authentication - Add dashboard with stats and quick actions - Add links management page (full CRUD with search) - Add collections management page - Add API key management page with copy-to-clipboard - Add admin user management page (admin only) - Fix UUID type mismatches across all endpoints - Add updated_at column to api_keys and audit_log in schema.sql - Fix DB_PASSWORD default in docker-compose.yml - Add PyJWT to requirements.txt - Fix API docs URL (/docs instead of /api/docs) - Improve JS error handling (show actual messages) - Rewrite conftest.py with proper DB lifecycle management - Add 42 new integration tests (84 total, all passing) - test_admin.py: 15 tests for admin endpoints - test_auth_extended.py: 9 tests for API key CRUD - test_tags.py: 12 tests for tag endpoints - test_sync.py: 6 tests for sync endpoints
This commit is contained in:
@@ -5,8 +5,9 @@ LinkSyncServer - Authentication Endpoints
|
||||
import hashlib
|
||||
import os
|
||||
import secrets
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
from typing import List, Optional
|
||||
|
||||
import bcrypt
|
||||
import jwt
|
||||
@@ -178,6 +179,32 @@ async def logout():
|
||||
return {"message": "Logged out successfully"}
|
||||
|
||||
|
||||
@router.get("/api-keys", response_model=List[dict])
|
||||
async def list_api_keys(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = get_session()
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user["username"]).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
|
||||
return [
|
||||
{
|
||||
"id": str(key.id),
|
||||
"key_id": str(key.id),
|
||||
"name": key.name,
|
||||
"is_active": key.is_active,
|
||||
"expires_at": key.expires_at.isoformat() if key.expires_at else None,
|
||||
"created_at": key.created_at.isoformat() if key.created_at else None,
|
||||
}
|
||||
for key in api_keys
|
||||
]
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/api-key", response_model=ApiKeyResponse)
|
||||
async def create_api_key(
|
||||
name: str = "default",
|
||||
@@ -204,7 +231,7 @@ async def create_api_key(
|
||||
|
||||
return {
|
||||
"api_key": raw_key,
|
||||
"key_id": api_key.id,
|
||||
"key_id": str(api_key.id),
|
||||
"name": api_key.name,
|
||||
"expires_at": api_key.expires_at.isoformat() if api_key.expires_at else None,
|
||||
}
|
||||
@@ -223,14 +250,19 @@ async def get_api_key_info(
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
parsed_key_id = uuid.UUID(key_id)
|
||||
except (ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
api_key = db.query(ApiKey).filter(
|
||||
ApiKey.id == key_id, ApiKey.user_id == user.id
|
||||
ApiKey.id == parsed_key_id, ApiKey.user_id == user.id
|
||||
).first()
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
return {
|
||||
"key_id": api_key.id,
|
||||
"key_id": str(api_key.id),
|
||||
"name": api_key.name,
|
||||
"is_active": api_key.is_active,
|
||||
"expires_at": api_key.expires_at.isoformat() if api_key.expires_at else None,
|
||||
@@ -251,8 +283,13 @@ async def delete_api_key(
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
parsed_key_id = uuid.UUID(key_id)
|
||||
except (ValueError, AttributeError):
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
api_key = db.query(ApiKey).filter(
|
||||
ApiKey.id == key_id, ApiKey.user_id == user.id
|
||||
ApiKey.id == parsed_key_id, ApiKey.user_id == user.id
|
||||
).first()
|
||||
if not api_key:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
|
||||
Reference in New Issue
Block a user