Spaces:
Sleeping
Sleeping
File size: 7,709 Bytes
c549f7c | 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 | """
EduClone AI β Authentication & User Management
SQLite WAL mode, bcrypt passwords, roles: admin / user
"""
import sqlite3, bcrypt, datetime, os, re, threading
DB_PATH = os.environ.get("EDUCLONE_DB", "educlone_users.db")
ADMIN_USERNAME = "admin"
ADMIN_EMAIL = "admin@educlone.ai"
ADMIN_PASSWORD = "admin123"
_lock = threading.Lock()
def _conn():
"""Return a fresh connection each call β WAL so reads never block."""
c = sqlite3.connect(DB_PATH, timeout=30)
c.row_factory = sqlite3.Row
c.execute("PRAGMA journal_mode=WAL")
c.execute("PRAGMA busy_timeout=10000")
return c
def init_db():
with _lock:
c = _conn()
c.execute("""CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
full_name TEXT DEFAULT '',
institution TEXT DEFAULT '',
is_approved INTEGER DEFAULT 1,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
last_login TEXT)""")
c.execute("""CREATE TABLE IF NOT EXISTS activity_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
username TEXT,
action TEXT,
detail TEXT,
timestamp TEXT)""")
# seed admin
if not c.execute("SELECT id FROM users WHERE username=?",
(ADMIN_USERNAME,)).fetchone():
ph = bcrypt.hashpw(ADMIN_PASSWORD.encode(), bcrypt.gensalt()).decode()
c.execute("""INSERT INTO users
(username,email,password_hash,role,full_name,institution,is_approved,created_at)
VALUES(?,?,?,?,?,?,?,?)""",
(ADMIN_USERNAME, ADMIN_EMAIL, ph, "admin",
"Abdulqayyum MBA", "EduClone AI", 1,
datetime.datetime.now().isoformat()))
c.commit(); c.close()
def _valid_email(e): return bool(re.match(r'^[^@\s]+@[^@\s]+\.[^@\s]+$', e))
def _valid_user(u): return bool(re.match(r'^[a-zA-Z0-9_]{3,30}$', u))
def _valid_pass(p): return len(p)>=8 and re.search(r'[A-Z]',p) and re.search(r'[0-9]',p)
def register_user(username, email, password, full_name="", institution=""):
username = username.strip().lower(); email = email.strip().lower()
if not _valid_user(username): return False,"Username: 3β30 chars, letters/numbers/underscore only."
if not _valid_email(email): return False,"Please enter a valid email address."
if not _valid_pass(password): return False,"Password: 8+ chars, 1 uppercase, 1 number."
with _lock:
c = _conn()
try:
if c.execute("SELECT id FROM users WHERE username=?",(username,)).fetchone():
return False,"Username already taken."
if c.execute("SELECT id FROM users WHERE email=?",(email,)).fetchone():
return False,"Email already registered."
ph = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
c.execute("""INSERT INTO users
(username,email,password_hash,role,full_name,institution,is_approved,created_at)
VALUES(?,?,?,?,?,?,?,?)""",
(username,email,ph,"user",full_name.strip(),institution.strip(),1,
datetime.datetime.now().isoformat()))
c.commit()
return True, f"β
Account created! Welcome, {username}. You can now log in."
except Exception as ex:
return False, f"Registration error: {ex}"
finally:
c.close()
def login_user(username, password):
username = username.strip().lower()
with _lock:
c = _conn()
try:
row = c.execute(
"SELECT * FROM users WHERE username=? OR email=?",
(username, username)).fetchone()
if not row:
return False, "β Username or password incorrect.", None
if not row["is_active"]:
return False, "β Account deactivated. Contact admin.", None
if not bcrypt.checkpw(password.encode(), row["password_hash"].encode()):
return False, "β Username or password incorrect.", None
c.execute("UPDATE users SET last_login=? WHERE id=?",
(datetime.datetime.now().isoformat(), row["id"]))
c.commit()
user = {k: row[k] for k in row.keys() if k != "password_hash"}
return True, f"β
Welcome back, {row['full_name'] or row['username']}!", user
except Exception as ex:
return False, f"Login error: {ex}", None
finally:
c.close()
def log_activity(user_id, username, action, detail=""):
with _lock:
c = _conn()
try:
c.execute("""INSERT INTO activity_log(user_id,username,action,detail,timestamp)
VALUES(?,?,?,?,?)""",
(user_id, username, action, detail, datetime.datetime.now().isoformat()))
c.commit()
finally:
c.close()
def get_all_users():
with _lock:
c = _conn()
try:
rows = c.execute("""SELECT id,username,email,role,full_name,institution,
is_approved,is_active,created_at,last_login
FROM users ORDER BY created_at DESC""").fetchall()
return [dict(r) for r in rows]
finally:
c.close()
def toggle_user_active(user_id, active):
with _lock:
c = _conn()
try:
c.execute("UPDATE users SET is_active=? WHERE id=?", (1 if active else 0, user_id))
c.commit()
return "β
User status updated."
finally:
c.close()
def change_user_role(user_id, role):
if role not in ("admin","user"): return "β Invalid role."
with _lock:
c = _conn()
try:
c.execute("UPDATE users SET role=? WHERE id=?", (role, user_id))
c.commit()
return f"β
Role updated to {role}."
finally:
c.close()
def delete_user(user_id):
with _lock:
c = _conn()
try:
c.execute("DELETE FROM users WHERE id=? AND role != 'admin'", (user_id,))
c.commit()
return "β
User deleted."
finally:
c.close()
def get_activity_log(limit=200):
with _lock:
c = _conn()
try:
rows = c.execute(
"SELECT * FROM activity_log ORDER BY timestamp DESC LIMIT ?",
(limit,)).fetchall()
return [dict(r) for r in rows]
finally:
c.close()
def get_stats():
with _lock:
c = _conn()
try:
today = datetime.date.today().isoformat()
return {
"total": c.execute("SELECT COUNT(*) FROM users").fetchone()[0],
"admins": c.execute("SELECT COUNT(*) FROM users WHERE role='admin'").fetchone()[0],
"active": c.execute("SELECT COUNT(*) FROM users WHERE is_active=1").fetchone()[0],
"new_today": c.execute("SELECT COUNT(*) FROM users WHERE created_at LIKE ?",
(today+"%",)).fetchone()[0],
"logins_today": c.execute("SELECT COUNT(*) FROM activity_log WHERE action='login' AND timestamp LIKE ?",
(today+"%",)).fetchone()[0],
}
finally:
c.close()
init_db()
|