Files
platform/gateway/auth.py
Yusuf Suleman 687d6c5f12
All checks were successful
Security Checks / dockerfile-lint (push) Successful in 4s
Security Checks / dependency-audit (push) Successful in 14s
Security Checks / secret-scanning (push) Successful in 3s
feat: instant feedback button — creates Gitea issues with auto-labels
iOS:
- Subtle floating feedback button (bottom-left, speech bubble icon)
- Quick sheet: type → send → auto-creates Gitea issue
- Shows checkmark on success, auto-dismisses
- No friction — tap, type, done

Gateway:
- POST /api/feedback endpoint
- Auto-labels: bug/feature/enhancement + ios/web + fitness/brain/reader/podcasts
- Keyword detection for label assignment
- Creates issue via Gitea API with user name and source

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 12:44:10 -05:00

100 lines
3.1 KiB
Python

"""
Platform Gateway — Auth handlers (login, logout, register).
"""
"""
NOTE: Passwords are hashed with bcrypt. Any existing SHA-256 hashed passwords
in the database will no longer work. The admin user is re-seeded on first boot
if no users exist. Other users need manual password reset.
"""
import json
import sqlite3
import bcrypt
from config import SESSION_COOKIE_SECURE
from database import get_db
from sessions import create_session, delete_session
def handle_login(handler, body):
try:
data = json.loads(body)
except Exception as e:
handler._send_json({"error": "Invalid JSON"}, 400)
return
username = data.get("username", "").strip().lower()
password = data.get("password", "")
if not username or not password:
handler._send_json({"error": "Username and password required"}, 400)
return
conn = get_db()
user = conn.execute("SELECT * FROM users WHERE username = ?",
(username,)).fetchone()
conn.close()
if not user or not bcrypt.checkpw(password.encode(), user["password_hash"].encode()):
handler._send_json({"error": "Invalid credentials"}, 401)
return
token = create_session(user["id"])
handler.send_response(200)
handler.send_header("Content-Type", "application/json")
handler._set_session_cookie(token)
resp = json.dumps({
"success": True,
"user": {"id": user["id"], "username": user["username"], "display_name": user["display_name"]}
}).encode()
handler.send_header("Content-Length", len(resp))
handler.end_headers()
handler.wfile.write(resp)
def handle_logout(handler):
token = handler._get_session_token()
delete_session(token)
handler.send_response(200)
handler.send_header("Content-Type", "application/json")
cookie = "platform_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0"
if SESSION_COOKIE_SECURE:
cookie += "; Secure"
handler.send_header("Set-Cookie", cookie)
resp = b'{"success": true}'
handler.send_header("Content-Length", len(resp))
handler.end_headers()
handler.wfile.write(resp)
def handle_register(handler, body):
try:
data = json.loads(body)
except Exception as e:
handler._send_json({"error": "Invalid JSON"}, 400)
return
username = data.get("username", "").strip().lower()
password = data.get("password", "")
display_name = data.get("display_name", username)
if not username or not password:
handler._send_json({"error": "Username and password required"}, 400)
return
pw_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
conn = get_db()
try:
conn.execute("INSERT INTO users (username, password_hash, display_name) VALUES (?, ?, ?)",
(username, pw_hash, display_name))
conn.commit()
user_id = conn.execute("SELECT id FROM users WHERE username = ?", (username,)).fetchone()["id"]
conn.close()
handler._send_json({"success": True, "user_id": user_id})
except sqlite3.IntegrityError:
conn.close()
handler._send_json({"error": "Username already exists"}, 409)