o
    ŒH)j…ë  ã                   @   sˆ   d dl Z d dlZd dlZd dlmZmZ d dlmZ d dlmZm	Z	 d dl
Z
d dlZe
 e¡ZG dd„ dƒZdd„ Zd	d
„ Zdd„ ZdS )é    N)ÚdatetimeÚ	timedelta©Úwraps)ÚrequestÚjsonifyc                   @   sè  e Zd Zh d£ZddhZdd„ Zdd„ Zedd	„ ƒZd
d„ Z	dd„ Z
dd„ Zedd„ ƒZdd„ Zedd„ ƒZdd„ Zdd„ Zdd„ Zedd„ ƒZ						dgdd „Zdhd"d#„Zd$d%„ Zdid'd(„Zd)d*„ Zdjd+d,„Zdjd-d.„Zdkd/d0„Zd1d2„ Zdld3d4„Zd5d6„ Zd7d8„ Zd9d:„ Z dkd;d<„Z!d=d>„ Z"dkd?d@„Z#dAdB„ Z$dCdD„ Z%dkdEdF„Z&dGdH„ Z'dIdJ„ Z(dKdL„ Z)djdMdN„Z*dOdP„ Z+edQdR„ ƒZ,	S	T								dmdUdV„Z-dWdX„ Z.edYdZ„ ƒZ/dld[d\„Z0djd]d^„Z1d_d`„ Z2dadb„ Z3dcdd„ Z4dedf„ Z5dS )nÚAuthHandler>   ÚuserÚadminÚagentÚmanagerÚbusiness_adminr
   r   c              
   C   s¦   || _ | d¡t| dd¡ƒ| d¡| d¡| d¡tjjdœ| _| dd	¡| _d
| _d| _	z|  
¡  |  ¡  W d S  tyR } zt d|¡ W Y d }~d S d }~ww )NÚDB_HOSTÚDB_PORTiê  ÚDB_USERÚDB_PASSWORDÚDB_NAME)ÚhostÚportr	   ÚpasswordÚdatabaseÚcursorclassÚ
SECRET_KEYz)your-secret-key-here-change-in-productionÚHS256é   zMCould not ensure auth extension tables at startup (DB may be unreachable): %s)ÚconfigÚgetÚintÚpymysqlÚcursorsÚ
DictCursorÚ	db_configÚ
jwt_secretÚjwt_algorithmÚjwt_expiration_daysÚensure_embed_tablesÚensure_rbac_tablesÚ	ExceptionÚloggerÚwarning)Úselfr   Úe© r,   ú7/home/aiteam/pcaa-dev/dashboard-backend/auth_handler.pyÚ__init__   s$   ú	€ÿzAuthHandler.__init__c                 C   s   t jdi | j¤ŽS )zGet database connectionNr,   )r   Úconnectr!   )r*   r,   r,   r-   Úget_connection%   s   zAuthHandler.get_connectionc                 C   s   d|   dd¡› dS )Nú`z``©Úreplace)Ú
identifierr,   r,   r-   Ú_quote_identifier)   s   zAuthHandler._quote_identifierc                 C   s   |  d||f¡ | ¡ d uS )NzSELECT 1
            FROM information_schema.tables
            WHERE table_schema = %s AND table_name = %s
            LIMIT 1©ÚexecuteÚfetchone)r*   ÚcursorÚschemaÚ
table_namer,   r,   r-   Ú_table_exists-   s
   ûzAuthHandler._table_existsc                 C   s\   d|› }|  d||f¡ | ¡ }|r|d S |  d|d|› f¡ | ¡ }|r,|d S d S )NÚ7987_zSELECT table_name AS name
            FROM information_schema.tables
            WHERE table_schema = %s AND table_name = %s
            LIMIT 1Únamez³SELECT table_name AS name
            FROM information_schema.tables
            WHERE table_schema = %s AND table_name LIKE %s
            ORDER BY table_name
            LIMIT 1z%_r6   )r*   r9   r:   ÚsuffixÚ	preferredÚrowr,   r,   r-   Ú_find_template_table7   s   
ûúz AuthHandler._find_template_tablec                 C   s    t  ¡ }t  | d¡|¡ d¡S )zHash a password using bcryptzutf-8)ÚbcryptÚgensaltÚhashpwÚencodeÚdecode)r*   r   Úsaltr,   r,   r-   Úhash_passwordO   s   zAuthHandler.hash_passwordc                 C   s,   | rt | tƒs	dS |  d¡sdS t| ƒdkS )NF)z$2a$z$2b$z$2y$é2   )Ú
isinstanceÚstrÚ
startswithÚlen)Úpassword_hashr,   r,   r-   Ú_looks_like_bcrypt_hashT   s
   
z#AuthHandler._looks_like_bcrypt_hashc                 C   s   |   |¡sdS dS )z"Verify a password against its hashFN)rP   )r*   r   rO   r,   r,   r-   Úverify_password]   s   
ÿzAuthHandler.verify_passwordc                 C   s$   t |pdƒ ¡  ¡ }|dkrd}|S )Nr	   zbusiness-adminr   )rL   ÚstripÚlower)ÚclsÚroler,   r,   r-   Únormalize_roleb   s   zAuthHandler.normalize_rolec                 C   s„   dD ]=}|  d|f¡ | ¡ }|sqt|pi  d¡pdƒ ¡ }d|v s'd|v r?|  d|› d¡ |  d	|› d
¡ t d|¡ qdS )zHExpand legacy ENUM('admin','user','viewer') role columns for RBAC roles.)Úbusiness_usersÚuser_business_accesszå
                SELECT COLUMN_TYPE
                FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE()
                  AND TABLE_NAME = %s
                  AND COLUMN_NAME = 'role'
                ÚCOLUMN_TYPEÚ ÚenumÚviewerzUPDATE `z)` SET role = 'user' WHERE role = 'viewer'zALTER TABLE `z8` MODIFY COLUMN role VARCHAR(30) NOT NULL DEFAULT 'user'z)Expanded role column on %s for RBAC rolesN)r7   r8   rL   r   rS   r(   Úinfo)r*   r9   ÚtablerA   Úcolumn_typer,   r,   r-   Ú_ensure_role_column_supporti   s&   ø

ÿ
ÿ€êz'AuthHandler._ensure_role_column_supportc           
   
   C   s.  |   ¡ }zzn| ¡ }|  |¡ | d¡ | d¡ | d¡ | d¡ | d¡ g d¢g d¢g d¢g d¢g d	¢d
œ}dd„ | ¡ D ƒ}| ¡ D ]\}}| d||dd… f¡ qG| ¡ D ]\}}|D ]
}| d||f¡ qbq\| ¡  W n tyŠ }	 z| ¡  t	 
d|	¡ ‚ d}	~	ww W | ¡  dS | ¡  w )zHCreate additive role/scope tables without changing existing auth tables.aJ  
                CREATE TABLE IF NOT EXISTS permissions (
                    code VARCHAR(100) PRIMARY KEY,
                    description VARCHAR(255) DEFAULT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            aÅ  
                CREATE TABLE IF NOT EXISTS role_permissions (
                    role VARCHAR(30) NOT NULL,
                    permission_code VARCHAR(100) NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    PRIMARY KEY (role, permission_code),
                    INDEX idx_permission_code (permission_code)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            an  
                CREATE TABLE IF NOT EXISTS user_agent_mapping (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    bid VARCHAR(20) NOT NULL,
                    agent_name VARCHAR(255) DEFAULT NULL,
                    agent_phone VARCHAR(100) DEFAULT NULL,
                    agent_extension VARCHAR(100) DEFAULT NULL,
                    is_primary TINYINT(1) DEFAULT 0,
                    is_active TINYINT(1) DEFAULT 1,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    UNIQUE KEY uq_user_bid_agent (user_id, bid, agent_name, agent_phone, agent_extension),
                    INDEX idx_user_bid (user_id, bid),
                    INDEX idx_bid_agent_name (bid, agent_name),
                    INDEX idx_bid_agent_phone (bid, agent_phone),
                    FOREIGN KEY (user_id) REFERENCES business_users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            aS  
                CREATE TABLE IF NOT EXISTS user_group_mapping (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    bid VARCHAR(20) NOT NULL,
                    groupname VARCHAR(255) NOT NULL,
                    is_active TINYINT(1) DEFAULT 1,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    UNIQUE KEY uq_user_bid_group (user_id, bid, groupname),
                    INDEX idx_user_bid (user_id, bid),
                    INDEX idx_bid_group (bid, groupname),
                    FOREIGN KEY (user_id) REFERENCES business_users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            a.  
                CREATE TABLE IF NOT EXISTS user_permission_overrides (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    user_id INT NOT NULL,
                    bid VARCHAR(20) NOT NULL,
                    permission_code VARCHAR(100) NOT NULL,
                    allowed TINYINT(1) NOT NULL,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                    UNIQUE KEY uq_user_bid_permission (user_id, bid, permission_code),
                    INDEX idx_user_bid (user_id, bid),
                    FOREIGN KEY (user_id) REFERENCES business_users(id) ON DELETE CASCADE
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            )ú
users.viewúusers.createzusers.updatezusers.disablezusers.reset_passwordzroles.assignzagents.manageúcalls.view_allúcalls.view_detailúcalls.view_transcriptzcalls.edit_transcriptzcalls.delete_transcriptzcalls.reprocessúleads.view_allzleads.updateúanalytics.view_allzanalytics.leaderboardzexports.downloadz
crm.managezquality.managezsettings.managezpipeline.managezaudit.view_business)ra   úcalls.view_teamrd   re   úleads.view_teamúanalytics.view_teamzanalytics.leaderboard_teamzexports.team_download)údashboard.viewrc   rd   re   rf   rg   )rk   úcalls.view_ownrd   zcalls.view_transcript_ownúleads.view_ownúanalytics.view_ownzprofile.manage_own)r
   r   r   r	   r   c                 S   s,   i | ]}|D ]}||  d d¡  dd¡“qqS )Ú_Ú Ú.z: r2   )Ú.0ÚpermsÚcoder,   r,   r-   Ú
<dictcomp>é   s    ýþÿz2AuthHandler.ensure_rbac_tables.<locals>.<dictcomp>zBINSERT IGNORE INTO permissions (code, description) VALUES (%s, %s)Néÿ   zKINSERT IGNORE INTO role_permissions (role, permission_code) VALUES (%s, %s)zError ensuring RBAC tables: %s)r0   r9   r`   r7   ÚvaluesÚitemsÚcommitr'   Úrollbackr(   ÚerrorÚclose)
r*   Úconnr9   Údefault_permissionsÚdescriptionsrt   ÚdescriptionrU   rs   r+   r,   r,   r-   r&   ƒ   sN   



	

