Нормализовать переводы строк в LF

Решение по TD-STYLE-ANALYZERS: LF — инструменты проекта (Python/Node) пишут LF,
CRLF-.sh не работают на Linux CI (sh scripts/ci.sh), большинство файлов уже были
LF. Добавлен .gitattributes (* text=auto eol=lf, бинарные исключения),
.editorconfig переведён на lf, 1029 файлов конвертированы, git add --renormalize.
Из индекса убраны закравшиеся archive/**/__pycache__/*.pyc.
This commit is contained in:
Rustam Khalimov
2026-09-11 19:01:42 +03:00
parent 39c9bdc1b7
commit 713d554dc2
751 changed files with 94507 additions and 94486 deletions
+98 -98
View File
@@ -1,98 +1,98 @@
"""Авторизация в дашборд: admin/admin по умолчанию, сессия 30 дней.
Хэш пароля — PBKDF2-HMAC-SHA256. Токен сессии — случайный, хранится в БД,
передаётся httpOnly-кукой. По ТЗ смена пароля — через интерфейс.
"""
from __future__ import annotations
import hashlib
import hmac
import secrets
import time
from fastapi import Cookie, HTTPException, Response
from . import config
from .db import store
_ITERATIONS = 200_000
def _hash_password(password: str, salt: str) -> str:
return hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), _ITERATIONS).hex()
def _make_salt() -> str:
return secrets.token_hex(16)
def ensure_creds() -> None:
"""Создаёт учётные данные по умолчанию, если их нет."""
if store.scalar("SELECT count(*) FROM creds") == 0:
salt = _make_salt()
store.execute(
"INSERT INTO creds(login, password_hash, salt) VALUES (?, ?, ?)",
[config.BOOTSTRAP_LOGIN, _hash_password(config.BOOTSTRAP_PASSWORD, salt), salt],
)
def verify_password(login: str, password: str) -> bool:
row = store.query_one("SELECT password_hash, salt FROM creds WHERE login = ?", [login])
if not row:
return False
return hmac.compare_digest(row["password_hash"], _hash_password(password, row["salt"]))
def change_password(login: str, old_password: str, new_password: str) -> bool:
if not verify_password(login, old_password):
return False
if len(new_password) < 4:
raise HTTPException(400, "Пароль слишком короткий (минимум 4 символа)")
salt = _make_salt()
store.execute(
"UPDATE creds SET password_hash = ?, salt = ? WHERE login = ?",
[_hash_password(new_password, salt), salt, login],
)
# разлогиниваем все старые сессии пользователя
store.execute("DELETE FROM sessions WHERE login = ?", [login])
return True
def create_session(login: str) -> str:
token = secrets.token_urlsafe(32)
expires = time.time_ns() // 1_000_000 + config.SESSION_DAYS * 86_400_000
store.execute("INSERT INTO sessions(token, login, expires) VALUES (?, ?, ?)", [token, login, expires])
return token
def resolve_session(token: str | None) -> str | None:
if not token:
return None
row = store.query_one(
"SELECT login FROM sessions WHERE token = ? AND expires > ?",
[token, time.time_ns() // 1_000_000],
)
return row["login"] if row else None
def set_session_cookie(response: Response, token: str) -> None:
response.set_cookie(
key=config.COOKIE_NAME,
value=token,
max_age=config.SESSION_DAYS * 86400,
httponly=True,
samesite="lax",
# secure включается при HTTPS-проксировании; флаг не критичен локально
)
def destroy_session(token: str | None) -> None:
if token:
store.execute("DELETE FROM sessions WHERE token = ?", [token])
def current_login(token: str | None = Cookie(default=None, alias=config.COOKIE_NAME)) -> str | None:
login = resolve_session(token)
if not login:
raise HTTPException(401, "Требуется авторизация")
return login
"""Авторизация в дашборд: admin/admin по умолчанию, сессия 30 дней.
Хэш пароля — PBKDF2-HMAC-SHA256. Токен сессии — случайный, хранится в БД,
передаётся httpOnly-кукой. По ТЗ смена пароля — через интерфейс.
"""
from __future__ import annotations
import hashlib
import hmac
import secrets
import time
from fastapi import Cookie, HTTPException, Response
from . import config
from .db import store
_ITERATIONS = 200_000
def _hash_password(password: str, salt: str) -> str:
return hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), _ITERATIONS).hex()
def _make_salt() -> str:
return secrets.token_hex(16)
def ensure_creds() -> None:
"""Создаёт учётные данные по умолчанию, если их нет."""
if store.scalar("SELECT count(*) FROM creds") == 0:
salt = _make_salt()
store.execute(
"INSERT INTO creds(login, password_hash, salt) VALUES (?, ?, ?)",
[config.BOOTSTRAP_LOGIN, _hash_password(config.BOOTSTRAP_PASSWORD, salt), salt],
)
def verify_password(login: str, password: str) -> bool:
row = store.query_one("SELECT password_hash, salt FROM creds WHERE login = ?", [login])
if not row:
return False
return hmac.compare_digest(row["password_hash"], _hash_password(password, row["salt"]))
def change_password(login: str, old_password: str, new_password: str) -> bool:
if not verify_password(login, old_password):
return False
if len(new_password) < 4:
raise HTTPException(400, "Пароль слишком короткий (минимум 4 символа)")
salt = _make_salt()
store.execute(
"UPDATE creds SET password_hash = ?, salt = ? WHERE login = ?",
[_hash_password(new_password, salt), salt, login],
)
# разлогиниваем все старые сессии пользователя
store.execute("DELETE FROM sessions WHERE login = ?", [login])
return True
def create_session(login: str) -> str:
token = secrets.token_urlsafe(32)
expires = time.time_ns() // 1_000_000 + config.SESSION_DAYS * 86_400_000
store.execute("INSERT INTO sessions(token, login, expires) VALUES (?, ?, ?)", [token, login, expires])
return token
def resolve_session(token: str | None) -> str | None:
if not token:
return None
row = store.query_one(
"SELECT login FROM sessions WHERE token = ? AND expires > ?",
[token, time.time_ns() // 1_000_000],
)
return row["login"] if row else None
def set_session_cookie(response: Response, token: str) -> None:
response.set_cookie(
key=config.COOKIE_NAME,
value=token,
max_age=config.SESSION_DAYS * 86400,
httponly=True,
samesite="lax",
# secure включается при HTTPS-проксировании; флаг не критичен локально
)
def destroy_session(token: str | None) -> None:
if token:
store.execute("DELETE FROM sessions WHERE token = ?", [token])
def current_login(token: str | None = Cookie(default=None, alias=config.COOKIE_NAME)) -> str | None:
login = resolve_session(token)
if not login:
raise HTTPException(401, "Требуется авторизация")
return login