Spaces:
Paused
Paused
| """ | |
| Authentication module. | |
| Real password hashing (werkzeug's generate_password_hash / check_password_hash | |
| -- salted, uses PBKDF2/scrypt depending on werkzeug version) and Flask-Login | |
| session management. | |
| NOTE ON SCOPE: this is hackathon-appropriate auth -- correct password hashing, | |
| proper session cookies via Flask-Login, but no email verification, no | |
| password reset flow, no rate limiting on login attempts. Be upfront about | |
| that if asked -- it's a normal, expected scope cut, not a hidden flaw. | |
| """ | |
| import sqlite3 | |
| from datetime import datetime | |
| from werkzeug.security import generate_password_hash, check_password_hash | |
| from flask_login import UserMixin | |
| DB_FILE = "verimed.db" | |
| class User(UserMixin): | |
| """Flask-Login needs an object with .id, .is_authenticated, etc. -- UserMixin provides those.""" | |
| def __init__(self, id, name, email): | |
| self.id = str(id) | |
| self.name = name | |
| self.email = email | |
| def init_users_table(): | |
| conn = sqlite3.connect(DB_FILE) | |
| cur = conn.cursor() | |
| cur.execute(""" | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name TEXT NOT NULL, | |
| email TEXT UNIQUE NOT NULL, | |
| password_hash TEXT NOT NULL, | |
| created_at TEXT NOT NULL | |
| ) | |
| """) | |
| conn.commit() | |
| conn.close() | |
| def create_user(name: str, email: str, password: str) -> "User | None": | |
| """Returns the created User, or None if the email is already registered.""" | |
| conn = sqlite3.connect(DB_FILE) | |
| cur = conn.cursor() | |
| cur.execute("SELECT id FROM users WHERE email = ?", (email.lower().strip(),)) | |
| if cur.fetchone(): | |
| conn.close() | |
| return None # email already taken | |
| password_hash = generate_password_hash(password) | |
| now = datetime.now().isoformat(timespec="seconds") | |
| cur.execute( | |
| "INSERT INTO users (name, email, password_hash, created_at) VALUES (?, ?, ?, ?)", | |
| (name.strip(), email.lower().strip(), password_hash, now), | |
| ) | |
| conn.commit() | |
| user_id = cur.lastrowid | |
| conn.close() | |
| return User(user_id, name.strip(), email.lower().strip()) | |
| def verify_login(email: str, password: str) -> "User | None": | |
| """Returns the User if email+password match, else None.""" | |
| conn = sqlite3.connect(DB_FILE) | |
| conn.row_factory = sqlite3.Row | |
| cur = conn.cursor() | |
| cur.execute("SELECT * FROM users WHERE email = ?", (email.lower().strip(),)) | |
| row = cur.fetchone() | |
| conn.close() | |
| if not row: | |
| return None | |
| if not check_password_hash(row["password_hash"], password): | |
| return None | |
| return User(row["id"], row["name"], row["email"]) | |
| def get_user_by_id(user_id: str) -> "User | None": | |
| """Required by Flask-Login's user_loader callback.""" | |
| conn = sqlite3.connect(DB_FILE) | |
| conn.row_factory = sqlite3.Row | |
| cur = conn.cursor() | |
| cur.execute("SELECT * FROM users WHERE id = ?", (user_id,)) | |
| row = cur.fetchone() | |
| conn.close() | |
| if not row: | |
| return None | |
| return User(row["id"], row["name"], row["email"]) | |
| if __name__ == "__main__": | |
| # Quick manual test -- run: python auth.py | |
| import os | |
| if os.path.exists(DB_FILE): | |
| os.remove(DB_FILE) # clean slate for this test only | |
| init_users_table() | |
| user = create_user("Test User", "test@example.com", "correct-password-123") | |
| print(f"Created user: {user.name} ({user.email}), id={user.id}") | |
| dupe = create_user("Someone Else", "test@example.com", "whatever") | |
| print(f"Duplicate email rejected: {dupe is None}") | |
| good_login = verify_login("test@example.com", "correct-password-123") | |
| print(f"Correct password login: {'SUCCESS' if good_login else 'FAILED'}") | |
| bad_login = verify_login("test@example.com", "wrong-password") | |
| print(f"Wrong password login: {'correctly rejected' if bad_login is None else 'SECURITY BUG'}") | |
| fetched = get_user_by_id(user.id) | |
| print(f"Fetched by id: {fetched.name} ({fetched.email})") | |