æþþþÿ€ýÿzAuthHandler.ensure_rbac_tablesc                 C   s  |   ¡ }zzzC| ¡ }| d¡ dd„ | ¡ pg D ƒ}g }d|vr%| d¡ d|vr.| d¡ d|vr7| d	¡ |rG| d
d |¡ ¡ | ¡  W n( typ } zt 	d|¡ z| 
¡  W n	 tye   Y nw W Y d}~nd}~ww W | ¡  dS W | ¡  dS | ¡  w )zBAdd bid / business_name / action_code if missing (additive ALTER).z©
                SELECT COLUMN_NAME FROM information_schema.COLUMNS
                WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_activity_log'
                c                 S   ó   h | ]}|d  ’qS )ÚCOLUMN_NAMEr,   )rr   Úrr,   r,   r-   Ú	<setcomp>  ó    zEAuthHandler.ensure_user_activity_log_audit_columns.<locals>.<setcomp>Úbidz'ADD COLUMN bid VARCHAR(64) DEFAULT NULLÚbusiness_namez2ADD COLUMN business_name VARCHAR(255) DEFAULT NULLÚaction_codez/ADD COLUMN action_code VARCHAR(32) DEFAULT NULLzALTER TABLE user_activity_log z, z.Could not extend user_activity_log columns: %sN)r0   r9   r7   ÚfetchallÚappendÚjoinry   r'   r(   r)   rz   r|   )r*   r}   r9   ÚexistingÚpartsr+   r,   r,   r-   Ú&ensure_user_activity_log_audit_columns  s>   ÿ


€ÿ€ü€€ z2AuthHandler.ensure_user_activity_log_audit_columnsc                 C   sœ   | sd S t | ƒ ¡  ¡ }|dkrdS |dkrdS | d¡s&| d¡s&|dkr(dS | d¡s1|d	kr3d
S d|v s@d|v s@| d¡rBdS d|v sJd|v rLdS d S )NÚloginÚLOGINÚlogoutÚLOGOUTÚdeleteÚrevokeÚDELETEÚcreateÚregisterÚCREATEÚchangeÚupdateÚ_updateÚUPDATEÚretryÚ	reprocessÚRETRY)rL   rR   rS   rM   Úendswith)Úactivity_typeÚtr,   r,   r-   Ú_infer_action_code   s    zAuthHandler._infer_action_codeNc
                 C   s¶   |   ¡  |	du r|  |¡}	|  ¡ }
zDz|
 ¡ }| d|||||||||	f	¡ |
 ¡  W n tyG } zt dt	|ƒ› ¡ W Y d}~nd}~ww W |
 
¡  dS W |
 
¡  dS |
 
