Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f5550d5
Initial plan
Copilot Nov 2, 2025
4962cf6
Add FastAPI dependencies and initial migration setup
Copilot Nov 2, 2025
92585fc
Update log, update, and tools routes to FastAPI
Copilot Nov 2, 2025
a2b5bd4
Update file, t2i, and conversation routes to FastAPI
Copilot Nov 2, 2025
aa6dea2
Update persona and session_management routes to FastAPI
Copilot Nov 2, 2025
767a9b9
Complete route migration from Quart to FastAPI
Copilot Nov 2, 2025
b35b624
WIP: FastAPI migration - core structure complete, needs cleanup
Copilot Nov 2, 2025
549bc5a
Move imports to top of auth.py and clarify MD5 password handling
Copilot Nov 2, 2025
be15a23
Implement Argon2 password hashing for MD5 values with auto-migration
Copilot Nov 2, 2025
e28e6a8
修正
LIghtJUNction Nov 2, 2025
0eaeddf
Update plugin.py
LIghtJUNction Nov 2, 2025
27b7212
Initial plan
Copilot Nov 2, 2025
f80ef08
Fix StaticFileRoute to use FastAPI methods instead of Quart
Copilot Nov 2, 2025
b637b78
Merge branch 'copilot/update-backend-to-fastapi' of https://github.co…
LIghtJUNction Nov 2, 2025
aaecb34
Update auth.py
LIghtJUNction Nov 2, 2025
e01566a
Fix asyncio.sleep to anyio.sleep after rebase
Copilot Nov 2, 2025
45fd17d
anyio
LIghtJUNction Nov 2, 2025
d7b5740
anyio
LIghtJUNction Nov 2, 2025
b7d8fc2
Merge branch 'copilot/update-backend-to-fastapi' of https://github.co…
LIghtJUNction Nov 2, 2025
e7b5884
Remove custom cryptographic functions, use only pwdlib
Copilot Nov 2, 2025
3de1bf1
Update route.py
LIghtJUNction Nov 2, 2025
779cd79
Add graceful handling for old password format migration
Copilot Nov 2, 2025
d148a3e
Merge branch 'copilot/update-backend-to-fastapi' of https://github.co…
LIghtJUNction Nov 2, 2025
8f6d3e6
Update route.py
LIghtJUNction Nov 2, 2025
cb60b7d
Changes before error encountered
Copilot Nov 2, 2025
4e59708
Fix mypy type errors in dashboard code
Copilot Nov 2, 2025
333c833
修正错误
LIghtJUNction Nov 2, 2025
ae4a49e
Merge branch 'copilot/update-backend-to-fastapi' of https://github.co…
LIghtJUNction Nov 2, 2025
f0ad416
搁置到今后完善
LIghtJUNction Nov 2, 2025
ce811d9
Fix plugin.py: Remove all .__dict__ and .model_dump() calls
Copilot Nov 2, 2025
6a07c6a
Fix t2i.py: Remove all Quart patterns, add Request parameters
Copilot Nov 2, 2025
daacb61
Fix log.py and auth.py: Remove all .__dict__ calls
Copilot Nov 2, 2025
5e55ef6
Complete Response pattern migration for all remaining route files
Copilot Nov 2, 2025
fa752af
Addressing PR comments
Copilot Nov 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions astrbot/core/db/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""User models for fastapi-users authentication."""

import uuid

from fastapi_users.db import SQLAlchemyBaseUserTableUUID
from sqlmodel import SQLModel


class User(SQLAlchemyBaseUserTableUUID):
"""User model for authentication with fastapi-users.

