feat: implement pivot-currency model, rbac smart tokens & fix circular imports
This commit is contained in:
0
backend/app/core/__pycache__/__init__.cpython-312.pyc
Executable file → Normal file
0
backend/app/core/__pycache__/__init__.cpython-312.pyc
Executable file → Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
import os
|
||||
import json
|
||||
from typing import Any, Optional, List
|
||||
from typing import Any, Optional
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -10,41 +9,38 @@ class Settings(BaseSettings):
|
||||
PROJECT_NAME: str = "Traffic Ecosystem SuperApp"
|
||||
VERSION: str = "1.0.0"
|
||||
API_V1_STR: str = "/api/v1"
|
||||
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
|
||||
DEBUG: bool = False
|
||||
|
||||
# --- Security / JWT ---
|
||||
# Szigorúan .env-ből!
|
||||
SECRET_KEY: str = os.getenv("SECRET_KEY", "NOT_SET_DANGER")
|
||||
SECRET_KEY: str = "NOT_SET_DANGER"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7 nap
|
||||
|
||||
# --- Database & Cache ---
|
||||
DATABASE_URL: str = os.getenv("DATABASE_URL")
|
||||
REDIS_URL: str = os.getenv("REDIS_URL", "redis://service_finder_redis:6379/0")
|
||||
# --- Initial Admin (ÚJ SZEKCIÓ) ---
|
||||
# Ezeket a .env-ből fogja venni
|
||||
INITIAL_ADMIN_EMAIL: str = "admin@servicefinder.hu"
|
||||
INITIAL_ADMIN_PASSWORD: str = "Admin123!"
|
||||
|
||||
# --- Email (Auto Provider) ---
|
||||
EMAIL_PROVIDER: str = os.getenv("EMAIL_PROVIDER", "auto")
|
||||
EMAILS_FROM_EMAIL: str = os.getenv("EMAILS_FROM_EMAIL", "info@profibot.hu")
|
||||
# --- Database & Cache ---
|
||||
DATABASE_URL: str
|
||||
REDIS_URL: str = "redis://service_finder_redis:6379/0"
|
||||
|
||||
# --- Email ---
|
||||
EMAIL_PROVIDER: str = "auto"
|
||||
EMAILS_FROM_EMAIL: str = "info@profibot.hu"
|
||||
EMAILS_FROM_NAME: str = "Profibot"
|
||||
|
||||
# SMTP & SendGrid (Szigorúan .env-ből)
|
||||
SENDGRID_API_KEY: Optional[str] = os.getenv("SENDGRID_API_KEY")
|
||||
SMTP_HOST: Optional[str] = os.getenv("SMTP_HOST")
|
||||
SMTP_PORT: int = int(os.getenv("SMTP_PORT", 587))
|
||||
SMTP_USER: Optional[str] = os.getenv("SMTP_USER")
|
||||
SMTP_PASSWORD: Optional[str] = os.getenv("SMTP_PASSWORD")
|
||||
SENDGRID_API_KEY: Optional[str] = None
|
||||
SMTP_HOST: Optional[str] = None
|
||||
SMTP_PORT: int = 587
|
||||
SMTP_USER: Optional[str] = None
|
||||
SMTP_PASSWORD: Optional[str] = None
|
||||
|
||||
# --- External URLs ---
|
||||
# .env-ben legyen átírva a .10-es IP-re!
|
||||
FRONTEND_BASE_URL: str = os.getenv("FRONTEND_BASE_URL", "http://localhost:3000")
|
||||
FRONTEND_BASE_URL: str = "http://localhost:3000"
|
||||
|
||||
# --- Dinamikus Admin Motor ---
|
||||
async def get_db_setting(self, db: AsyncSession, key_name: str, default: Any = None) -> Any:
|
||||
"""
|
||||
Lekéri a paramétert a data.system_settings táblából.
|
||||
Ezzel érjük el, hogy a kód újraírása nélkül, adminból lehessen
|
||||
állítani a jutalom napokat, százalékokat, stb.
|
||||
"""
|
||||
try:
|
||||
query = text("SELECT value_json FROM data.system_settings WHERE key_name = :key")
|
||||
result = await db.execute(query, {"key": key_name})
|
||||
@@ -63,5 +59,4 @@ class Settings(BaseSettings):
|
||||
extra="ignore"
|
||||
)
|
||||
|
||||
|
||||
settings = Settings()
|
||||
@@ -1,3 +1,4 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/core/i18n.py
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -9,21 +10,44 @@ class LocaleManager:
|
||||
self._load()
|
||||
|
||||
data = self._locales.get(lang, self._locales.get("hu", {}))
|
||||
# Biztonságos bejárás a pontokkal elválasztott kulcsokhoz
|
||||
for k in key.split("."):
|
||||
data = data.get(k, {})
|
||||
if isinstance(data, dict):
|
||||
data = data.get(k, {})
|
||||
else:
|
||||
return key # Ha elakadunk, adjuk vissza magát a kulcsot
|
||||
|
||||
if isinstance(data, str):
|
||||
return data.format(**kwargs)
|
||||
return key
|
||||
|
||||
def _load(self):
|
||||
path = "backend/app/locales" # Konténeren belül: "/app/app/locales"
|
||||
if not os.path.exists(path): path = "app/locales"
|
||||
# A konténeren belül ez a biztos útvonal
|
||||
possible_paths = [
|
||||
"/app/app/locales",
|
||||
"app/locales",
|
||||
"backend/app/locales"
|
||||
]
|
||||
|
||||
path = ""
|
||||
for p in possible_paths:
|
||||
if os.path.exists(p):
|
||||
path = p
|
||||
break
|
||||
|
||||
if not path:
|
||||
print("FIGYELEM: Nem található a locales könyvtár!")
|
||||
return
|
||||
|
||||
for file in os.listdir(path):
|
||||
if file.endswith(".json"):
|
||||
lang = file.split(".")[0]
|
||||
with open(os.path.join(path, file), "r", encoding="utf-8") as f:
|
||||
self._locales[lang] = json.load(f)
|
||||
try:
|
||||
with open(os.path.join(path, file), "r", encoding="utf-8") as f:
|
||||
self._locales[lang] = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Hiba a {file} betöltésekor: {e}")
|
||||
|
||||
locale_manager = LocaleManager()
|
||||
locale_manager = LocaleManager()
|
||||
# Rövid alias a könnyebb használathoz
|
||||
t = locale_manager.get
|
||||
40
backend/app/core/rbac.py
Normal file
40
backend/app/core/rbac.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/core/rbac.py
|
||||
from fastapi import HTTPException, Depends, status
|
||||
from app.api.deps import get_current_user
|
||||
from app.models.identity import User
|
||||
|
||||
class RBAC:
|
||||
def __init__(self, required_perm: str = None, min_rank: int = 0):
|
||||
self.required_perm = required_perm
|
||||
self.min_rank = min_rank
|
||||
|
||||
async def __call__(self, current_user: User = Depends(get_current_user)):
|
||||
# 1. Szuperadmin (Rank 100) mindent visz
|
||||
if current_user.role == "SUPERADMIN":
|
||||
return True
|
||||
|
||||
# 2. Rang ellenőrzés (Hierarchia)
|
||||
# Itt feltételezzük, hogy a role-okhoz rendelt rank-okat egy configból vesszük
|
||||
user_rank = self.get_role_rank(current_user.role)
|
||||
if user_rank < self.min_rank:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Ezen a hierarchia szinten ez a művelet nem engedélyezett."
|
||||
)
|
||||
|
||||
# 3. Egyedi képesség ellenőrzés (Capabilities)
|
||||
user_perms = current_user.custom_permissions.get("capabilities", [])
|
||||
if self.required_perm and self.required_perm not in user_perms:
|
||||
# Ha a sablonban sincs benne, akkor tiltás
|
||||
if not self.check_role_template(current_user.role, self.required_perm):
|
||||
raise HTTPException(status_code=403, detail="Nincs meg a specifikus jogosultságod.")
|
||||
|
||||
return True
|
||||
|
||||
def get_role_rank(self, role: str):
|
||||
ranks = {"COUNTRY_ADMIN": 80, "REGION_ADMIN": 60, "MODERATOR": 40, "SALES": 20, "USER": 10}
|
||||
return ranks.get(role, 0)
|
||||
|
||||
def check_role_template(self, role: str, perm: str):
|
||||
# Ide jön majd az RBAC_MASTER_CONFIG JSON betöltése
|
||||
return False
|
||||
@@ -4,6 +4,20 @@ import bcrypt
|
||||
from jose import jwt, JWTError
|
||||
from app.core.config import settings
|
||||
|
||||
# Master Book 5.0: RBAC Rank Definition Matrix
|
||||
# Ezek a szintek határozzák meg a hozzáférést a Middleware szintjén.
|
||||
RANK_MAP = {
|
||||
"superadmin": 100,
|
||||
"country_admin": 80,
|
||||
"region_admin": 60,
|
||||
"moderator": 40,
|
||||
"sales": 20,
|
||||
"user": 10,
|
||||
"service": 15,
|
||||
"fleet_manager": 25,
|
||||
"driver": 5
|
||||
}
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""Összehasonlítja a sima szöveges jelszót a hash-elt változattal."""
|
||||
if not hashed_password:
|
||||
@@ -22,14 +36,23 @@ def get_password_hash(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode("utf-8"), salt).decode("utf-8")
|
||||
|
||||
def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""Létrehozza a JWT access tokent."""
|
||||
"""
|
||||
Létrehozza a JWT access tokent bővített RBAC adatokkal.
|
||||
Várt kulcsok: sub (user_id), role, rank, scope_level, scope_id
|
||||
"""
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
# Rendszer szintű metaadatok hozzáadása
|
||||
to_encode.update({
|
||||
"exp": expire,
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"iss": "service-finder-auth"
|
||||
})
|
||||
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
Reference in New Issue
Block a user