¡  w )z=Persist audit events (distinct from orchestration file logs).NzÒINSERT INTO user_activity_log
                (user_id, username, activity_type, description, ip_address, user_agent, bid, business_name, action_code)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)zError logging activity: )rŽ   r£   r0   r9   r7   ry   r'   r(   r{   rL   r|   )r*   Úuser_idÚusernamer¡   r€   Ú
ip_addressÚ
user_agentr†   r‡   rˆ   r}   r9   r+   r,   r,   r-   Úlog_activity3  s8   
÷ü €ÿÿþzAuthHandler.log_activityFc              	   C   s@   |||||t  ¡ t| jd t  ¡ dœ}tj|| j| jd}|S )z=Generate a JWT token with user info and accessible businesses)Údays)r¤   r¥   ÚemailÚ
businessesÚ	is_masterÚexpÚiat©Ú	algorithm)r   Úutcnowr   r$   ÚjwtrF   r"   r#   )r*   r¤   r¥   rª   r«   r¬   ÚpayloadÚtokenr,   r,   r-   Úgenerate_jwt_token\  s   ù	zAuthHandler.generate_jwt_tokenc                 C   sH   zt j|| j| jgd}|W S  t jy   Y dS  t jy#   Y dS w )zDecode and validate a JWT token©Ú
algorithmsN)r²   rG   r"   r#   ÚExpiredSignatureErrorÚInvalidTokenError©r*   r´   r³   r,   r,   r-   Údecode_jwt_tokenj  s   ÿzAuthHandler.decode_jwt_tokenr	   c              
   C   s&  |   ¡ }z‰z[| ¡ }|  |¡}|| jvr ddidfW W | ¡  S | d||f¡ | ¡ r8ddidfW W | ¡  S |  |¡}	| d|||	||||f¡ | ¡  |j	}
|
||||dœd	fW W | ¡  S  t
y } z!| ¡  t d
t|ƒ› ¡ dt|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )z/Create a new user (no business assignment here)r{   úAInvalid role. Use admin, business_admin, manager, user, or agent.é  z?SELECT id FROM business_users WHERE username = %s OR email = %sú Username or email already existsé™  z¼INSERT INTO business_users
                (username, email, password_hash, plain_password, full_name, role, is_master, is_active)
                VALUES (%s, %s, %s, %s, %s, %s, %s, TRUE))Úidr¥   rª   rU   r¬   éÉ   zError creating user: éô  N)r0   r9   rV   ÚVALID_ROLESr|   r7   r8   rI   ry   Ú	lastrowidr'   rz   r(   r{   rL   )r*   r¥   rª   r   Ú	full_namerU   r¬   r}   r9   rO   r¤   r+   r,   r,   r-   Úcreate_usert  sL   


%Þþ

æüûú
û
€û
zAuthHandler.create_userc              
   C   s¤  |   ¡ }zÈzš| ¡ }d}z| d¡ | ¡ du}W n ty$   d}Y nw | d|f¡ | ¡ }|s>ddidfW W | ¡  S |  || d¡¡}| d	¡}	|sV|	rV||	krVd
}|sf| d¡rf|| d¡krfd
}|stddidfW W | ¡  S |r„| d|  |¡|||f¡ n| d|  |¡||f¡ | 	¡  ddidfW W | ¡  S  tyÌ }
 z!| 
¡  t dt|
ƒ› ¡ dt|
ƒidfW  Y d}
~
W | ¡  S d}
~
ww | ¡  w )z?Change a user's password after verifying the existing password.FzþSELECT 1
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_SCHEMA = DATABASE()
                      AND TABLE_NAME = 'business_users'
                      AND COLUMN_NAME = 'plain_password2'
                    LIMIT 1Nz‰SELECT id, username, password_hash, plain_password
                FROM business_users
                WHERE id = %s AND is_active = TRUEr{   úUser not foundé”  rO   Úplain_passwordTzPrevious password is incorrecté‘  zUPDATE business_users
                    SET password_hash = %s, plain_password = %s, plain_password2 = %s
                    WHERE id = %szwUPDATE business_users
                    SET password_hash = %s, plain_password = %s
                    WHERE id = %sÚmessagezPassword changed successfullyéÈ   zError changing user password: rÂ   )r0   r9   r7   r8   r'   r|   rQ   r   rI   ry   rz   r(   r{   rL   )r*   r¤   Úprevious_passwordÚnew_passwordr}   r9   Úhas_plain_password2r	   Úpassword_okrÉ   r+   r,   r,   r-   Úchange_user_password£  sb   ÿÿü
"à

êüü
û
€û
z AuthHandler.change_user_passwordc                 C   sR   |   ¡ }z| ¡ }|r| d||f¡ n| d|f¡ | ¡ W | ¡  S | ¡  w )z3Return the primary login user linked to a business.a  SELECT bu.id, bu.username, bu.email, bu.plain_password
                    FROM business_users bu
                    JOIN user_business_access uba ON uba.user_id = bu.id
                    WHERE uba.bid = %s AND bu.id = %s AND bu.is_active = TRUE
                    LIMIT 1aE  SELECT bu.id, bu.username, bu.email, bu.plain_password
                    FROM business_users bu
                    JOIN user_business_access uba ON uba.user_id = bu.id
                    WHERE uba.bid = %s AND bu.is_active = TRUE AND bu.is_master = FALSE
                    ORDER BY bu.id ASC
                    LIMIT 1)r0   r9   r7   r8   r|   )r*   r†   r¤   r}   r9   r,   r,   r-   Úget_business_login_userâ  s   ú	ù	z#AuthHandler.get_business_login_userc              
   C   s¦  |   ¡ }zÉz›| ¡ }| j||d}|sddidfW W | ¡  S |d }	t|p'dƒ ¡ }t|p/dƒ ¡  ¡ }t|p9dƒ}|sIddidfW W | ¡  S |sWdd	idfW W | ¡  S |sedd
idfW W | ¡  S | d|||	f¡ | ¡ r~ddidfW W | ¡  S | d|||  	|¡||	f¡ | 
¡  |	|||dœdœdfW W | ¡  S  tyÍ }
 z!| ¡  t dt|
ƒ› ¡ dt|
ƒidfW  Y d}
~
W | ¡  S d}
~
ww | ¡  w )z;Master-admin update of a business login user's credentials.)r¤   r{   zBusiness login user not foundrÈ   rÀ   rZ   zUsername is requiredr½   zEmail is requiredzPassword is requiredzvSELECT id FROM business_users
                WHERE (username = %s OR email = %s) AND id != %s
                LIMIT 1r¾   r¿   zŠUPDATE business_users
                SET username = %s, email = %s, password_hash = %s, plain_password = %s
                WHERE id = %s)r¥   r   rª   )Úbusiness_user_idÚcredentialsrÌ   z%Error updating business credentials: rÂ   N)r0   r9   rÒ   r|   rL   rR   rS   r7   r8   rI   ry   r'   rz   r(   r{   )r*   r†   r¥   rª   r   r¤   r}   r9   ÚtargetÚ	target_idr+   r,   r,   r-   Ú update_business_user_credentialsþ  sb   
