Fix .gitignore and add missing authentication source files
This commit fixes overly broad .gitignore patterns that were excluding important source code files from version control. Previously, wildcard patterns like *auth*, *token*, *secret*, *connection*, and *credential* were excluding ALL files containing these words, including critical application code. Changes: - Updated .gitignore with specific patterns for sensitive config files (*.json, *.txt, *.yml, *.yaml extensions only) - Removed broad wildcards that excluded source code files Added missing source files: - shared/auth/ (9 files): Complete authentication system - JWT handler, middleware, auth service, models, routes - reports-app/backend/app/routers/auth.py: Authentication API router - reports-app/backend/app/auth_middleware_wrapper.py: Middleware wrapper - reports-app/frontend/src/stores/auth.js: Vue.js auth store - reports-app/frontend/tests/: E2E tests and fixtures for auth - reports-app/telegram-bot/app/auth/: Telegram auth linking module - deployment/windows/scripts/Setup-ClaudeAuth.ps1: Windows deployment script - security/secrets_scanner.py: Security scanning utility These files are essential for the application to function and should have been included in the initial commit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
412
shared/auth/dependencies.py
Normal file
412
shared/auth/dependencies.py
Normal file
@@ -0,0 +1,412 @@
|
||||
"""
|
||||
FastAPI Authentication Dependencies pentru ROA2WEB
|
||||
|
||||
Acest modul oferă dependency functions pentru FastAPI care pot fi folosite
|
||||
pentru a proteja endpoint-urile și a obține informații despre utilizatorul curent.
|
||||
|
||||
Dependencies disponibile:
|
||||
- get_current_user: Obține utilizatorul curent (obligatoriu)
|
||||
- get_optional_user: Obține utilizatorul curent (opțional)
|
||||
- require_company_access: Verifică accesul la o firmă specifică
|
||||
- require_permissions: Verifică permisiunile necesare
|
||||
- get_current_company: Obține firma curentă din context
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, List, Callable, Any
|
||||
from functools import wraps
|
||||
|
||||
from fastapi import Depends, HTTPException, status, Request
|
||||
from fastapi.security import HTTPAuthorizationCredentials
|
||||
|
||||
from .middleware import security_required, security_optional
|
||||
from .jwt_handler import jwt_handler, TokenData
|
||||
from .auth_service import auth_service
|
||||
from .models import CurrentUser, PermissionType, AuthError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthenticationRequired(Exception):
|
||||
"""Excepție pentru când autentificarea este obligatorie"""
|
||||
pass
|
||||
|
||||
|
||||
class InsufficientPermissions(Exception):
|
||||
"""Excepție pentru permisiuni insuficiente"""
|
||||
pass
|
||||
|
||||
|
||||
class CompanyAccessDenied(Exception):
|
||||
"""Excepție pentru acces refuzat la firmă"""
|
||||
pass
|
||||
|
||||
|
||||
async def get_current_user_from_token(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security_required)
|
||||
) -> CurrentUser:
|
||||
"""
|
||||
Extrage și validează utilizatorul curent din token JWT
|
||||
|
||||
Args:
|
||||
credentials: Credențialele HTTP de autentificare din header
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent autentificat
|
||||
|
||||
Raises:
|
||||
HTTPException: Dacă token-ul este invalid sau utilizatorul nu există
|
||||
"""
|
||||
if not credentials:
|
||||
logger.warning("No credentials provided for protected endpoint")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Validează token-ul
|
||||
token_data = jwt_handler.verify_token(credentials.credentials)
|
||||
|
||||
if not token_data:
|
||||
logger.warning("Invalid token provided")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if token_data.token_type != "access":
|
||||
logger.warning(f"Invalid token type: {token_data.token_type}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token type",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Creează obiectul CurrentUser
|
||||
current_user = CurrentUser(
|
||||
username=token_data.username,
|
||||
user_id=token_data.user_id,
|
||||
companies=token_data.companies,
|
||||
permissions=token_data.permissions
|
||||
)
|
||||
|
||||
logger.debug(f"Successfully authenticated user: {current_user.username}")
|
||||
return current_user
|
||||
|
||||
|
||||
async def get_current_user_from_request(request: Request) -> CurrentUser:
|
||||
"""
|
||||
Obține utilizatorul curent din request state (setat de middleware)
|
||||
|
||||
Args:
|
||||
request: Request-ul HTTP curent
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent autentificat
|
||||
|
||||
Raises:
|
||||
HTTPException: Dacă utilizatorul nu este autentificat
|
||||
"""
|
||||
print(f"[DEPENDENCY DEBUG] get_current_user_from_request called")
|
||||
print(f"[DEPENDENCY DEBUG] request.state attributes: {dir(request.state)}")
|
||||
print(f"[DEPENDENCY DEBUG] has is_authenticated: {hasattr(request.state, 'is_authenticated')}")
|
||||
print(f"[DEPENDENCY DEBUG] is_authenticated value: {getattr(request.state, 'is_authenticated', 'NOT_SET')}")
|
||||
print(f"[DEPENDENCY DEBUG] has user: {hasattr(request.state, 'user')}")
|
||||
print(f"[DEPENDENCY DEBUG] user value: {getattr(request.state, 'user', 'NOT_SET')}")
|
||||
|
||||
if not hasattr(request.state, 'is_authenticated') or not request.state.is_authenticated:
|
||||
print(f"[DEPENDENCY DEBUG] Returning 401: Authentication required")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if not hasattr(request.state, 'user') or not request.state.user:
|
||||
print(f"[DEPENDENCY DEBUG] Returning 401: User not found in request")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="User not found in request",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
print(f"[DEPENDENCY DEBUG] Returning user: {request.state.user}")
|
||||
return request.state.user
|
||||
|
||||
|
||||
async def get_optional_user_from_request(request: Request) -> Optional[CurrentUser]:
|
||||
"""
|
||||
Obține utilizatorul curent din request (opțional)
|
||||
|
||||
Args:
|
||||
request: Request-ul HTTP curent
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent sau None dacă nu este autentificat
|
||||
"""
|
||||
if (hasattr(request.state, 'is_authenticated') and
|
||||
request.state.is_authenticated and
|
||||
hasattr(request.state, 'user')):
|
||||
return request.state.user
|
||||
|
||||
return None
|
||||
|
||||
|
||||
async def get_optional_user_from_token(
|
||||
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional)
|
||||
) -> Optional[CurrentUser]:
|
||||
"""
|
||||
Extrage utilizatorul curent din token (opțional)
|
||||
|
||||
Args:
|
||||
credentials: Credențialele HTTP Bearer (opționale)
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent sau None
|
||||
"""
|
||||
if not credentials:
|
||||
return None
|
||||
|
||||
try:
|
||||
return await get_current_user_from_token(credentials)
|
||||
except HTTPException:
|
||||
return None
|
||||
|
||||
|
||||
def require_company_access(company_code: str):
|
||||
"""
|
||||
Dependency factory care verifică accesul la o firmă specifică
|
||||
|
||||
Args:
|
||||
company_code: Codul firmei la care se verifică accesul
|
||||
|
||||
Returns:
|
||||
Dependency function pentru FastAPI
|
||||
"""
|
||||
async def check_company_access(
|
||||
current_user: CurrentUser = Depends(get_current_user_from_request)
|
||||
) -> CurrentUser:
|
||||
"""
|
||||
Verifică dacă utilizatorul curent are acces la firma specificată
|
||||
|
||||
Args:
|
||||
current_user: Utilizatorul curent autentificat
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent dacă are acces
|
||||
|
||||
Raises:
|
||||
HTTPException: Dacă nu are acces la firmă
|
||||
"""
|
||||
if company_code not in current_user.companies:
|
||||
logger.warning(
|
||||
f"User {current_user.username} attempted to access "
|
||||
f"unauthorized company {company_code}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Access denied to company {company_code}"
|
||||
)
|
||||
|
||||
# Verifică și în baza de date pentru siguranță
|
||||
has_access = await auth_service.validate_user_company_access(
|
||||
current_user.username, company_code
|
||||
)
|
||||
|
||||
if not has_access:
|
||||
logger.error(
|
||||
f"Database access check failed for user {current_user.username} "
|
||||
f"and company {company_code}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Database access denied to company {company_code}"
|
||||
)
|
||||
|
||||
logger.debug(f"User {current_user.username} granted access to company {company_code}")
|
||||
return current_user
|
||||
|
||||
return check_company_access
|
||||
|
||||
|
||||
def require_permissions(required_permissions: List[PermissionType]):
|
||||
"""
|
||||
Dependency factory care verifică permisiunile necesare
|
||||
|
||||
Args:
|
||||
required_permissions: Lista permisiunilor necesare
|
||||
|
||||
Returns:
|
||||
Dependency function pentru FastAPI
|
||||
"""
|
||||
async def check_permissions(
|
||||
current_user: CurrentUser = Depends(get_current_user_from_request)
|
||||
) -> CurrentUser:
|
||||
"""
|
||||
Verifică dacă utilizatorul are permisiunile necesare
|
||||
|
||||
Args:
|
||||
current_user: Utilizatorul curent autentificat
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent dacă are permisiunile
|
||||
|
||||
Raises:
|
||||
HTTPException: Dacă nu are permisiunile necesare
|
||||
"""
|
||||
user_permissions = set(current_user.permissions)
|
||||
missing_permissions = [
|
||||
perm for perm in required_permissions
|
||||
if perm not in user_permissions
|
||||
]
|
||||
|
||||
if missing_permissions:
|
||||
logger.warning(
|
||||
f"User {current_user.username} missing permissions: {missing_permissions}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Missing required permissions: {missing_permissions}"
|
||||
)
|
||||
|
||||
logger.debug(f"User {current_user.username} has required permissions")
|
||||
return current_user
|
||||
|
||||
return check_permissions
|
||||
|
||||
|
||||
def require_company_and_permissions(
|
||||
company_code: str,
|
||||
required_permissions: List[PermissionType]
|
||||
):
|
||||
"""
|
||||
Dependency factory care verifică atât accesul la firmă cât și permisiunile
|
||||
|
||||
Args:
|
||||
company_code: Codul firmei
|
||||
required_permissions: Lista permisiunilor necesare
|
||||
|
||||
Returns:
|
||||
Dependency function pentru FastAPI
|
||||
"""
|
||||
async def check_company_and_permissions(
|
||||
current_user: CurrentUser = Depends(get_current_user_from_request)
|
||||
) -> CurrentUser:
|
||||
"""
|
||||
Verifică accesul la firmă și permisiunile
|
||||
|
||||
Args:
|
||||
current_user: Utilizatorul curent
|
||||
|
||||
Returns:
|
||||
Utilizatorul curent dacă are acces și permisiuni
|
||||
"""
|
||||
# Verifică accesul la firmă
|
||||
company_checker = require_company_access(company_code)
|
||||
await company_checker(current_user)
|
||||
|
||||
# Verifică permisiunile
|
||||
permissions_checker = require_permissions(required_permissions)
|
||||
await permissions_checker(current_user)
|
||||
|
||||
return current_user
|
||||
|
||||
return check_company_and_permissions
|
||||
|
||||
|
||||
async def get_current_company_from_header(
|
||||
request: Request,
|
||||
current_user: CurrentUser = Depends(get_current_user_from_request)
|
||||
) -> str:
|
||||
"""
|
||||
Obține codul firmei curente din header-ul X-Company-Code
|
||||
|
||||
Args:
|
||||
request: Request-ul HTTP
|
||||
current_user: Utilizatorul curent
|
||||
|
||||
Returns:
|
||||
Codul firmei curente
|
||||
|
||||
Raises:
|
||||
HTTPException: Dacă header-ul lipsește sau utilizatorul nu are acces
|
||||
"""
|
||||
company_code = request.headers.get("X-Company-Code")
|
||||
|
||||
if not company_code:
|
||||
# Folosește prima firmă ca default dacă nu este specificată
|
||||
if current_user.companies:
|
||||
company_code = current_user.companies[0]
|
||||
logger.debug(f"Using default company {company_code} for user {current_user.username}")
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Company code required (X-Company-Code header or user default)"
|
||||
)
|
||||
|
||||
# Verifică accesul
|
||||
if company_code not in current_user.companies:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Access denied to company {company_code}"
|
||||
)
|
||||
|
||||
return company_code
|
||||
|
||||
|
||||
# Aliasuri pentru folosire mai ușoară
|
||||
get_current_user = get_current_user_from_request
|
||||
get_optional_user = get_optional_user_from_request
|
||||
|
||||
# Dependency-uri predefinite pentru permisiuni comune
|
||||
require_read_permission = require_permissions([PermissionType.READ])
|
||||
require_write_permission = require_permissions([PermissionType.WRITE])
|
||||
require_admin_permission = require_permissions([PermissionType.ADMIN])
|
||||
require_reports_permission = require_permissions([PermissionType.REPORTS])
|
||||
|
||||
# Decorator pentru validarea companiei în funcții
|
||||
def validate_company_access(company_param: str = "company"):
|
||||
"""
|
||||
Decorator pentru validarea automată a accesului la firmă
|
||||
|
||||
Args:
|
||||
company_param: Numele parametrului care conține codul firmei
|
||||
|
||||
Returns:
|
||||
Decorator function
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
# Caută utilizatorul curent în argumentele funcției
|
||||
current_user = None
|
||||
for arg in args:
|
||||
if isinstance(arg, CurrentUser):
|
||||
current_user = arg
|
||||
break
|
||||
|
||||
if not current_user:
|
||||
# Caută în kwargs
|
||||
current_user = kwargs.get('current_user')
|
||||
|
||||
if not current_user:
|
||||
raise ValueError("CurrentUser not found in function arguments")
|
||||
|
||||
# Obține codul firmei
|
||||
company_code = kwargs.get(company_param)
|
||||
if not company_code:
|
||||
raise ValueError(f"Company parameter '{company_param}' not found")
|
||||
|
||||
# Validează accesul
|
||||
if company_code not in current_user.companies:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Access denied to company {company_code}"
|
||||
)
|
||||
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
Reference in New Issue
Block a user