feat(auth): support trusted device (#52)

New API to maintain sessions and devices:

- GET /api/private/admin/sessions
- DELETE /api/private/admin/sessions/{session_id}
- GET /api/private/admin/trusted-devices
- DELETE /api/private/admin/trusted-devices/{device_id}

Auth:

web clients request `/oauth/token` and `/api/v2/session/verify` with `X-UUID` header to save the client as trusted device.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
MingxuanGame
2025-10-03 11:26:43 +08:00
committed by GitHub
parent f34ed53a55
commit 40670c094b
28 changed files with 897 additions and 1456 deletions

View File

@@ -1,122 +0,0 @@
"""
数据库清理调度器 - 定时清理过期数据
"""
from __future__ import annotations
import asyncio
from app.dependencies.database import engine
from app.log import logger
from app.service.database_cleanup_service import DatabaseCleanupService
from sqlmodel.ext.asyncio.session import AsyncSession
class DatabaseCleanupScheduler:
"""数据库清理调度器"""
def __init__(self):
self.running = False
self.task = None
async def start(self):
"""启动调度器"""
if self.running:
return
self.running = True
self.task = asyncio.create_task(self._run_scheduler())
logger.debug("Database cleanup scheduler started")
async def stop(self):
"""停止调度器"""
if not self.running:
return
self.running = False
if self.task:
self.task.cancel()
try:
await self.task
except asyncio.CancelledError:
pass
logger.debug("Database cleanup scheduler stopped")
async def _run_scheduler(self):
"""运行调度器"""
while self.running:
try:
# 每小时运行一次清理
await asyncio.sleep(3600) # 3600秒 = 1小时
if not self.running:
break
await self._run_cleanup()
except asyncio.CancelledError:
break
except Exception as e:
logger.error(f"Database cleanup scheduler error: {e!s}")
# 发生错误后等待5分钟再继续
await asyncio.sleep(300)
async def _run_cleanup(self):
"""执行清理任务"""
try:
async with AsyncSession(engine) as db:
logger.debug("Starting scheduled database cleanup...")
# 清理过期的验证码
expired_codes = await DatabaseCleanupService.cleanup_expired_verification_codes(db)
# 清理过期的登录会话
expired_sessions = await DatabaseCleanupService.cleanup_expired_login_sessions(db)
# 清理1小时前未验证的登录会话
unverified_sessions = await DatabaseCleanupService.cleanup_unverified_login_sessions(db, 1)
# 只在有清理记录时输出总结
total_cleaned = expired_codes + expired_sessions + unverified_sessions
if total_cleaned > 0:
logger.debug(
f"Scheduled cleanup completed - codes: {expired_codes}, "
f"sessions: {expired_sessions}, unverified: {unverified_sessions}"
)
except Exception as e:
logger.error(f"Error during scheduled database cleanup: {e!s}")
async def run_manual_cleanup(self):
"""手动运行完整清理"""
try:
async with AsyncSession(engine) as db:
logger.debug("Starting manual database cleanup...")
results = await DatabaseCleanupService.run_full_cleanup(db)
total = sum(results.values())
if total > 0:
logger.debug(f"Manual cleanup completed, total records cleaned: {total}")
return results
except Exception as e:
logger.error(f"Error during manual database cleanup: {e!s}")
return {}
# 全局实例
database_cleanup_scheduler = DatabaseCleanupScheduler()
async def start_database_cleanup_scheduler():
"""启动数据库清理调度器"""
await database_cleanup_scheduler.start()
async def stop_database_cleanup_scheduler():
"""停止数据库清理调度器"""
await database_cleanup_scheduler.stop()
async def run_manual_database_cleanup():
"""手动运行数据库清理"""
return await database_cleanup_scheduler.run_manual_cleanup()