File size: 7,150 Bytes
eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 eda187d 65c84d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | """
OmniParse AI - Authentication Module
Handles user registration, login, session management, and API key generation.
Uses bcrypt for password hashing and delegates all storage to database.py.
Late-imports database functions inside call-bodies to avoid circular imports
(auth ↔ database).
"""
import secrets
import bcrypt
from validation import validate_email, validate_name, validate_password, sanitize_string
# ---------------------------------------------------------------------------
# Password helpers
# ---------------------------------------------------------------------------
def hash_pw(password: str) -> str:
"""Hash *password* with bcrypt (12 rounds) and return the hash as a UTF-8 string."""
salt = bcrypt.gensalt(rounds=12)
hashed = bcrypt.hashpw(password.encode("utf-8"), salt)
return hashed.decode("utf-8")
def verify_pw(password: str, hashed: str) -> bool:
"""Return True if *password* matches the bcrypt *hashed* string."""
try:
return bcrypt.checkpw(password.encode("utf-8"), hashed.encode("utf-8"))
except Exception:
return False
# ---------------------------------------------------------------------------
# Token / API-key generators
# ---------------------------------------------------------------------------
def gen_token() -> str:
"""Generate a cryptographically random session token (URL-safe, 32 bytes)."""
return secrets.token_urlsafe(32)
def gen_api_key() -> str:
"""Generate a unique API key with a recognizable prefix."""
return "op_live_" + secrets.token_hex(20)
# ---------------------------------------------------------------------------
# User creation
# ---------------------------------------------------------------------------
def create_user(email: str, name: str, password: str, plan: str = "free"):
"""Register a new user after validating all inputs.
Args:
email: User email address.
name: Display name.
password: Plain-text password (will be hashed).
plan: Subscription plan (default "free").
Returns:
(user_dict, "") on success.
(None, "error message") on failure.
"""
# --- Input validation ---
valid, err = validate_email(email)
if not valid:
return None, err
valid, err = validate_name(name)
if not valid:
return None, err
valid, errs = validate_password(password)
if not valid:
return None, "; ".join(errs)
email = sanitize_string(email).lower()
name = sanitize_string(name)
if plan not in ("free", "basic", "pro", "enterprise"):
return None, "Invalid plan."
# --- Late import to avoid circular dependency ---
from database import get_user_by_email, create_user as db_create_user
# --- Check uniqueness ---
existing = get_user_by_email(email)
if existing is not None:
return None, "An account with this email already exists."
# --- Hash & store ---
hashed = hash_pw(password)
user = db_create_user(email=email, name=name, password=hashed, plan=plan)
return user, ""
# ---------------------------------------------------------------------------
# User lookups
# ---------------------------------------------------------------------------
def get_user_by_email(email: str):
"""Fetch a user by email. Returns user dict or None."""
from database import get_user_by_email as db_get_user_by_email
return db_get_user_by_email(email)
def get_user_by_id(uid: str):
"""Fetch a user by ID. Returns user dict or None."""
from database import get_user_by_id as db_get_user_by_id
return db_get_user_by_id(uid)
# ---------------------------------------------------------------------------
# User update
# ---------------------------------------------------------------------------
def update_user(uid: str, fields: dict):
"""Update user fields. Returns the updated user dict or None on failure."""
from database import update_user as db_update_user
return db_update_user(uid, fields)
# ---------------------------------------------------------------------------
# Session management
# ---------------------------------------------------------------------------
def create_session(user_id: str) -> str:
"""Create a new session for *user_id* and return the session token."""
from database import create_session as db_create_session
token = gen_token()
db_create_session(user_id=user_id, token=token)
return token
def get_session_user(token: str):
"""Look up the user associated with *token*.
Returns:
user_dict if the session is valid and not expired, else None.
"""
from database import get_session_user as db_get_session_user
return db_get_session_user(token)
def delete_session(token: str) -> None:
"""Delete a session token (logout)."""
from database import delete_session as db_delete_session
db_delete_session(token)
# ---------------------------------------------------------------------------
# Authentication (login)
# ---------------------------------------------------------------------------
def authenticate_user(email: str, password: str, ip: str = ""):
"""Verify credentials (session is created separately by the caller).
Args:
email: User email.
password: Plain-text password.
ip: Client IP address for rate-limiting.
Returns:
(user_dict, "") on success.
(None, "error message") on failure.
"""
# --- Input validation ---
valid, err = validate_email(email)
if not valid:
return None, err
if not isinstance(password, str) or not password:
return None, "Password is required."
email = sanitize_string(email).lower()
# --- Rate limit check ---
if ip:
from rate_limiter import check_auth_rate
if not check_auth_rate(ip):
return None, "Too many login attempts. Please try again later."
# --- Look up user ---
from database import get_user_by_email as db_get_user_by_email
user = db_get_user_by_email(email)
if user is None:
return None, "Invalid email or password."
# --- Verify password ---
stored_hash = user.get("password", "")
if not stored_hash:
return None, "Invalid email or password."
if not verify_pw(password, stored_hash):
return None, "Invalid email or password."
return user, ""
# ---------------------------------------------------------------------------
# Demo account bootstrap
# ---------------------------------------------------------------------------
def ensure_demo_account():
"""Create a demo account if one does not already exist."""
from database import get_user_by_email, create_user as db_create_user
existing = get_user_by_email("demo@omniparse.ai")
if existing is not None:
return
try:
hashed = hash_pw("demo1234")
db_create_user(
email="demo@omniparse.ai",
name="Demo User",
password=hashed,
plan="pro",
)
except Exception as e:
print(f"[INFO] Demo account not created: {e}") |