feat: implement role-based access control for miniapp
All checks were successful
CI / lint-and-test (push) Successful in 22s
All checks were successful
CI / lint-and-test (push) Successful in 22s
- Introduced a new roles table in the database to manage user roles ('user' and 'admin') for access control.
- Updated the user model to include a foreign key reference to the roles table, allowing for role assignment.
- Enhanced command handlers to support the `/set_role` command for admins to assign roles to users.
- Refactored access control logic to utilize role checks instead of username/phone allowlists, improving security and maintainability.
- Updated documentation to reflect changes in access control mechanisms and role management.
- Added unit tests to ensure correct functionality of role assignment and access checks.
This commit is contained in:
@@ -9,7 +9,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
import duty_teller.config as config
|
||||
from duty_teller.api.telegram_auth import validate_init_data_with_reason
|
||||
from duty_teller.db.repository import get_duties, get_user_by_telegram_id
|
||||
from duty_teller.db.repository import (
|
||||
get_duties,
|
||||
get_user_by_telegram_id,
|
||||
can_access_miniapp_for_telegram_user,
|
||||
)
|
||||
from duty_teller.db.schemas import DUTY_EVENT_TYPES, DutyWithUser
|
||||
from duty_teller.db.session import session_scope
|
||||
from duty_teller.i18n import t
|
||||
@@ -181,22 +185,27 @@ def get_authenticated_username(
|
||||
raise HTTPException(
|
||||
status_code=403, detail=_auth_error_detail(auth_reason, lang)
|
||||
)
|
||||
if username and config.can_access_miniapp(username):
|
||||
return username
|
||||
failed_phone: str | None = None
|
||||
if telegram_user_id is not None:
|
||||
user = get_user_by_telegram_id(session, telegram_user_id)
|
||||
if user and user.phone and config.can_access_miniapp_by_phone(user.phone):
|
||||
return username or (user.full_name or "") or f"id:{telegram_user_id}"
|
||||
if user and user.phone:
|
||||
failed_phone = config.normalize_phone(user.phone)
|
||||
log.warning(
|
||||
"username/phone not in allowlist (username=%s, telegram_id=%s, phone=%s)",
|
||||
username,
|
||||
telegram_user_id,
|
||||
failed_phone if failed_phone else "—",
|
||||
)
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
if telegram_user_id is None:
|
||||
log.warning("initData valid but telegram_user_id missing")
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
user = get_user_by_telegram_id(session, telegram_user_id)
|
||||
if not user:
|
||||
log.warning(
|
||||
"user not in DB (username=%s, telegram_id=%s)",
|
||||
username,
|
||||
telegram_user_id,
|
||||
)
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
if not can_access_miniapp_for_telegram_user(session, telegram_user_id):
|
||||
failed_phone = config.normalize_phone(user.phone) if user.phone else None
|
||||
log.warning(
|
||||
"access denied (username=%s, telegram_id=%s, phone=%s)",
|
||||
username,
|
||||
telegram_user_id,
|
||||
failed_phone or "—",
|
||||
)
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
return username or (user.full_name or "") or f"id:{telegram_user_id}"
|
||||
|
||||
|
||||
def fetch_duties_response(
|
||||
|
||||
Reference in New Issue
Block a user