From 43f22de8fd6f3655bde761500bdbb8dfae1ba6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Thu, 2 Oct 2025 21:52:52 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8C=AA=E8=BD=A6?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 7 ++- main.py | 4 +- model/database.py | 33 ++++++++++---- model/items.py | 1 + model/response.py | 1 + routes/admin.py | 32 ++++++++----- routes/object.py | 114 +++++++++++++++++++++++++++++++++++++++++----- 7 files changed, 158 insertions(+), 34 deletions(-) diff --git a/app.py b/app.py index 0ad36a0..e89af34 100644 --- a/app.py +++ b/app.py @@ -11,7 +11,7 @@ asyncio.run(model.database.Database().init_db()) # 定义程序参数 APP_NAME: str = 'Findreve' -VERSION: str = '2.0.0' +VERSION: str = '2.0.0-ootc' summary='标记、追踪与找回 —— 就这么简单。' description='Findreve 是一款强大且直观的解决方案,旨在帮助您管理个人物品,'\ '并确保丢失后能够安全找回。每个物品都会被分配一个 唯一 ID ,'\ @@ -42,11 +42,16 @@ app.include_router(object.Router) @app.get("/") def read_root(): + if not os.path.exists("dist/index.html"): + raise HTTPException(status_code=404, detail="Frontend not built. Please build the frontend first.") return FileResponse("dist/index.html") # 回退路由 @app.get("/{path:path}") async def serve_spa(request: Request, path: str): + if not os.path.exists("dist/index.html"): + raise HTTPException(status_code=404, detail="Frontend not built. Please build the frontend first.") + # 排除API路由 if path.startswith("api/"): raise HTTPException(status_code=404, detail="Not Found") diff --git a/main.py b/main.py index a45c395..240541f 100644 --- a/main.py +++ b/main.py @@ -21,5 +21,5 @@ if __name__ == '__main__': uvicorn.run( 'app:app', host='0.0.0.0', - port=8080, - reload=True) \ No newline at end of file + port=8080 + ) \ No newline at end of file diff --git a/model/database.py b/model/database.py index ddc7e62..0e8ab0c 100644 --- a/model/database.py +++ b/model/database.py @@ -13,7 +13,7 @@ import aiosqlite from datetime import datetime import tool import logging -from typing import Optional +from typing import Literal, Optional # 数据库类 class Database: @@ -32,6 +32,7 @@ class Database: create_objects_table = """ CREATE TABLE IF NOT EXISTS fr_objects ( id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL, key TEXT NOT NULL, name TEXT NOT NULL, icon TEXT, @@ -109,10 +110,18 @@ class Database: await db.commit() logging.info("数据库初始化完成并提交更改") - async def add_object(self, key: str, name: str, icon: str = None, phone: str = None): + async def add_object( + self, + key: str, + type: Literal['normal', 'car'], + name: str, + icon: str = None, + phone: str = None, + ): """ 添加新对象 + :param type: 对象类型 :param key: 序列号 :param name: 名称 :param icon: 图标 @@ -126,14 +135,15 @@ class Database: now = datetime.now() now = now.strftime("%Y-%m-%d %H:%M:%S") await db.execute( - "INSERT INTO fr_objects (key, name, icon, phone, create_at, status) VALUES (?, ?, ?, ?, ?, 'ok')", - (key, name, icon, phone, now) + "INSERT INTO fr_objects (key, name, icon, phone, create_at, status, type) VALUES (?, ?, ?, ?, ?, 'ok', ?)", + (key, name, icon, phone, now, type) ) await db.commit() async def update_object( self, id: int, + type: Literal['normal', 'car'] = None, key: str = None, name: str = None, icon: str = None, @@ -141,11 +151,13 @@ class Database: phone: int = None, lost_description: Optional[str] = None, find_ip: Optional[str] = None, - lost_time: Optional[str] = None): + lost_time: Optional[str] = None + ): """ 更新对象信息 :param id: 对象ID + :param type: 对象类型 :param key: 序列号 :param name: 名称 :param icon: 图标 @@ -173,13 +185,18 @@ class Database: f"phone = COALESCE(?, phone), " f"context = COALESCE(?, context), " f"find_ip = COALESCE(?, find_ip), " - f"lost_at = COALESCE(?, lost_at) " + f"lost_at = COALESCE(?, lost_at), " + f"type = COALESCE(?, type) " f"WHERE id = ?", - (key, name, icon, status, phone, lost_description, find_ip, lost_time, id) + (key, name, icon, status, phone, lost_description, find_ip, lost_time, type, id) ) await db.commit() - async def get_object(self, id: int = None, key: str = None): + async def get_object( + self, + id: int = None, + key: str = None + ): """ 获取对象 diff --git a/model/items.py b/model/items.py index 5479206..b39ed3a 100644 --- a/model/items.py +++ b/model/items.py @@ -3,6 +3,7 @@ from typing import Optional class Item(BaseModel): id: int + type: str key: str name: str icon: str diff --git a/model/response.py b/model/response.py index 367ad96..a87dd32 100644 --- a/model/response.py +++ b/model/response.py @@ -8,6 +8,7 @@ class DefaultResponse(BaseModel): class ObjectData(BaseModel): id: int + type: Literal['normal', 'car'] key: str name: str icon: str diff --git a/routes/admin.py b/routes/admin.py index 9b2b795..f4b0437 100644 --- a/routes/admin.py +++ b/routes/admin.py @@ -80,17 +80,19 @@ async def get_items( items = results item = [] for i in items: + print(i) item.append(Item( id=i[0], - key=i[1], - name=i[2], - icon=i[3], - status=i[4], - phone=i[5], - lost_description=i[6], - find_ip=i[7], - create_time=i[8], - lost_time=i[9] + type=i[1], + key=i[2], + name=i[3], + icon=i[4], + status=i[5], + phone=i[6], + lost_description=i[7], + find_ip=i[8], + create_time=i[9], + lost_time=i[10] )) return DefaultResponse(data=item) else: @@ -105,13 +107,16 @@ async def get_items( ) async def add_items( key: str, + type: Literal['normal', 'car'], name: str, icon: str, - phone: str) -> DefaultResponse: + phone: str +) -> DefaultResponse: ''' 添加物品信息。 - **key**: 物品的关键字 + - **type**: 物品的类型 - **name**: 物品的名称 - **icon**: 物品的图标 - **phone**: 联系电话 @@ -119,7 +124,12 @@ async def add_items( try: await database.Database().add_object( - key=key, name=name, icon=icon, phone=phone) + key=key, + type=type, + name=name, + icon=icon, + phone=phone + ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) else: diff --git a/routes/object.py b/routes/object.py index cf209f8..e81ffc3 100644 --- a/routes/object.py +++ b/routes/object.py @@ -4,6 +4,7 @@ from fastapi.responses import JSONResponse from model.database import Database from model.response import DefaultResponse, ObjectData import asyncio +import aiohttp Router = APIRouter(prefix='/api/object', tags=['物品 Object']) @@ -24,23 +25,25 @@ async def get_object(item_key: str, request: Request): object_data = await db.get_object(key=item_key) if object_data: - if object_data[4] == 'lost': + if object_data[5] == 'lost': # 物品已标记为丢失,更新IP地址 await db.update_object(id=object_data[0], find_ip=str(request.client.host)) - # 添加一些随机延迟,类似JWT身份验证时根据延迟爆破引发的问题 - await asyncio.sleep(random.uniform(0.10, 0.30)) - else: - await asyncio.sleep(random.uniform(0.10, 0.30)) + # 添加一些随机延迟,类似JWT身份验证时根据延迟爆破引发的问题 + await asyncio.sleep(random.uniform(0.10, 0.30)) + print(object_data) return DefaultResponse(data=ObjectData( id=object_data[0], - key=object_data[1], - name=object_data[2], - icon=object_data[3], - status=object_data[4], - phone=object_data[5], - context=object_data[6] + type=object_data[1], + key=object_data[2], + name=object_data[3], + icon=object_data[4], + status=object_data[5], + phone=object_data[6], + lost_description=object_data[7], + create_time=object_data[9], + lost_time=object_data[10] ).model_dump()) else: return JSONResponse( status_code=404, @@ -48,4 +51,91 @@ async def get_object(item_key: str, request: Request): code=404, msg='物品不存在或出现异常' ).model_dump() - ) \ No newline at end of file + ) + +@Router.put( + path='/{item_id}', + summary="通知车主进行挪车", + description="向车主发送挪车通知", + response_model=DefaultResponse, + response_description="挪车通知结果" +) +async def notify_move_car( + item_id: int, + phone: str = None, +): + """通知车主进行挪车 / Notify car owner to move the car + + Args: + item_id (int): 物品ID / Item ID + phone (str, optional): 挪车发起者电话 / Phone number of the person initiating the move. Defaults to None. + """ + db = Database() + await db.init_db() + + # 检查是否存在该物品 + object_data = await db.get_object(id=item_id) + if not object_data: + return JSONResponse( + status_code=404, + content=DefaultResponse( + code=404, + msg='物品不存在或出现异常' + ).model_dump() + ) + + # 检查物品类型是否为车辆 + if object_data[1] != 'car': + return JSONResponse( + status_code=400, + content=DefaultResponse( + code=400, + msg='该物品不是车辆,无法发送挪车通知' + ).model_dump() + ) + + # 发起挪车通知(目前仅适配Server酱) + server_chan_key = await db.get_setting('server_chan_key') + if not server_chan_key: + return JSONResponse( + status_code=500, + content=DefaultResponse( + code=500, + msg='未配置Server酱,无法发送挪车通知' + ).model_dump() + ) + + title = "挪车通知 - Findreve" + description = f"您的车辆“{object_data[3]}”被请求挪车。\n\n" + if phone: + description += f"请求挪车者电话:[{phone}](tel:{phone})\n\n" + description += "请尽快联系请求者并挪车。" + + async with aiohttp.ClientSession() as session: + async with session.post( + url=f"https://sctapi.ftqq.com/{server_chan_key}.send", + data={ + "title": title, + "desp": description + } + ) as resp: + if resp.status == 200: + resp_json = await resp.json() + if resp_json.get('code') == 0: + return DefaultResponse(msg='挪车通知发送成功') + else: + return JSONResponse( + status_code=500, + content=DefaultResponse( + code=500, + msg=f"挪车通知发送失败,Server酱返回错误:{resp_json.get('message')}" + ).model_dump() + ) + else: + return JSONResponse( + status_code=500, + content=DefaultResponse( + code=500, + msg=f"挪车通知发送失败,HTTP状态码:{resp.status}" + ).model_dump() + ) \ No newline at end of file From 643f19c1f192bc5b8d53178b3eadf065306476b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Fri, 3 Oct 2025 10:00:22 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E6=9E=84=E5=BB=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- JWT.py | 35 ++++++++-- model/database.py | 168 ++++++--------------------------------------- model/migration.py | 8 ++- model/response.py | 5 +- requirements.txt | Bin 2100 -> 2318 bytes routes/admin.py | 35 ++++++---- routes/session.py | 30 +++++--- 7 files changed, 101 insertions(+), 180 deletions(-) diff --git a/JWT.py b/JWT.py index 1a343f5..7fcd52a 100644 --- a/JWT.py +++ b/JWT.py @@ -1,12 +1,35 @@ from fastapi.security import OAuth2PasswordBearer -from model import database -import asyncio +from model import Setting +from model.database import Database oauth2_scheme = OAuth2PasswordBearer( scheme_name='获取 JWT Bearer 令牌', - description='用于获取 JWT Bearer 令牌,需要以表单的形式提交', + description='用于获取 JWT Bearer 令牌,需要以表单的形式提交', tokenUrl="/api/token" - ) +) -SECRET_KEY = asyncio.run(database.Database().get_setting('SECRET_KEY')) -ALGORITHM = "HS256" \ No newline at end of file +ALGORITHM = "HS256" + +# 延迟加载 SECRET_KEY +_SECRET_KEY_CACHE = None + +async def get_secret_key() -> str: + """ + 获取 JWT 密钥 + + :return: JWT 密钥字符串 + """ + global _SECRET_KEY_CACHE + + if _SECRET_KEY_CACHE is None: + async with Database.get_session() as session: + setting = await Setting.get( + session=session, + condition=(Setting.name == 'SECRET_KEY') + ) + if setting: + _SECRET_KEY_CACHE = setting.value + else: + raise RuntimeError("SECRET_KEY not found in database") + + return _SECRET_KEY_CACHE \ No newline at end of file diff --git a/model/database.py b/model/database.py index 244a872..cbcb623 100644 --- a/model/database.py +++ b/model/database.py @@ -1,15 +1,12 @@ +# ~/models/database.py from contextlib import asynccontextmanager -import aiosqlite -from datetime import datetime -from typing import Optional - -from sqlmodel import SQLModel -from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine -from sqlmodel.ext.asyncio.session import AsyncSession -from sqlalchemy.orm import sessionmaker from typing import AsyncGenerator -import warnings +from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine +from sqlalchemy.orm import sessionmaker +from sqlmodel import SQLModel +from sqlmodel.ext.asyncio.session import AsyncSession + from .migration import migration ASYNC_DATABASE_URL = "sqlite+aiosqlite:///data.db" @@ -19,161 +16,38 @@ engine: AsyncEngine = create_async_engine( echo=True, connect_args={ "check_same_thread": False - } if ASYNC_DATABASE_URL.startswith("sqlite") else None, + } if ASYNC_DATABASE_URL.startswith("sqlite") else {}, future=True, # pool_size=POOL_SIZE, # max_overflow=64, ) -_async_session_factory = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) +_async_session_factory = sessionmaker( + engine, class_=AsyncSession, expire_on_commit=False +) + # 数据库类 class Database: - # Database 初始化方法 def __init__( - self, # self 用于引用类的实例 - db_path: str = "data.db" # db_path 数据库文件路径,默认为 data.db + self, # self 用于引用类的实例 + db_path: str = "data.db", # db_path 数据库文件路径,默认为 data.db ): self.db_path = db_path - + @staticmethod - @asynccontextmanager async def get_session() -> AsyncGenerator[AsyncSession, None]: + """FastAPI dependency to get a database session.""" async with _async_session_factory() as session: yield session - async def init_db( - self, - url: str = ASYNC_DATABASE_URL - ): + async def init_db(self, url: str = ASYNC_DATABASE_URL): """创建数据库结构""" async with engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) - - async with self.get_session() as session: - await migration(session) # 执行迁移脚本 - - async def add_object(self, key: str, name: str, icon: str = None, phone: str = None): - """ - 添加新对象 - - :param key: 序列号 - :param name: 名称 - :param icon: 图标 - :param phone: 电话 - """ - async with aiosqlite.connect(self.db_path) as db: - async with db.execute("SELECT 1 FROM fr_objects WHERE key = ?", (key,)) as cursor: - if await cursor.fetchone(): - raise ValueError(f"序列号 {key} 已存在") - - now = datetime.now() - now = now.strftime("%Y-%m-%d %H:%M:%S") - await db.execute( - "INSERT INTO fr_objects (key, name, icon, phone, create_at, status) VALUES (?, ?, ?, ?, ?, 'ok')", - (key, name, icon, phone, now) - ) - await db.commit() - - async def update_object( - self, - id: int, - key: str = None, - name: str = None, - icon: str = None, - status: str = None, - phone: int = None, - lost_description: Optional[str] = None, - find_ip: Optional[str] = None, - lost_time: Optional[str] = None): - """ - 更新对象信息 - - :param id: 对象ID - :param key: 序列号 - :param name: 名称 - :param icon: 图标 - :param status: 状态 - :param phone: 电话 - :param lost_description: 丢失描述 - :param find_ip: 发现IP - :param lost_time: 丢失时间 - """ - async with aiosqlite.connect(self.db_path) as db: - async with db.execute("SELECT 1 FROM fr_objects WHERE id = ?", (id,)) as cursor: - if not await cursor.fetchone(): - raise ValueError(f"ID {id} 不存在") - - async with db.execute("SELECT 1 FROM fr_objects WHERE key = ? AND id != ?", (key, id)) as cursor: - if await cursor.fetchone(): - raise ValueError(f"序列号 {key} 已存在") - - await db.execute( - f"UPDATE fr_objects SET " - f"key = COALESCE(?, key), " - f"name = COALESCE(?, name), " - f"icon = COALESCE(?, icon), " - f"status = COALESCE(?, status), " - f"phone = COALESCE(?, phone), " - f"context = COALESCE(?, context), " - f"find_ip = COALESCE(?, find_ip), " - f"lost_at = COALESCE(?, lost_at) " - f"WHERE id = ?", - (key, name, icon, status, phone, lost_description, find_ip, lost_time, id) - ) - await db.commit() - - async def get_object(self, id: int = None, key: str = None): - """ - 获取对象 - - :param id: 对象ID - :param key: 序列号 - """ - async with aiosqlite.connect(self.db_path) as db: - if id is not None or key is not None: - async with db.execute( - "SELECT * FROM fr_objects WHERE id = ? OR key = ?", (id, key) - ) as cursor: - return await cursor.fetchone() - else: - async with db.execute("SELECT * FROM fr_objects") as cursor: - return await cursor.fetchall() - - async def delete_object(self, id: int): - """ - 删除对象 - - :param id: 对象ID - """ - async with aiosqlite.connect(self.db_path) as db: - await db.execute("DELETE FROM fr_objects WHERE id = ?", (id,)) - await db.commit() - - async def set_setting(self, name: str, value: str): - """ - 设置配置项 - - :param name: 配置项名称 - :param value: 配置项值 - """ - async with aiosqlite.connect(self.db_path) as db: - await db.execute( - "INSERT OR REPLACE INTO fr_settings (name, value) VALUES (?, ?)", - (name, value) - ) - await db.commit() - - async def get_setting(self, name: str): - """ - 获取配置项 - - :param name: 配置项名称 - """ - async with aiosqlite.connect(self.db_path) as db: - async with db.execute( - "SELECT value FROM fr_settings WHERE name = ?", (name,) - ) as cursor: - result = await cursor.fetchone() - return result[0] if result else None \ No newline at end of file + + # For internal use, create a temporary context manager + get_session_cm = asynccontextmanager(self.get_session) + async with get_session_cm() as session: + await migration(session) # 执行迁移脚本 \ No newline at end of file diff --git a/model/migration.py b/model/migration.py index c829735..a903a56 100644 --- a/model/migration.py +++ b/model/migration.py @@ -1,4 +1,4 @@ -from typing import Sequence +from loguru import logger from sqlmodel import select from .setting import Setting import tool @@ -13,9 +13,13 @@ async def migration(session): # 先准备基础配置 settings: list[Setting] = default_settings.copy() + if await Setting.get(session, Setting.name == 'version'): + # 已有数据,说明不是第一次运行,直接返回 + return + # 生成初始密码与密钥 admin_password = tool.generate_password() - print(f"密码(请牢记,后续不再显示): {admin_password}") + logger.warning(f"密码(请牢记,后续不再显示): {admin_password}") settings.append(Setting(type='string', name='password', value=tool.hash_password(admin_password))) settings.append(Setting(type='string', name='SECRET_KEY', value=tool.generate_password(64))) diff --git a/model/response.py b/model/response.py index a87dd32..bf6169a 100644 --- a/model/response.py +++ b/model/response.py @@ -14,4 +14,7 @@ class ObjectData(BaseModel): icon: str status: Literal['ok', 'lost'] phone: str - context: Optional[str] = None \ No newline at end of file + context: str | None = None + lost_description: str | None = None + create_time: str + lost_time: str | None = None \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 85b9a57e250855ee123e56a41d2a38a87200e41d..b1bd31fab1ba32531aceeebdc535456dd2550a39 100644 GIT binary patch delta 293 zcmX|+%?`m(5QR^vpGYKrR+U&{Cup&cxODhUH%Zhao6Ji~xAGom9o8vys8egnSbxboQ3!gm zt=1o5hyl8cXk)>ct1qfzsD&0Ir;)+|J2ERqnv(MVffUm~Gq}dHT1DkB`5Y5hKepK} Nhi3onp{+a;oev|ZH--QJ delta 104 zcmeAZ+9I%_ifQs3rfrkUnB6AdV$PGyW5{GkW=Lg7XD9`-Y=O{-L65qE0%qH(+Rgp<#C Literal[True]: +async def is_admin( + token: Annotated[str, Depends(JWT.oauth2_scheme)], + session: Annotated[AsyncSession, Depends(database.Database.get_session)] +) -> Literal[True]: ''' 验证是否为管理员。 @@ -24,9 +29,9 @@ async def is_admin(token: Annotated[str, Depends(JWT.oauth2_scheme)]) -> Literal ) try: - payload = jwt.decode(token, JWT.SECRET_KEY, algorithms=[JWT.ALGORITHM]) + payload = jwt.decode(token, JWT.get_secret_key(), algorithms=[JWT.ALGORITHM]) username = payload.get("sub") - if username is None or not await database.Database().get_setting('account') == username: + if username is None or not await Setting.get(session, Setting.name == 'account') == username: raise credentials_exception else: return True @@ -64,8 +69,8 @@ async def verity_admin() -> DefaultResponse: response_description='物品信息列表' ) async def get_items( - id: Optional[int] = Query(default=None, ge=1, description='物品ID'), - key: Optional[str] = Query(default=None, description='物品序列号')): + id: int | None = Query(default=None, ge=1, description='物品ID'), + key: str | None = Query(default=None, description='物品序列号')): ''' 获得物品信息。 @@ -80,7 +85,6 @@ async def get_items( items = results item = [] for i in items: - print(i) item.append(Item( id=i[0], type=i[1], @@ -144,14 +148,15 @@ async def add_items( ) async def update_items( id: int = Query(ge=1), - key: Optional[str] = None, - name: Optional[str] = None, - icon: Optional[str] = None, - status: Optional[str] = None, - phone: Optional[int] = None, - lost_description: Optional[str] = None, - find_ip: Optional[str] = None, - lost_time: Optional[str] = None) -> DefaultResponse: + key: str | None = None, + name: str | None = None, + icon: str | None = None, + status: str | None = None, + phone: int | None = None, + lost_description: str | None = None, + find_ip: str | None = None, + lost_time: str | None = None + ) -> DefaultResponse: ''' 更新物品信息。 diff --git a/routes/session.py b/routes/session.py index 6e006f9..10c091f 100644 --- a/routes/session.py +++ b/routes/session.py @@ -5,31 +5,38 @@ from fastapi import Depends, HTTPException from fastapi.security import OAuth2PasswordRequestForm from fastapi import APIRouter import jwt, JWT +from sqlmodel.ext.asyncio.session import AsyncSession +from tool import verify_password +from loguru import logger from model.token import Token from model import Setting, database -from tool import verify_password Router = APIRouter(tags=["令牌 session"]) # 创建令牌 -def create_access_token(data: dict, expires_delta: timedelta | None = None): +async def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta(minutes=15) to_encode.update({"exp": expire}) - encoded_jwt = jwt.encode(to_encode, JWT.SECRET_KEY, algorithm='HS256') + encoded_jwt = jwt.encode(to_encode, key=await JWT.get_secret_key(), algorithm='HS256') return encoded_jwt # 验证账号密码 -async def authenticate_user(username: str, password: str): +async def authenticate_user(session: AsyncSession, username: str, password: str): # 验证账号和密码 - account = await Setting.get('setting', 'account') - stored_password = await Setting.get('setting', 'password') + account = await Setting.get(session, Setting.name == 'account') + stored_password = await Setting.get(session, Setting.name == 'password') - if account != username or not verify_password(stored_password, password): + if not account or not stored_password: + logger.error("Account or password not set in settings.") + return False + + if account != username or not verify_password(stored_password.value, password): + logger.error("Invalid username or password.") return False return {'is_authenticated': True} @@ -44,8 +51,13 @@ async def authenticate_user(username: str, password: str): ) async def login_for_access_token( form_data: Annotated[OAuth2PasswordRequestForm, Depends()], + session: Annotated[AsyncSession, Depends(database.Database.get_session)], ) -> Token: - user = await authenticate_user(form_data.username, form_data.password) + user = await authenticate_user( + session=session, + username=form_data.username, + password=form_data.password + ) if not user: raise HTTPException( status_code=401, @@ -53,7 +65,7 @@ async def login_for_access_token( headers={"WWW-Authenticate": "Bearer"}, ) access_token_expires = timedelta(hours=1) - access_token = create_access_token( + access_token = await create_access_token( data={"sub": form_data.username}, expires_delta=access_token_expires ) return Token(access_token=access_token, token_type="bearer") \ No newline at end of file From 252031cb221e51ea62474873fa75074faafcb54a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E5=B0=8F=E4=B8=98?= Date: Fri, 3 Oct 2025 10:53:27 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B4=A6=E5=8F=B7?= =?UTF-8?q?=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/session.py b/routes/session.py index 10c091f..288396b 100644 --- a/routes/session.py +++ b/routes/session.py @@ -35,7 +35,7 @@ async def authenticate_user(session: AsyncSession, username: str, password: str) logger.error("Account or password not set in settings.") return False - if account != username or not verify_password(stored_password.value, password): + if account.value != username or not verify_password(stored_password.value, password): logger.error("Invalid username or password.") return False