Initial commit: backend scaffold, auth, frontend login
- FastAPI + SQLAlchemy async + Alembic + Postgres backend - Auth: JWT access + rotating refresh tokens, argon2, roles, audit log - React 19 + Vite frontend: login page, protected route, auth context - Docker Compose: postgres, redis, migrate, api, worker (placeholder), frontend/nginx Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""Служебные команды.
|
||||
|
||||
python -m app.cli bootstrap # создать первого администратора
|
||||
python -m app.cli gen-keys # сгенерировать SECRET_KEY и ENCRYPTION_KEY
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import secrets
|
||||
import sys
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import hash_password
|
||||
from app.db.models.user import User, UserRole
|
||||
from app.db.session import session_scope
|
||||
|
||||
|
||||
async def bootstrap() -> int:
|
||||
"""Создаёт первого администратора из переменных окружения.
|
||||
|
||||
Идемпотентна: повторный запуск ничего не меняет и пароль не сбрасывает.
|
||||
"""
|
||||
if not settings.first_admin_password:
|
||||
print("FIRST_ADMIN_PASSWORD не задан в .env", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
email = settings.first_admin_email.strip().lower()
|
||||
|
||||
async with session_scope() as session:
|
||||
existing = await session.scalar(select(User).where(User.email == email))
|
||||
if existing is not None:
|
||||
print(f"Пользователь {email} уже существует — ничего не изменено.")
|
||||
return 0
|
||||
|
||||
session.add(
|
||||
User(
|
||||
email=email,
|
||||
full_name=settings.first_admin_name,
|
||||
password_hash=hash_password(settings.first_admin_password),
|
||||
role=UserRole.ADMIN,
|
||||
)
|
||||
)
|
||||
|
||||
print(f"Администратор {email} создан.")
|
||||
print("Смените пароль после первого входа и уберите FIRST_ADMIN_PASSWORD из .env.")
|
||||
return 0
|
||||
|
||||
|
||||
def gen_keys() -> int:
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
print(f"SECRET_KEY={secrets.token_urlsafe(64)}")
|
||||
print(f"ENCRYPTION_KEY={Fernet.generate_key().decode()}")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(prog="app.cli", description="Служебные команды lux_fiscal")
|
||||
parser.add_argument("command", choices=["bootstrap", "gen-keys"])
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "bootstrap":
|
||||
return asyncio.run(bootstrap())
|
||||
return gen_keys()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user