Owner accounts: auth, password reset, CRM dashboard visibility (#19)
Flask-session login scoped to one client_id (never a request param), self-service + operator-triggered password reset via single-use tokens, and an Owner accounts tab on the CRM dashboard. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -273,6 +273,37 @@ def get_user_by_email(client_id, email):
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def find_user_by_email(email):
|
||||
"""Login lookup (#19): users.email is globally unique, and at login time
|
||||
there's no client_id yet to scope by -- resolving client_id is exactly
|
||||
what a successful login establishes for the session."""
|
||||
with db.connect() as conn, conn.cursor() as cur:
|
||||
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def get_user(user_id):
|
||||
with db.connect() as conn, conn.cursor() as cur:
|
||||
cur.execute("SELECT * FROM users WHERE user_id = %s", (user_id,))
|
||||
return cur.fetchone()
|
||||
|
||||
|
||||
def list_users(client_id=None):
|
||||
"""Owner accounts for the CRM operator dashboard (#19). Unlike the rest
|
||||
of this module, this one is deliberately allowed to span every tenant
|
||||
when client_id is omitted -- an operator manages all clients, not one."""
|
||||
with db.connect() as conn, conn.cursor() as cur:
|
||||
if client_id is None:
|
||||
cur.execute(
|
||||
"SELECT user_id, client_id, email, created_at FROM users "
|
||||
"ORDER BY client_id, email")
|
||||
else:
|
||||
cur.execute(
|
||||
"SELECT user_id, client_id, email, created_at FROM users "
|
||||
"WHERE client_id = %s ORDER BY email", (client_id,))
|
||||
return cur.fetchall()
|
||||
|
||||
|
||||
def verify_password(user, password):
|
||||
return check_password_hash(user["password_hash"], password)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user