added multi user support, move api key to database
build-and-push / build (push) Failing after 35s

This commit is contained in:
2026-08-01 13:16:54 +05:00
parent a7c5fe739a
commit 66073c7891
23 changed files with 2123 additions and 103 deletions
+94
View File
@@ -0,0 +1,94 @@
"""Login, logout, current-user and self-service password change.
``POST /auth/login`` is the only unauthenticated endpoint here; the rest resolve
the caller from their Bearer session token.
"""
from fastapi import APIRouter, Depends, Request, Security
from sqlalchemy import select
from sqlalchemy.orm import Session as DbSession
from .. import auth
from ..auth import bearer_scheme, get_current_admin
from ..database import get_db
from ..errors import APIError
from ..models import Admin, Session
from ..schemas import (
ChangePasswordRequest,
CurrentUser,
LoginRequest,
LoginResponse,
)
router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/login", response_model=LoginResponse)
def login(payload: LoginRequest, request: Request, db: DbSession = Depends(get_db)):
admin = db.execute(
select(Admin).where(Admin.username == payload.username)
).scalar_one_or_none()
if admin is None or not auth.verify_password(payload.password, admin.password_hash):
# Generic message so we don't leak which usernames exist.
raise APIError(status_code=401, detail="Invalid username or password")
token = auth.create_session(db, admin)
auth.record_log(
db, username=admin.username, action="login", detail="Signed in", request=request
)
return LoginResponse(
token=token,
username=admin.username,
is_admin=admin.is_admin,
must_change_password=admin.must_change_password,
)
@router.post("/logout", status_code=204)
def logout(
request: Request,
admin: Admin = Depends(get_current_admin),
creds=Security(bearer_scheme),
db: DbSession = Depends(get_db),
):
if creds and creds.credentials:
session = db.get(Session, creds.credentials)
if session is not None:
db.delete(session)
db.commit()
auth.record_log(db, username=admin.username, action="logout", detail="Signed out", request=request)
@router.get("/me", response_model=CurrentUser)
def me(admin: Admin = Depends(get_current_admin)):
return admin
@router.post("/change-password", status_code=204)
def change_password(
payload: ChangePasswordRequest,
request: Request,
admin: Admin = Depends(get_current_admin),
db: DbSession = Depends(get_db),
):
"""Change your OWN password. Any authenticated user may do this.
When the account is flagged ``must_change_password`` (a forced first-login or
post-reset change) the current password is not required — the user just
authenticated with it. A normal self-service change still verifies it.
"""
if not admin.must_change_password:
if not payload.current_password or not auth.verify_password(
payload.current_password, admin.password_hash
):
raise APIError(status_code=400, detail="Current password is incorrect")
admin.password_hash = auth.hash_password(payload.new_password)
admin.must_change_password = False
db.commit()
auth.record_log(
db,
username=admin.username,
action="change_password",
detail="Changed own password",
request=request,
)