This model extends SQLAlchemyBaseUserTableUUID which provides:
- id: UUID primary key
- email: str
- hashed_password: str
- is_active: bool
- is_superuser: bool
- is_verified: bool
"""

# The base class provides these fields automatically:
# id: uuid.UUID
# email: str
# hashed_password: str
# is_active: bool = True
# is_superuser: bool = False
# is_verified: bool = False

__tablename__ = "user"


class UserRead(SQLModel):
"""Schema for reading user data."""

id: uuid.UUID
email: str
username: str
is_active: bool = True
is_superuser: bool = False
is_verified: bool = False


class UserCreate(SQLModel):
"""Schema for creating a new user."""

email: str
username: str
password: str


class UserUpdate(SQLModel):
"""Schema for updating user data."""

email: str | None = None
username: str | None = None
password: str | None = None
5 changes: 3 additions & 2 deletions astrbot/core/pipeline/preprocess_stage/stage.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import asyncio
import random
import traceback
from collections.abc import AsyncGenerator

import anyio

from astrbot.core import logger
from astrbot.core.message.components import Image, Plain, Record
from astrbot.core.platform.astr_message_event import AstrMessageEvent
Expand Down Expand Up @@ -92,7 +93,7 @@ async def process(
# napcat workaround
logger.warning(e)
logger.warning(f"重试中: {i + 1}/{retry}")
await asyncio.sleep(0.5)
await anyio.sleep(0.5)
continue
except BaseException as e:
logger.error(traceback.format_exc())
Expand Down
5 changes: 3 additions & 2 deletions astrbot/core/pipeline/respond/stage.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import asyncio
import math
import random
from collections.abc import AsyncGenerator

import anyio

import astrbot.core.message.components as Comp
from astrbot.core import logger
from astrbot.core.message.components import BaseMessageComponent, ComponentType
Expand Down Expand Up @@ -221,7 +222,7 @@ async def process(
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
for comp in result.chain:
i = await self._calc_comp_interval(comp)
await asyncio.sleep(i)
await anyio.sleep(i)
try:
if comp.type in need_separately:
await event.send(MessageChain([comp]))
Expand Down
97 changes: 97 additions & 0 deletions astrbot/dashboard/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""FastAPI-Users authentication configuration."""

import uuid

from fastapi import Depends, Request
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin
from fastapi_users.authentication import (
AuthenticationBackend,
BearerTransport,
JWTStrategy,
)
from fastapi_users.db import SQLAlchemyUserDatabase
from pydantic import SecretStr
from pwdlib import PasswordHash
from pwdlib.hashers.argon2 import Argon2Hasher
from sqlalchemy.ext.asyncio import AsyncSession

from astrbot.core import logger
from astrbot.core.db.user import User


class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
"""User manager for handling user operations."""

# Base class requires these to be str | SecretStr, not optional
reset_password_token_secret: str | SecretStr = ""
verification_token_secret: str | SecretStr = ""

def __init__(self, user_db: SQLAlchemyUserDatabase, jwt_secret: str):
# Use Argon2 for password hashing with salt
# The MD5 value from frontend will be treated as the "password" and hashed with Argon2
password_hash = PasswordHash((Argon2Hasher(),))
super().__init__(user_db, password_hash) # type: ignore[arg-type]
self.reset_password_token_secret = jwt_secret
self.verification_token_secret = jwt_secret

async def on_after_register(self, user: User, request: Request | None = None):
"""Called after a user registers."""
logger.info(f"User {user.id} has registered.")

async def on_after_forgot_password(
self, user: User, token: str, request: Request | None = None
):
"""Called after a user requests password reset."""
logger.info(f"User {user.id} has forgot their password. Reset token: {token}")

async def on_after_request_verify(
self, user: User, token: str, request: Request | None = None
):
"""Called after a user requests verification."""
logger.info(
f"Verification requested for user {user.id}. Verification token: {token}"
)


async def get_user_db(session: AsyncSession):
"""Dependency to get user database."""
yield SQLAlchemyUserDatabase(session, User)


async def get_user_manager(
user_db: SQLAlchemyUserDatabase = Depends(get_user_db),
jwt_secret: str = Depends(lambda: None),
):
"""Dependency to get user manager."""
yield UserManager(user_db, jwt_secret)


def get_jwt_strategy(jwt_secret: str) -> JWTStrategy:
"""Get JWT strategy with the provided secret."""
return JWTStrategy(secret=jwt_secret, lifetime_seconds=7 * 24 * 60 * 60) # 7 days


def get_auth_backend(jwt_secret: str) -> AuthenticationBackend:
"""Get authentication backend."""
bearer_transport = BearerTransport(tokenUrl="api/auth/login")

return AuthenticationBackend(
name="jwt",
transport=bearer_transport,
get_strategy=lambda: get_jwt_strategy(jwt_secret),
)


def setup_fastapi_users(
jwt_secret: str,
) -> tuple[FastAPIUsers[User, uuid.UUID], AuthenticationBackend]:
"""Setup FastAPI Users instance."""
auth_backend = get_auth_backend(jwt_secret)

# Note: get_user_manager will be passed when registering routes
fastapi_users = FastAPIUsers[User, uuid.UUID](
get_user_manager,
[auth_backend],
)

return fastapi_users, auth_backend
Loading
Loading