first commit
This commit is contained in:
27
backend/app/core/config.py
Normal file
27
backend/app/core/config.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
|
||||
APP_ENV: str = "dev"
|
||||
APP_NAME: str = "mt-traffic"
|
||||
BASE_URL: str = "http://localhost:8000"
|
||||
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///./mt_traffic.db"
|
||||
DOCKER_DATABASE_URL: str = "sqlite+aiosqlite:////data/mt_traffic.db"
|
||||
|
||||
SESSION_SECRET: str = "change_me"
|
||||
CREDENTIALS_MASTER_KEY: str = "change_me_32bytes_base64_fernet"
|
||||
PASSWORD_HASH_ALG: str = "argon2"
|
||||
|
||||
COOKIE_SECURE: bool = False
|
||||
COOKIE_SAMESITE: str = "lax"
|
||||
CORS_ORIGINS: str = "http://localhost:3000"
|
||||
|
||||
DEFAULT_POLL_INTERVAL_MS: int = 1000
|
||||
MAX_WS_SUBSCRIPTIONS_PER_USER: int = 10
|
||||
|
||||
ADMIN_BOOTSTRAP_EMAIL: str = "admin@example.com"
|
||||
ADMIN_BOOTSTRAP_PASSWORD: str = "admin1234"
|
||||
|
||||
settings = Settings()
|
||||
24
backend/app/core/db.py
Normal file
24
backend/app/core/db.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from typing import AsyncGenerator
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
def _db_url() -> str:
|
||||
# w dockerze zwykle ustawiamy DATABASE_URL przez env, więc preferujemy settings.DATABASE_URL
|
||||
return settings.DATABASE_URL
|
||||
|
||||
engine = create_async_engine(_db_url(), future=True, echo=False)
|
||||
SessionLocal = async_sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)
|
||||
|
||||
async def init_db() -> None:
|
||||
from app.models import user, router, dashboard # noqa
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async def get_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with SessionLocal() as session:
|
||||
yield session
|
||||
7
backend/app/core/logging.py
Normal file
7
backend/app/core/logging.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import logging
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
68
backend/app/core/security.py
Normal file
68
backend/app/core/security.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import base64
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.config import settings
|
||||
from app.models.user import User, UserRole
|
||||
|
||||
pwd_context = CryptContext(schemes=["argon2", "bcrypt"], deprecated="auto")
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def verify_password(password: str, password_hash: str) -> bool:
|
||||
return pwd_context.verify(password, password_hash)
|
||||
|
||||
def session_serializer() -> URLSafeTimedSerializer:
|
||||
return URLSafeTimedSerializer(settings.SESSION_SECRET, salt="session")
|
||||
|
||||
SESSION_COOKIE = "mt_session"
|
||||
CSRF_COOKIE = "mt_csrf"
|
||||
|
||||
def create_session_token(user_id: int) -> str:
|
||||
s = session_serializer()
|
||||
return s.dumps({"uid": user_id})
|
||||
|
||||
def read_session_token(token: str, max_age_seconds: int = 60 * 60 * 24 * 7) -> Optional[int]:
|
||||
s = session_serializer()
|
||||
try:
|
||||
data = s.loads(token, max_age=max_age_seconds)
|
||||
uid = int(data.get("uid"))
|
||||
return uid
|
||||
except (BadSignature, SignatureExpired, Exception):
|
||||
return None
|
||||
|
||||
def new_csrf_token() -> str:
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
def fernet() -> Fernet:
|
||||
# CREDENTIALS_MASTER_KEY powinien być base64 urlsafe 32 bytes
|
||||
key = settings.CREDENTIALS_MASTER_KEY.encode("utf-8")
|
||||
return Fernet(key)
|
||||
|
||||
def encrypt_secret(plain: str) -> str:
|
||||
return fernet().encrypt(plain.encode("utf-8")).decode("utf-8")
|
||||
|
||||
def decrypt_secret(enc: str) -> str:
|
||||
return fernet().decrypt(enc.encode("utf-8")).decode("utf-8")
|
||||
|
||||
async def bootstrap_admin_if_needed(session: AsyncSession) -> None:
|
||||
res = await session.execute(select(User).limit(1))
|
||||
first = res.scalar_one_or_none()
|
||||
if first:
|
||||
return
|
||||
admin = User(
|
||||
email=settings.ADMIN_BOOTSTRAP_EMAIL,
|
||||
password_hash=hash_password(settings.ADMIN_BOOTSTRAP_PASSWORD),
|
||||
role=UserRole.ADMIN,
|
||||
is_active=True,
|
||||
)
|
||||
session.add(admin)
|
||||
await session.commit()
|
||||
Reference in New Issue
Block a user