*Ø
#Þ
!à
ãü
ìüýþù
û
€û
z,AuthHandler.update_business_user_credentialsc              
   C   s:  |   ¡ }z“ze| ¡ }|  |¡}|| jvr ddidfW W | ¡  S | d|f¡ | ¡ s7ddidfW W | ¡  S | d|f¡ | ¡ sNddidfW W | ¡  S | d	||||f¡ | ¡  d
|||dœdfW W | ¡  S  ty— } z!| 	¡  t
 dt|ƒ› ¡ dt|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )z Assign business access to a userr{   r¼   r½   ú)SELECT bid FROM businesses WHERE bid = %súBusiness not foundrÈ   z+SELECT id FROM business_users WHERE id = %srÇ   z‹INSERT INTO user_business_access (user_id, bid, role)
                VALUES (%s, %s, %s)
                ON DUPLICATE KEY UPDATE role = %szBusiness access granted)rË   r¤   r†   rU   rÌ   z!Error assigning business access: rÂ   N)r0   r9   rV   rÃ   r|   r7   r8   ry   r'   rz   r(   r{   rL   )r*   r¤   r†   rU   r}   r9   r+   r,   r,   r-   Úassign_business_access1  s<   


ç
ì
ñ
ü
û
€û
z"AuthHandler.assign_business_accessc           
   
   C   sä   |   ¡ }zhzD| ¡ }| d|f¡ | ¡ }|D ]*}|  | d¡¡}|  || d¡|¡}|  || d¡|¡}||d< ||d< ||d< q|W W | ¡  S  t	yl }	 zt
 dt|	ƒ› ¡ g W  Y d}	~	W | ¡  S d}	~	ww | ¡  w )z'Get all businesses accessible to a userzîSELECT b.bid, b.name, b.description, uba.role
                FROM user_business_access uba
                JOIN businesses b ON uba.bid = b.bid
                WHERE uba.user_id = %s AND b.is_active = TRUE
                ORDER BY b.namerU   r†   ÚpermissionsÚscopezError getting user businesses: N)r0   r9   r7   r‰   rV   r   Úget_permissions_for_userÚget_user_scoper|   r'   r(   r{   rL   )
r*   r¤   r}   r9   r«   ÚbusinessrU   rÛ   rÜ   r+   r,   r,   r-   Úget_user_businessesW  s0   ú

ü
€ü
zAuthHandler.get_user_businessesc              
   C   sr  |   ¡ }z.zÿ| ¡ }| d||f¡ | ¡ }|s%ddidfW W | ¡  S |  || d¡¡}d}	|sb| d¡}
| d¡}|
rG||
krGd	}d	}	n|rR||krRd	}d	}	n| d¡rb|| d¡krbd	}d	}	|spddidfW W | ¡  S |	r‰|  |¡}| d
||d f¡ t 	d|d ¡ |  
|d ¡}|d r­| d¡ | ¡ }|D ]}dg|d< ddi|d< qŸ| d|d f¡ | ¡  | j|d |d |d ||d d}| j|d |d dd|d › d||dd ||d |d |d |d |  |d ¡|d |dœd œd!fW W | ¡  S  ty3 } z!| ¡  t d"t|ƒ› ¡ dt|ƒid#fW  Y d$}~W | ¡  S d$}~ww | ¡  w )%z&Authenticate user and create JWT tokenzîSELECT id, username, email, password_hash, plain_password, plain_password2, full_name,
                role, is_master, is_active
                FROM business_users
                WHERE (username = %s OR email = %s) AND is_active = TRUEr{   zInvalid credentialsrÊ   rO   FrÉ   Úplain_password2Tz:UPDATE business_users SET password_hash = %s WHERE id = %srÀ   z=Upgraded password hash for user %s due to legacy/invalid hashr¥   r¬   zŸSELECT bid, name, description, 'admin' as role
                    FROM businesses
                    WHERE is_active = TRUE
                    ORDER BY nameÚ*rÛ   ÚtypeÚallrÜ   z:UPDATE business_users SET last_login = NOW() WHERE id = %srª   )r¤   r¥   rª   r«   r¬   r   zUser z logged in successfullyr   )r¤   r¥   r¡   r€   r¦   r§   rˆ   rÅ   rU   )rÀ   r¥   rª   rÅ   rU   r¬   r«   )r´   r	   rÌ   zError during login: rÂ   N)r0   r9   r7   r8   r|   rQ   r   rI   r(   r)   rà   r‰   ry   rµ   r¨   rV   r'   rz   r{   rL   )r*   r¥   r   r¦   r§   r}   r9   r	   rÐ   Úneeds_upgraderÉ   rá   Únew_hashr«   rß   r´   r+   r,   r,   r-   r   u  s²   û
_¤


J¸

þþÿ
þû	ùùþõ
û
€û
zAuthHandler.loginc                 C   sD   |   |¡}|s	dS | d¡| d¡| d¡| dg ¡| dd¡dœS )	z)Validate a JWT token and return user infoNr¤   r¥   rª   r«   r¬   F)rÀ   r¥   rª   r«   r¬   )r»   r   rº   r,   r,   r-   Úvalidate_tokenç  s   


ûzAuthHandler.validate_tokenc                 C   s   ddidfS )z8Logout user (with JWT, we just rely on token expiration)rË   zLogged out successfullyrÌ   r,   )r*   r´   r,   r,   r-   r‘   õ  s   zAuthHandler.logoutc              
   C   s²   |   ¡ }zOz%| ¡ }| d¡ | ¡ }|D ]}|  |d ¡|d< q|dfW W | ¡  S  tyS } zt dt	|ƒ› ¡ dt	|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )	z!Get all users (master admin only)zÆSELECT id, username, email, plain_password, full_name, role, is_master, is_active,
                created_at, last_login
                FROM business_users
                ORDER BY created_at DESCrÀ   r«   rÌ   zError getting users: r{   rÂ   N)
r0   r9   r7   r‰   rà   r|   r'   r(   r{   rL   )r*   r}   r9   Úusersr	   r+   r,   r,   r-   Úget_all_usersû  s$   ÿ

ü
€ü
zAuthHandler.get_all_usersc              
   C   sœ   |   |¡}|  ¡ }z?z| ¡ }| d|f¡ tdd„ | ¡ pg D ƒƒW W | ¡  S  tyH } zt 	d||¡ g W  Y d}~W | ¡  S d}~ww | ¡  w )z?Return default permission codes for a role (no user overrides).ú<SELECT permission_code FROM role_permissions WHERE role = %sc                 S   r   ©Úpermission_coder,   ©rr   rA   r,   r,   r-   r„     r…   z8AuthHandler.get_role_base_permissions.<locals>.<setcomp>z/Could not load base permissions for role=%s: %sN)
