Commit inicial - upload de todos os arquivos da pasta
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
"""Autenticação: hash de senha (bcrypt), seed do admin, CSRF e guarda de sessão."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import secrets
|
||||
import time
|
||||
|
||||
import bcrypt
|
||||
from starlette.requests import Request
|
||||
|
||||
from . import database as db
|
||||
from .config import get_settings
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Senhas
|
||||
# --------------------------------------------------------------------------- #
|
||||
def hash_password(password: str) -> str:
|
||||
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("ascii")
|
||||
|
||||
|
||||
def verify_password(password: str, password_hash: str) -> bool:
|
||||
try:
|
||||
return bcrypt.checkpw(password.encode("utf-8"), password_hash.encode("ascii"))
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def seed_admin() -> str | None:
|
||||
"""Cria o usuário admin na 1ª execução. Retorna uma mensagem de aviso, se houver."""
|
||||
settings = get_settings()
|
||||
with db.session() as conn:
|
||||
if db.count_users(conn) > 0:
|
||||
return None
|
||||
password = settings.admin_password
|
||||
note = None
|
||||
if not password:
|
||||
# Sem ADMIN_PASSWORD definido: gera uma senha aleatória e a expõe UMA vez
|
||||
# no log para o operador. Em produção defina ADMIN_PASSWORD no ambiente.
|
||||
password = secrets.token_urlsafe(12)
|
||||
note = (
|
||||
f"[AVISO] Nenhum ADMIN_PASSWORD definido. Usuário '{settings.admin_username}' "
|
||||
f"criado com senha temporária: {password} (defina ADMIN_PASSWORD e reinicie)"
|
||||
)
|
||||
db.create_user(conn, settings.admin_username, hash_password(password))
|
||||
return note
|
||||
|
||||
|
||||
def authenticate(username: str, password: str) -> bool:
|
||||
with db.session() as conn:
|
||||
row = db.get_user(conn, username)
|
||||
if row is None:
|
||||
# Compara mesmo sem usuário para não vazar tempo (mitiga user enumeration).
|
||||
verify_password(password, hash_password("dummy"))
|
||||
return False
|
||||
return verify_password(password, row["password_hash"])
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Sessão / guarda
|
||||
# --------------------------------------------------------------------------- #
|
||||
def current_user(request: Request) -> str | None:
|
||||
return request.session.get("user")
|
||||
|
||||
|
||||
def login_session(request: Request, username: str) -> None:
|
||||
request.session["user"] = username
|
||||
request.session["logged_at"] = int(time.time())
|
||||
# rotaciona o token CSRF a cada login
|
||||
request.session["csrf"] = secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
def logout_session(request: Request) -> None:
|
||||
request.session.clear()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# CSRF
|
||||
# --------------------------------------------------------------------------- #
|
||||
def get_csrf_token(request: Request) -> str:
|
||||
token = request.session.get("csrf")
|
||||
if not token:
|
||||
token = secrets.token_urlsafe(32)
|
||||
request.session["csrf"] = token
|
||||
return token
|
||||
|
||||
|
||||
def check_csrf(request: Request, submitted: str | None) -> bool:
|
||||
expected = request.session.get("csrf")
|
||||
return bool(expected) and bool(submitted) and secrets.compare_digest(expected, submitted)
|
||||
Reference in New Issue
Block a user