Initial commit: site monorepo with API, web, and infra.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import secrets
|
||||
import time
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import bcrypt
|
||||
from jose import jwt
|
||||
|
||||
def generate_secret_token_urlsafe(length: int = 32) -> str:
|
||||
return secrets.token_urlsafe(length)
|
||||
|
||||
|
||||
def generate_secret_token_hex(length: int = 32) -> str:
|
||||
return secrets.token_hex(length)
|
||||
|
||||
|
||||
def generate_install_bundle() -> dict[str, str]:
|
||||
postgres_user = "compton_app"
|
||||
postgres_password = generate_secret_token_urlsafe(32)
|
||||
postgres_db = "compton"
|
||||
minio_root_user = "minio"
|
||||
minio_root_password = generate_secret_token_urlsafe(32)
|
||||
return {
|
||||
"POSTGRES_USER": postgres_user,
|
||||
"POSTGRES_PASSWORD": postgres_password,
|
||||
"POSTGRES_DB": postgres_db,
|
||||
"DATABASE_URL": f"postgresql+psycopg://{postgres_user}:{postgres_password}@postgres:5432/{postgres_db}",
|
||||
"JWT_ACCESS_SECRET": generate_secret_token_hex(32),
|
||||
"JWT_REFRESH_PEPPER": generate_secret_token_hex(32),
|
||||
"S3_ACCESS_KEY": minio_root_user,
|
||||
"S3_SECRET_KEY": minio_root_password,
|
||||
"MINIO_ROOT_USER": minio_root_user,
|
||||
"MINIO_ROOT_PASSWORD": minio_root_password,
|
||||
}
|
||||
|
||||
|
||||
def hash_password(raw_password: str) -> str:
|
||||
return bcrypt.hashpw(raw_password.encode("utf-8"), bcrypt.gensalt(rounds=12)).decode("utf-8")
|
||||
|
||||
|
||||
def verify_password(raw_password: str, password_hash: str) -> bool:
|
||||
return bcrypt.checkpw(raw_password.encode("utf-8"), password_hash.encode("utf-8"))
|
||||
|
||||
|
||||
def create_access_token(
|
||||
user_id: str,
|
||||
role: str,
|
||||
is_superuser: bool = False,
|
||||
*,
|
||||
enterprise_id: str | None = None,
|
||||
enterprise_role: str | None = None,
|
||||
farm_ids: list[str] | None = None,
|
||||
) -> str:
|
||||
from app.core.config import settings
|
||||
from app.core.jwt_denylist import get_auth_epoch
|
||||
|
||||
now = datetime.now(UTC)
|
||||
payload = {
|
||||
"sub": user_id,
|
||||
"role": role,
|
||||
"is_superuser": is_superuser,
|
||||
"auth_epoch": get_auth_epoch(user_id),
|
||||
"iat": int(now.timestamp()),
|
||||
"exp": int((now + timedelta(minutes=settings.jwt_access_ttl_min)).timestamp()),
|
||||
"jti": generate_secret_token_hex(16),
|
||||
}
|
||||
if enterprise_id:
|
||||
payload["enterprise_id"] = enterprise_id
|
||||
if enterprise_role:
|
||||
payload["enterprise_role"] = enterprise_role
|
||||
if farm_ids is not None:
|
||||
payload["farm_ids"] = farm_ids
|
||||
return jwt.encode(payload, settings.jwt_access_secret, algorithm="HS256")
|
||||
|
||||
|
||||
def decode_access_token(token: str) -> dict:
|
||||
from app.core.config import settings
|
||||
|
||||
return jwt.decode(token, settings.jwt_access_secret, algorithms=["HS256"])
|
||||
|
||||
|
||||
def generate_refresh_token() -> str:
|
||||
return generate_secret_token_urlsafe(48)
|
||||
|
||||
|
||||
def generate_opaque_token() -> str:
|
||||
return generate_secret_token_urlsafe(32)
|
||||
|
||||
|
||||
def hash_opaque_token(token: str) -> str:
|
||||
return hash_refresh_token(token)
|
||||
|
||||
|
||||
def hash_refresh_token(token: str) -> str:
|
||||
from app.core.config import settings
|
||||
|
||||
return hashlib.sha256(f"{token}:{settings.jwt_refresh_pepper}".encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def build_signed_media_url(stored_url: str | None) -> str | None:
|
||||
from app.core.config import settings
|
||||
|
||||
if not stored_url:
|
||||
return None
|
||||
if not stored_url.startswith("/api/v1/media/files/"):
|
||||
return stored_url
|
||||
path = stored_url.removeprefix("/api/v1/media/files/")
|
||||
expires = int(time.time()) + settings.media_url_ttl_seconds
|
||||
signature = _sign_media_path(path, expires)
|
||||
query = urlencode({"expires": expires, "sig": signature})
|
||||
return f"/api/v1/media/files/{path}?{query}"
|
||||
|
||||
|
||||
def verify_signed_media(path: str, expires: int, signature: str) -> bool:
|
||||
if expires < int(time.time()):
|
||||
return False
|
||||
expected = _sign_media_path(path, expires)
|
||||
return hmac.compare_digest(expected, signature)
|
||||
|
||||
|
||||
def _sign_media_path(path: str, expires: int) -> str:
|
||||
from app.core.config import settings
|
||||
|
||||
payload = f"{path}:{expires}"
|
||||
return hmac.new(
|
||||
settings.jwt_access_secret.encode("utf-8"),
|
||||
payload.encode("utf-8"),
|
||||
hashlib.sha256,
|
||||
).hexdigest()
|
||||
Reference in New Issue
Block a user