rV   r0   r9   r7   Úsortedr‰   r|   r'   r(   r)   )r*   rU   r}   r9   r+   r,   r,   r-   Úget_role_base_permissions  s"   
þ
ü
€ü
z%AuthHandler.get_role_base_permissionsc              
   C   s  |   |¡}t|  |¡ƒ}dd„ |pg D ƒ}|  ¡ }zpzF| ¡ }| d|t|ƒf¡ t|| ƒD ]}	| d|t|ƒ|	f¡ q/t|| ƒD ]}	| d|t|ƒ|	f¡ qC| ¡  ddidfW W | 	¡  S  t
y‰ }
 z| ¡  t d	|
¡ d
t|
ƒidfW  Y d}
~
W | 	¡  S d}
~
ww | 	¡  w )zJPersist overrides so effective permissions match desired_permissions list.c                 S   s$   h | ]}t |ƒ ¡ rt |ƒ ¡ ’qS r,   )rL   rR   )rr   rt   r,   r,   r-   r„   (  s   $ z8AuthHandler.sync_permission_overrides.<locals>.<setcomp>zEDELETE FROM user_permission_overrides WHERE user_id = %s AND bid = %szyINSERT INTO user_permission_overrides (user_id, bid, permission_code, allowed)
                    VALUES (%s, %s, %s, 1)zyINSERT INTO user_permission_overrides (user_id, bid, permission_code, allowed)
                    VALUES (%s, %s, %s, 0)rË   zPermission overrides updatedrÌ   z&Error syncing permission overrides: %sr{   rÂ   N)rV   Úsetrï   r0   r9   r7   rL   rî   ry   r|   r'   rz   r(   r{   )r*   r¤   r†   rU   Údesired_permissionsÚbaseÚdesiredr}   r9   rt   r+   r,   r,   r-   Úsync_permission_overrides$  s>   

þýý
û
€û
z%AuthHandler.sync_permission_overridesc           
   
   C   sh  |   |¡}|  ¡ }z¥zG| ¡ }| d|f¡ dd„ | ¡ pg D ƒ}| d|t|ƒf¡ | ¡ p0g D ]}|d }| d¡rB| |¡ q1| |¡ q1t	|ƒW W | 
¡  S  ty® }	 zQt d|||	¡ || jv rug d¢W  Y d	}	~	W | 
¡  S |d
krˆg d¢W  Y d	}	~	W | 
¡  S |dkr›g d¢W  Y d	}	~	W | 
¡  S g d¢W  Y d	}	~	W | 
¡  S d	}	~	ww | 
¡  w )zCReturn role permissions plus user-level overrides for one business.rê   c                 S   r   rë   r,   rí   r,   r,   r-   r„   O  r…   z7AuthHandler.get_permissions_for_user.<locals>.<setcomp>z~SELECT permission_code, allowed
                FROM user_permission_overrides
                WHERE user_id = %s AND bid = %srì   Úallowedz1Could not load permissions for user=%s bid=%s: %s)rc   rf   rg   ra   rb   Nr   )rl   rm   rn   r   )rh   ri   rj   )rc   rf   rg   )rV   r0   r9   r7   r‰   rL   r   ÚaddÚdiscardrî   r|   r'   r(   r)   ÚADMIN_ROLES)
r*   r¤   r†   rU   r}   r9   rÛ   rA   rt   r+   r,   r,   r-   rÝ   E  sH   
þ
ü


ö

ú
ü
þ
€ö

z$AuthHandler.get_permissions_for_userc              
   C   óŒ   |   ¡ }z<z| ¡ }| d|t|ƒf¡ | ¡ pg W W | ¡  S  ty@ } zt d|||¡ g W  Y d }~W | ¡  S d }~ww | ¡  w )NzåSELECT id, agent_name, agent_phone, agent_extension, is_primary, is_active
                FROM user_agent_mapping
                WHERE user_id = %s AND bid = %s AND is_active = 1
                ORDER BY is_primary DESC, id ASCz4Could not load agent mappings for user=%s bid=%s: %s©	r0   r9   r7   rL   r‰   r|   r'   r(   r)   ©r*   r¤   r†   r}   r9   r+   r,   r,   r-   Úget_agent_mappingsi  ó    
û
ü
€ü
zAuthHandler.get_agent_mappingsc              
   C   rù   )Nz°SELECT id, groupname, is_active
                FROM user_group_mapping
                WHERE user_id = %s AND bid = %s AND is_active = 1
                ORDER BY groupname ASCz4Could not load group mappings for user=%s bid=%s: %srú   rû   r,   r,   r-   Úget_group_mappings{  rý   zAuthHandler.get_group_mappingsc                 C   sÄ   |   |¡}|| jv s|dkrddiS |dkr>dd„ |  ||¡D ƒ}|  ||¡}d|dd„ |D ƒd	d„ |D ƒd
d„ |D ƒdœS |dkr^|  ||¡}ddd„ |D ƒdd„ |D ƒdd„ |D ƒdœS ddiS )Nr	   rã   rß   r   c                 S   ó    g | ]}|  d ¡r|  d ¡‘qS )Ú	groupname©r   rí   r,   r,   r-   Ú
<listcomp>’  ó     z.AuthHandler.get_user_scope.<locals>.<listcomp>Úteamc                 S   rÿ   ©Ú
agent_namer  rí   r,   r,   r-   r  —  r  c                 S   rÿ   ©Úagent_phoner  rí   r,   r,   r-   r  ˜  r  c                 S   rÿ   ©Úagent_extensionr  rí   r,   r,   r-   r  ™  r  )rã   Ú
groupnamesÚagent_namesÚagent_phonesÚagent_extensionsr   Ú	own_agentc                 S   rÿ   r  r  rí   r,   r,   r-   r  Ÿ  r  c                 S   rÿ   r  r  rí   r,   r,   r-   r     r  c                 S   rÿ   r	  r  rí   r,   r,   r-   r  ¡  r  )rã   r  r  r  )rV   rø   rþ   rü   )r*   r¤   r†   rU   ÚgroupsÚagentsr,   r,   r-   rÞ     s(   
ûüzAuthHandler.get_user_scopec                 C   sh  |   ¡ }zªz€| ¡ }| d|t|ƒf¡ t|pg ƒD ][\}}t|tƒs$qt| d¡p0| d¡p0dƒ ¡ p5d}t| d¡pB| d¡pBdƒ ¡ pGd}	t| d¡pT| d	¡pTdƒ ¡ pYd}
|sa|	sa|
saq| d
|t|ƒ||	|
|dkrqdndf¡ q| 	¡  ddidfW W | 
¡  S  ty® } z| ¡  t d|¡ dt|ƒidfW  Y d}~W | 
¡  S d}~ww | 
¡  w )z2Replace active agent mappings for a user/business.zKUPDATE user_agent_mapping SET is_active = 0 WHERE user_id = %s AND bid = %sr  Ú	agentnamerZ   Nr  Úphoner
  Ú	extensiona  INSERT INTO user_agent_mapping
                    (user_id, bid, agent_name, agent_phone, agent_extension, is_primary, is_active)
                    VALUES (%s, %s, %s, %s, %s, %s, 1)
                    ON DUPLICATE KEY UPDATE is_primary = VALUES(is_primary), is_active = 1r   é   rË   zAgent mappings updatedrÌ   z"Error replacing agent mappings: %sr{   rÂ   )r0   r9   r7   rL   Ú	enumeraterK   Údictr   rR   ry   r|   r'   rz   r(   r{   )r*   r¤   r†   Úmappingsr}   r9   ÚidxÚitemr  r  r
  r+   r,   r,   r-   Úreplace_agent_mappings¥  s<   
