Implement phone number normalization and access control for Telegram users
- Added functionality to normalize phone numbers for comparison, ensuring only digits are stored and checked. - Updated configuration to include optional phone number allowlists for users and admins in the environment settings. - Enhanced authentication logic to allow access based on normalized phone numbers, in addition to usernames. - Introduced new helper functions for parsing and validating phone numbers, improving code organization and maintainability. - Added unit tests to validate phone normalization and access control based on phone numbers.
This commit is contained in:
@@ -4,12 +4,12 @@ import logging
|
||||
import re
|
||||
from typing import Annotated, Generator
|
||||
|
||||
from fastapi import Header, HTTPException, Query, Request
|
||||
from fastapi import Depends, Header, HTTPException, Query, Request
|
||||
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
|
||||
from duty_teller.db.repository import get_duties, get_user_by_telegram_id
|
||||
from duty_teller.db.schemas import DutyWithUser
|
||||
from duty_teller.db.session import session_scope
|
||||
from duty_teller.i18n import t
|
||||
@@ -74,8 +74,9 @@ def require_miniapp_username(
|
||||
x_telegram_init_data: Annotated[
|
||||
str | None, Header(alias="X-Telegram-Init-Data")
|
||||
] = None,
|
||||
session: Session = Depends(get_db_session),
|
||||
) -> str:
|
||||
return get_authenticated_username(request, x_telegram_init_data)
|
||||
return get_authenticated_username(request, x_telegram_init_data, session)
|
||||
|
||||
|
||||
def _is_private_client(client_host: str | None) -> bool:
|
||||
@@ -95,31 +96,43 @@ def _is_private_client(client_host: str | None) -> bool:
|
||||
|
||||
|
||||
def get_authenticated_username(
|
||||
request: Request, x_telegram_init_data: str | None
|
||||
request: Request,
|
||||
x_telegram_init_data: str | None,
|
||||
session: Session,
|
||||
) -> str:
|
||||
"""Return identifier for miniapp auth (username or full_name or id:...); empty if skip-auth."""
|
||||
if config.MINI_APP_SKIP_AUTH:
|
||||
log.warning("allowing without any auth check (MINI_APP_SKIP_AUTH is set)")
|
||||
return ""
|
||||
init_data = (x_telegram_init_data or "").strip()
|
||||
if not init_data:
|
||||
client_host = request.client.host if request.client else None
|
||||
if _is_private_client(client_host) or config.MINI_APP_SKIP_AUTH:
|
||||
if config.MINI_APP_SKIP_AUTH:
|
||||
log.warning("allowing without initData (MINI_APP_SKIP_AUTH is set)")
|
||||
if _is_private_client(client_host):
|
||||
return ""
|
||||
log.warning("no X-Telegram-Init-Data header (client=%s)", client_host)
|
||||
lang = _lang_from_accept_language(request.headers.get("Accept-Language"))
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.open_from_telegram"))
|
||||
max_age = config.INIT_DATA_MAX_AGE_SECONDS or None
|
||||
username, auth_reason, lang = validate_init_data_with_reason(
|
||||
telegram_user_id, username, auth_reason, lang = validate_init_data_with_reason(
|
||||
init_data, config.BOT_TOKEN, max_age_seconds=max_age
|
||||
)
|
||||
if username is None:
|
||||
if auth_reason != "ok":
|
||||
log.warning("initData validation failed: %s", auth_reason)
|
||||
raise HTTPException(
|
||||
status_code=403, detail=_auth_error_detail(auth_reason, lang)
|
||||
)
|
||||
if not config.can_access_miniapp(username):
|
||||
log.warning("username not in allowlist: %s", username)
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
return username
|
||||
if username and config.can_access_miniapp(username):
|
||||
return username
|
||||
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}"
|
||||
log.warning(
|
||||
"username/phone not in allowlist (username=%s, telegram_id=%s)",
|
||||
username,
|
||||
telegram_user_id,
|
||||
)
|
||||
raise HTTPException(status_code=403, detail=t(lang, "api.access_denied"))
|
||||
|
||||
|
||||
def fetch_duties_response(
|
||||
|
||||
Reference in New Issue
Block a user