mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-23 16:35:59 +00:00
feat(auth): introduce backend auth module
Port RFC-001 authentication core from PR #1728: - JWT token handling (create_access_token, decode_token, TokenPayload) - Password hashing (bcrypt) with verify_password - SQLite UserRepository with base interface - Provider Factory pattern (LocalAuthProvider) - CLI reset_admin tool - Auth-specific errors (AuthErrorCode, TokenError, AuthErrorResponse) Deps: - bcrypt>=4.0.0 - pyjwt>=2.9.0 - email-validator>=2.0.0 - backend/uv.toml pins public PyPI index Tests: 12 pure unit tests (test_auth_config.py, test_auth_errors.py). Scope note: authz.py, test_auth.py, and test_auth_type_system.py are deferred to commit 2 because they depend on middleware and deps wiring that is not yet in place. Commit 1 stays "pure new files only" as the spec mandates.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
"""CLI tool to reset admin password.
|
||||
|
||||
Usage:
|
||||
python -m app.gateway.auth.reset_admin
|
||||
python -m app.gateway.auth.reset_admin --email admin@example.com
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import secrets
|
||||
import sys
|
||||
|
||||
from app.gateway.auth.password import hash_password
|
||||
from app.gateway.auth.repositories.sqlite import SQLiteUserRepository
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Reset admin password")
|
||||
parser.add_argument("--email", help="Admin email (default: first admin found)")
|
||||
args = parser.parse_args()
|
||||
|
||||
repo = SQLiteUserRepository()
|
||||
|
||||
# Find admin user synchronously (CLI context, no event loop)
|
||||
import asyncio
|
||||
|
||||
user = asyncio.run(_find_admin(repo, args.email))
|
||||
if user is None:
|
||||
if args.email:
|
||||
print(f"Error: user '{args.email}' not found.", file=sys.stderr)
|
||||
else:
|
||||
print("Error: no admin user found.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
new_password = secrets.token_urlsafe(16)
|
||||
user.password_hash = hash_password(new_password)
|
||||
user.token_version += 1
|
||||
user.needs_setup = True
|
||||
asyncio.run(repo.update_user(user))
|
||||
|
||||
print(f"Password reset for: {user.email}")
|
||||
print(f"New password: {new_password}")
|
||||
print("Next login will require setup (new email + password).")
|
||||
|
||||
|
||||
async def _find_admin(repo: SQLiteUserRepository, email: str | None):
|
||||
if email:
|
||||
return await repo.get_user_by_email(email)
|
||||
# Find first admin
|
||||
import asyncio
|
||||
|
||||
from app.gateway.auth.repositories.sqlite import _get_users_conn
|
||||
|
||||
def _find_sync():
|
||||
with _get_users_conn() as conn:
|
||||
cursor = conn.execute("SELECT id FROM users WHERE system_role = 'admin' LIMIT 1")
|
||||
row = cursor.fetchone()
|
||||
return dict(row)["id"] if row else None
|
||||
|
||||
admin_id = await asyncio.to_thread(_find_sync)
|
||||
if admin_id:
|
||||
return await repo.get_user_by_id(admin_id)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user