þ
$$$û
û
€û
z"AuthHandler.replace_agent_mappingsc              
   C   sÞ   |   ¡ }zez;| ¡ }| d|t|ƒf¡ |pg D ]}t|pdƒ ¡ }|s%q| d|t|ƒ|f¡ q| ¡  ddidfW W | ¡  S  tyi } z| ¡  t	 
d|¡ dt|ƒid	fW  Y d
}~W | ¡  S d
}~ww | ¡  w )z2Replace active group mappings for a user/business.zKUPDATE user_group_mapping SET is_active = 0 WHERE user_id = %s AND bid = %srZ   z¨INSERT INTO user_group_mapping (user_id, bid, groupname, is_active)
                    VALUES (%s, %s, %s, 1)
                    ON DUPLICATE KEY UPDATE is_active = 1rË   zGroup mappings updatedrÌ   z"Error replacing group mappings: %sr{   rÂ   N)r0   r9   r7   rL   rR   ry   r|   r'   rz   r(   r{   )r*   r¤   r†   r  r}   r9   r   r+   r,   r,   r-   Úreplace_group_mappingsÆ  s4   
þü
û
€û
z"AuthHandler.replace_group_mappingsc              
   C   s  |   ¡ }z‚z\| ¡ }| dt|ƒf¡ | ¡ pg }|D ]<}|  | d¡¡}||d< |  | d¡||¡|d< |  | d¡||¡|d< |  	| d¡|¡|d< |  
| d¡|¡|d< q|dfW W | ¡  S  ty† } zt d	|¡ d
t|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )z:Get users assigned to a business with role/scope metadata.a˜  SELECT bu.id, bu.username, bu.email, bu.full_name, bu.role AS global_role,
                    bu.is_master, bu.is_active, bu.created_at, bu.last_login, uba.role
                FROM user_business_access uba
                JOIN business_users bu ON bu.id = uba.user_id
                WHERE uba.bid = %s
                ORDER BY CASE WHEN uba.role IN ('admin', 'business_admin') THEN 0 ELSE 1 END, bu.id ASCrU   rÀ   rÛ   rÜ   Úagent_mappingsÚgroup_mappingsrÌ   z#Error getting users by business: %sr{   rÂ   N)r0   r9   r7   rL   r‰   rV   r   rÝ   rÞ   rü   rþ   r|   r'   r(   r{   )r*   r†   r}   r9   rè   r	   rU   r+   r,   r,   r-   Úget_users_by_businessâ  s0   ù	

ü
€ü
z!AuthHandler.get_users_by_businessc                 C   s„  t |ƒ}|  ¡ }g }z1z¯| ¡ }| d|f¡ | ¡ r(ddidfW W | ¡  S | jd }g d¢}i }	|D ] }
|  |||
¡}|sQdd|
› idf  W W | ¡  S ||	|
< q5|D ]"}
|› d	|
› }|  |||¡rzdd
|› didf  W W | ¡  S qX|D ]#}
|› d	|
› }|	|
 }| d|  	|¡› d|  	|¡› ¡ | 
|¡ q}| d|||f¡ | ¡  |||dœdfW W | ¡  S  tjyÔ   | ¡  ddidf Y W | ¡  S  ty< } z\| ¡  t dt |ƒ› ¡ |r%z| ¡ }|D ]}| d|  	|¡› ¡ qô| ¡  W n ty$ } zt dt |ƒ› ¡ W Y d}~nd}~ww dt |ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )z)Create a new business (master admin only)rØ   r{   zBusiness ID already existsr¿   r   )Ú	raw_callsÚsarvamresponseÚcallanalyticszNo template table found for rÂ   ro   zTable z already existszCREATE TABLE z LIKE zdINSERT INTO businesses (bid, name, description, is_active)
                VALUES (%s, %s, %s, TRUE))r†   r>   r€   rÁ   zError creating business: zDROP TABLE IF EXISTS zError cleaning up tables: N)rL   r0   r9   r7   r8   r|   r!   rB   r<   r5   rŠ   ry   r   ÚIntegrityErrorrz   r'   r(   r{   )r*   r†   r>   r€   r}   Úcreated_tablesr9   r:   ÚsuffixesÚ	templatesr?   Útemplater;   r+   Úcleanup_cursorÚcleanup_errorr,   r,   r-   Úcreate_businessÿ  sŠ   
9
É
0
Ñ
*Õÿÿýýü
î
ñÿ €ÿ
€ñ
zAuthHandler.create_businessc              
   C   s–   |   ¡ }zAz| ¡ }| d¡ | ¡ }|dfW W | ¡  S  tyE } zt dt|ƒ› ¡ dt|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )zGet all businesseszrSELECT bid, name, description, is_active, created_at
                FROM businesses
                ORDER BY namerÌ   zError getting businesses: r{   rÂ   N©	r0   r9   r7   r‰   r|   r'   r(   r{   rL   )r*   r}   r9   r«   r+   r,   r,   r-   Úget_all_businessesE  s    ÿ

ü
€ü
zAuthHandler.get_all_businessesc                 C   sÎ   | rt | tƒs	dS |  ¡ }t|ƒdkrdS | d¡}t|ƒdkr"dS z:t|d ƒt|d ƒt|d ƒ}}}d|  ko@dkn  o[d|  koLd	kn  o[d
|  koYdkW S   W S  tyf   Y dS w )NFé
   Ú-é   r   r  é   é   é   i²  i4  )rK   rL   rR   rN   Úsplitr   Ú
ValueError)ÚvalueÚsr   ÚyÚmÚdr,   r,   r-   Ú_is_yyyy_mm_ddW  s   
(LÿzAuthHandler._is_yyyy_mm_ddéd   r   c              
   C   s  |   ¡  |  ¡ }z|zP| ¡ }d}g }|dur=zt|ƒ}W n ttfy+   d}Y nw |dur=|dkr=|d7 }| |¡ |rL|d7 }| t|ƒ ¡ ¡ |r[|d7 }| t|ƒ ¡ ¡ |durrt|ƒ ¡ rr|d7 }| t|ƒ ¡ ¡ |r‡t|ƒ ¡ r‡|d7 }| t|ƒ ¡ ¡ |rœt|ƒ ¡ rœ|d	7 }| t|ƒ ¡ ¡ |	r°|  	|	¡r°|d
7 }| t|	ƒ ¡ ¡ |
rÄ|  	|
¡rÄ|d7 }| t|
ƒ ¡ ¡ d|› }| 
|t|ƒ¡ | ¡ pÖi }t| d¡pÞdƒ}tdtt|pçdƒdƒƒ}tdt|pòdƒƒ}d|› d}t|ƒ||g }| 
||¡ | ¡ pg }|D ]<}| d¡p!|  | d¡¡}||d< | d¡p6| d¡p6| d¡}|sF| d¡rFt|d ƒ}|pJd|d< q||dœdfW W | ¡  S  ty… } zt dt|ƒ› ¡ dt|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w ) zKReturn structured audit rows + total count (for Audit Trail UI pagination).z	WHERE 1=1Nr   z AND al.user_id = %sz AND al.activity_type = %sz AND al.action_code = %sz AND al.bid = %sz. AND LOCATE(LOWER(%s), LOWER(al.username)) > 0z1 AND LOCATE(LOWER(%s), LOWER(al.description)) > 0z AND DATE(al.created_at) >= %sz AND DATE(al.created_at) <= %sz1SELECT COUNT(*) AS cnt FROM user_activity_log al Úcntr  r;  rÂ   a#  SELECT al.id, al.user_id, al.username, al.activity_type,
                       al.description, al.ip_address, al.user_agent, al.created_at,
                       al.bid, al.business_name, al.action_code,
                       bu.email AS user_email, bu.full_name AS user_full_name,
                       b.name AS business_join_name,
                       user_businesses.business_names AS assigned_business_names
                FROM user_activity_log al
                LEFT JOIN business_users bu ON al.user_id = bu.id
                LEFT JOIN businesses b
                    ON al.bid IS NOT NULL
                    AND al.bid COLLATE utf8mb4_unicode_ci = b.bid
                LEFT JOIN (
                    SELECT uba.user_id, GROUP_CONCAT(b2.name ORDER BY b2.name SEPARATOR ', ') AS business_names
                    FROM user_business_access uba
                    LEFT JOIN businesses b2 ON uba.bid = b2.bid
                    GROUP BY uba.user_id
                ) user_businesses ON al.user_id = user_businesses.user_id
                zO
                ORDER BY al.created_at DESC
                LIMIT %s OFFSET %srˆ   r¡   Úaction_code_resolvedr‡   Úbusiness_join_nameÚassigned_business_namesr†   u   â€”Úbusiness_display_name)ÚlogsÚtotalrÌ   zError getting activity log: r{   )rŽ   r0   r9   r   Ú	TypeErrorr4  rŠ   rL   rR   r:  r7   Úlistr8   r   ÚmaxÚminr‰   r£   r|   r'   r(   r{   )r*   ÚlimitÚoffsetr¤   r¡   rˆ   r†   Úusername_containsÚdescription_containsÚ	date_fromÚdate_tor}   r9   Ú	where_sqlÚparamsÚuidÚ	count_sqlÚ	count_rowrB  ÚqueryÚlist_paramsrA  rA   rt   r>   r+   r,   r,   r-   Úget_activity_logg  s€   ÿ

ï"
ü
€ü
zAuthHandler.get_activity_logc              
   C   s’   |   ¡ }z?z| ¡ }| d¡ | ¡  t d¡ W n ty5 } zt dt|ƒ› ¡ W Y d}~nd}~ww W | 	¡  dS W | 	¡  dS | 	¡  w )z/Create embed_api_keys table if it doesn't existaô  
                CREATE TABLE IF NOT EXISTS embed_api_keys (
                    id INT AUTO_INCREMENT PRIMARY KEY,
                    api_key VARCHAR(64) UNIQUE NOT NULL,
                    bid VARCHAR(20) NOT NULL,
                    partner_name VARCHAR(100) NOT NULL,
                    allowed_origins TEXT DEFAULT NULL,
                    is_active TINYINT(1) DEFAULT 1,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    expires_at TIMESTAMP NULL DEFAULT NULL,
                    last_used_at TIMESTAMP NULL DEFAULT NULL,
                    INDEX idx_api_key (api_key),
                    INDEX idx_bid (bid)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
            zembed_api_keys table ensuredzError ensuring embed tables: N)
r0   r9   r7   ry   r(   r]   r'   r{   rL   r|   )r*   r}   r9   r+   r,   r,   r-   r%   ×  s   
 €ÿÿþzAuthHandler.ensure_embed_tablesc                   C   s   dt  d¡ S )z*Generate a unique API key for embed accessÚ	sk_embed_é   )ÚsecretsÚ	token_hexr,   r,   r,   r-   Úgenerate_api_keyò  s   zAuthHandler.generate_api_keyc           	   
   C   sü   |   ¡ }ztzF| ¡ }| d|f¡ | ¡ s!ddidfW W | ¡  S |  ¡ }| d|||||f¡ | ¡  |j|||||r@t|ƒnddœdfW W | ¡  S  t	yx } z!| 
¡  t d	t|ƒ› ¡ dt|ƒid
fW  Y d}~W | ¡  S d}~ww | ¡  w )z)Create a new embed API key for a businessrØ   r{   rÙ   rÈ   zINSERT INTO embed_api_keys
                (api_key, bid, partner_name, allowed_origins, expires_at)
                VALUES (%s, %s, %s, %s, %s)N)rÀ   Úapi_keyr†   Úpartner_nameÚallowed_originsÚ
expires_atrÁ   zError creating embed API key: rÂ   )r0   r9   r7   r8   r|   rY  ry   rÄ   rL   r'   rz   r(   r{   )	r*   r†   r[  r\  r]  r}   r9   rZ  r+   r,   r,   r-   Úcreate_embed_api_key÷  s>   
èüúù
û
€û
z AuthHandler.create_embed_api_keyc              
   C   sÖ   |   ¡ }zaz7| ¡ }|r| d|f¡ n| d¡ | ¡ }|D ]}|d }d|dd…  |d< |d= q|dfW W | ¡  S  tye } zt d	t|ƒ› ¡ d
t|ƒidfW  Y d}~W | ¡  S d}~ww | ¡  w )z3List all embed API keys, optionally filtered by bida‡  SELECT ek.id, ek.api_key, ek.bid, b.name as business_name,
                       ek.partner_name, ek.allowed_origins, ek.is_active,
                       ek.created_at, ek.expires_at, ek.last_used_at
                    FROM embed_api_keys ek
                    LEFT JOIN businesses b ON ek.bid = b.bid
                    WHERE ek.bid = %s
                    ORDER BY ek.created_at DESCaa  SELECT ek.id, ek.api_key, ek.bid, b.name as business_name,
                       ek.partner_name, ek.allowed_origins, ek.is_active,
                       ek.created_at, ek.expires_at, ek.last_used_at
                    FROM embed_api_keys ek
                    LEFT JOIN businesses b ON ek.bid = b.bid
                    ORDER BY ek.created_at DESCrZ  z***iøÿÿÿNÚapi_key_maskedrÌ   zError listing embed API keys: r{   rÂ   r+  )r*   r†   r}   r9   ÚkeysÚkeyÚfull_keyr+   r,   r,   r-   Úlist_embed_api_keys  s2   øÿ	

ü
€ü
zAuthHandler.list_embed_api_keysc              
   C   sÈ   |   ¡ }zZz,| ¡ }| d|f¡ | ¡  |jdkr&ddidfW W | ¡  S ddidfW W | ¡  S  ty^ } z!| ¡  t 	d	t
|ƒ› ¡ dt
|ƒid
fW  Y d}~W | ¡  S d}~ww | ¡  w )z$Revoke (deactivate) an embed API keyz5UPDATE embed_api_keys SET is_active = 0 WHERE id = %sr   r{   zAPI key not foundrÈ   rË   zAPI key revoked successfullyrÌ   zError revoking embed API key: rÂ   N)r0   r9   r7   ry   Úrowcountr|   r'   rz   r(   r{   rL   )r*   Úkey_idr}   r9   r+   r,   r,   r-   Úrevoke_embed_api_keyG  s*   þ

	ù
û
€û
z AuthHandler.revoke_embed_api_keyc              
   C   sà   |   ¡ }zfzC| ¡ }| d||f¡ | ¡ }|s W W | ¡  dS |d r4|d t ¡ k r4W W | ¡  dS | d|d f¡ | ¡  |W W | ¡  S  tyj } zt	 
dt|ƒ› ¡ W Y d}~W | ¡  dS d}~ww | ¡  w )z1Validate an embed API key for a specific businessz¨SELECT id, api_key, bid, partner_name, allowed_origins, expires_at
                FROM embed_api_keys
                WHERE api_key = %s AND bid = %s AND is_active = 1Nr]  z<UPDATE embed_api_keys SET last_used_at = NOW() WHERE id = %srÀ   zError validating API key: )r0   r9   r7   r8   r|   r   r±   ry   r'   r(   r{   rL   )r*   rZ  r†   r}   r9   Ú
key_recordr+   r,   r,   r-   Úvalidate_api_key_  s8   üðôþ
ü€ü
zAuthHandler.validate_api_keyc                 C   s@   dt |ƒ||t ¡ tdd t ¡ dœ}tj|| j| jd}|S )z5Generate a short-lived JWT token for iframe embeddingÚembedr  )Úhours)rã   r†   r[  Ú
api_key_idr­   r®   r¯   )rL   r   r±   r   r²   rF   r"   r#   )r*   r†   r[  rk  r³   r´   r,   r,   r-   Úgenerate_embed_tokenƒ  s   úz AuthHandler.generate_embed_tokenc                 C   sŠ   z%t j|| j| jgd}| d¡dkrW dS | d¡| d¡| d¡dœW S  t jy5   t d	¡ Y dS  t jyD   t d
¡ Y dS w )zValidate an embed JWT tokenr¶   rã   ri  Nr†   r[  rk  )r†   r[  rk  zEmbed token expiredzInvalid embed token)	r²   rG   r"   r#   r   r¸   r(   r)   r¹   rº   r,   r,   r-   Úvalidate_embed_token  s   ý

þz AuthHandler.validate_embed_token)NNNNNN)F)Nr	   F)N)r	   )NN)
r;  r   NNNNNNNN)6Ú__name__Ú
__module__Ú__qualname__rÃ   rø   r.   r0   Ústaticmethodr5   r<   rB   rI   rP   rQ   ÚclassmethodrV   r`   r&   rŽ   r£   r¨   rµ   r»   rÆ   rÑ   rÒ   r×   rÚ   rà   r   rç   r‘   ré   rï   rô   rÝ   rü   rþ   rÞ   r  r  r  r*  r,  r:  rT  r%   rY  r^  rc  rf  rh  rl  rm  r,   r,   r,   r-   r      s    



~

ö
)

/
?

3&
r

!$
!
F

õp


%+$r   c                    ó   t ˆ ƒ‡ fdd„ƒ}|S )z.Decorator to require authentication for routesc                     sz   t j d¡}|r| d¡stddiƒdfS | dd¡}ddlm} |j}| 	|¡}|s3tdd	iƒdfS |t _
ˆ | i |¤ŽS )
NÚAuthorizationzBearer r{   z'Missing or invalid authorization headerrÊ   rZ   r   )Úcurrent_appzInvalid or expired token)r   Úheadersr   rM   r   r3   Úflaskru  Úauth_handlerrç   Úcurrent_user)ÚargsÚkwargsÚauth_headerr´   ru  rx  r	   ©Úfr,   r-   Údecorated_function§  s   
z(require_auth.<locals>.decorated_functionr   ©r~  r  r,   r}  r-   Úrequire_auth¥  s   r  c                    rs  )z(Decorator to require master admin accessc                     s.   t j}| d¡stddiƒdfS ˆ | i |¤ŽS )Nr¬   r{   zMaster admin access requiredé“  )r   ry  r   r   )rz  r{  r	   r}  r,   r-   r  Â  s   
z*require_master.<locals>.decorated_functionr   r€  r,   r}  r-   Úrequire_masterÀ  s   rƒ  c                    rs  )z=Decorator to ensure user has access to the requested businessc                     sp   |  d¡p
tj  d¡}tj}|  d¡rˆ | i |¤ŽS dd„ |  dg ¡D ƒ}||vr1tddiƒdfS ˆ | i |¤ŽS )	Nr†   r¬   c                 S   s   g | ]}|d  ‘qS )r†   r,   )rr   Úbr,   r,   r-   r  Ø  r…   zGrequire_business_access.<locals>.decorated_function.<locals>.<listcomp>r«   r{   zAccess denied to this businessr‚  )r   r   Ú	view_argsry  r   )rz  r{  r†   r	   Úuser_businessesr}  r,   r-   r  Í  s   
z3require_business_access.<locals>.decorated_functionr   r€  r,   r}  r-   Úrequire_business_accessË  s   r‡  )r   rC   r²   r   r   Ú	functoolsr   rw  r   r   ÚloggingrW  Ú	getLoggerrn  r(   r   r  rƒ  r‡  r,   r,   r,   r-   Ú<module>   s0    
           #