U
    i|                     @   s  d dl mZ d dl mZmZmZmZmZmZm	Z	 d dl
Z
d dlZd dlZd dlmZ d dlmZmZ d dlZd dlZd dlmZmZmZ d dlmZ d dlmZmZmZ d d	l mZmZmZm	Z	mZ d d
lmZ d dlZd dlmZ d dl m!Z! d dl"m#Z# d d	l mZmZmZm	Z	mZ d dlZd dlmZ d dl$Z$dd Z%G dd dZ&dd Z'dd Z(dd Z)dd Z*d"ddZ+dd Z,d#d d!Z-dS )$    )current_app)Flaskrender_templatesessionjsonifyrequestredirecturl_forN)Message)
save_Tokenconsultar_profile)ConfigHostSCHEME)wraps)datetime	timedeltatimezone)r   r   r   r	   r   )r   )get_connection)MIMEMultipart)MIMEText)r   c                   C   s   t tjd S )N
SECRET_KEY)strappconfig r   r   _C:\Users\victor.barrera\Documents\proyectos\elepV3\Elep\src\App\Security_Module\UserPassword.py<lambda>        r   c                   @   s"   e Zd ZdZdd ZdddZdS )EmailSenderuS   
    Clase para el envío de correos electrónicos sin depender de Flask-Mail.
    c                 C   s"   || _ || _|| _|| _|| _dS )uX  
        Inicializa el objeto EmailSender con configuración.
        
        Args:
            mail_server: Servidor SMTP
            mail_port: Puerto SMTP
            mail_use_tls: Usar TLS para la conexión
            mail_username: Usuario para autenticación SMTP
            mail_password: Contraseña para autenticación SMTP
        N)smtp_server	smtp_portuse_tlssender_emailsender_password)selfZmail_serverZ	mail_portZmail_use_tlsZmail_usernameZmail_passwordr   r   r   __init__)   s
    zEmailSender.__init__Nc              
   C   s  |s|st dt|tr |g}td}||d< | j|d< d||d< |r\|t|d |rp|t|d zJt	| j
| j.}| jr|  || j| j || W 5 Q R X W d	S  tk
 r } z$td
|  tt  W Y dS d}~X Y nX dS )u  
        Envía un correo electrónico.
        
        Args:
            recipients: Lista de destinatarios o un solo destinatario
            subject: Asunto del correo
            html_content: Contenido HTML del correo (opcional)
            text_content: Contenido de texto plano del correo (opcional)
            
        Returns:
            True si el correo se envió correctamente, False si hubo un error
        z:Debe proporcionar contenido HTML o de texto para el correoalternativeSubjectFromz, ToplainhtmlTError al enviar correo: FN)
ValueError
isinstancer   r   r#   joinattachr   smtplibSMTPr    r!   r"   starttlsloginr$   send_message	Exceptionprint	traceback
format_exc)r%   
recipientssubjecthtml_contentZtext_contentmsgserverer   r   r   
send_email:   s.    

zEmailSender.send_email)NN)__name__
__module____qualname____doc__r&   rA   r   r   r   r   r   $   s   r   c                 C   s&  zt |}t|}|s*tddidfW S |d }|d }ttjtdd }|||| d}|}	|}
|}t	d	t
t  t	d
t  tj|t dd}t|tr|d}t||}|dkrtdd| didfW S z\t dt d| d|
 d|	 d| }|	}||d}d}td|d}d}z.t|| jd |gd}||_|| d}W n tk
r } zt	d|  zBt| jd | jd  | jd! | jd | jd" }|j|||d#}W n@ tk
r } z t	d$|  t	t   W 5 d}~X Y nX W 5 d}~X Y nX |s(tdd%id&fW W S t|||d'W W S  tk
r } zHt	d(| d) t	d*t  d) tdd+t| id&f W Y W S d}~X Y nX W nz tk
r  } zZt	d,| d) t	d-t
| d) t	d*t  d) tdd.t| id&f W Y S d}~X Y nX dS )/uP   
    Genera un token JWT y envía un correo de activación al usuario.
    
    errorz(No se pudo obtener el perfil del usuario  NombreEmail  minutesIDusernamerH   expzTipo de SECRET_KEY:zValor de SECRET_KEY:HS256	algorithmutf-8NEl usuario con ID  no existe en el sistemaz:///password_confirm?token=&userid=&email=&name=)activation_url	user_nameu   Configuración de passwordSecurity/UserPasswordemail.htmldataFMAIL_USERNAMEsenderr;   TzError al usar Flask-Mail: MAIL_SERVER	MAIL_PORTMAIL_USE_TLSMAIL_PASSWORD)r=   zError al usar EmailSender: u*   No se pudo enviar el correo de activación  tokentoken_id
email_sentu-   ===== Error al enviar correo de activación:  ========== Traceback completo: r-   z&===== Error en send_activation_email:    ===== Tipo de excepción: Error: )intr   r   r   nowr   utcr   	timestampr8   typer   jwtencoder/   bytesdecoder   r   r   r   r
   r   r,   sendr7   r   rA   r9   r:   r   )r   mailZUserID_UserIDZprofile_datarH   rI   exp_timetoken_payloadrecipient_emailuser_idr\   ri   rj   r[   destinatarior_   asuntor=   rk   r>   r@   email_sendere2r   r   r   send_activation_emaili   s    


 
&

&6r   c                 C   sN   zt j| t dgd}|W S  t jk
r2   Y dS  t jk
rH   Y dS X dS )u   
    Verifica que un token JWT sea válido
    
    Args:
        token: Token JWT a verificar
        
    Returns:
        Datos del payload si es válido, None si no lo es
    rQ   )
algorithmsN)ru   rx   r   ExpiredSignatureErrorInvalidTokenError)ri   payloadr   r   r   verify_token   s    
r   c                    s     j ddgd fdd}|S )Nz$/Tokensign/<UserID>/<Email>/<Nombre>GETmethodsc              
      s  z`| r|r|s*t d tddidfW S t d ttjtdd }| |||d}t d	| d
  jdst d tddidfW S t d t	j
|t dd}t|tr|d}t dt| d| d
 t d t| |}|d krt d|  d tdd|  didfW S t d| d
 t d t }|j||| |d}t d t|||dW S  tk
r }	 zbt d |	 d
 t d!t|	 d
 d"d l}
t d#|
  d
 tdd$t|	 idf W Y S d }	~	X Y nX d S )%Nz%===== Error: Campos incompletos =====rF   z!Todos los campos son obligatorios  u*   ===== Iniciando generación de token =====rJ   rK   rM   z===== Token payload preparado: rl   r   z,===== Error: SECRET_KEY no configurada =====u&   Configuración del servidor incompletarg   z ===== Iniciando jwt.encode =====rQ   rR   rT   z===== Token generado (tipo: z): z,===== Guardando token en base de datos =====z===== Error: El UserID z" no existe en la tabla Users =====rU   rV   rG   z===== Token guardado con ID: u*   ===== Enviando correo de activación =====)r~   r\   r   ri   z%===== Preparando respuesta JSON =====rh   z===== Error en generar_token: rn   r   rm   ro   )r8   r   r   rq   r   rr   r   r   getru   rv   r   r/   rw   rx   rt   r   r   r   r7   r9   r:   r   )r{   rI   rH   r|   r}   ri   rj   r   rk   r@   r9   r   r   r   generar_token   sn    



 

z!Token_sign.<locals>.generar_tokenroute)r   r   r   r   r   
Token_sign   s    Fr   c                 C   s   | j ddgddd }|S )Nz	/activater   r   c               
   S   s  t jd} t jd}t jd}t jd}| r8|sDtdddS z~d}t h}| }|||| g | }|stdd	dW  5 Q R  W S |\}}	}
}|rtdd
dW  5 Q R  W S t	 |
krtdddW  5 Q R  W S zt
j| t dgddid}W nb t
jk
r4   tddd Y W  5 Q R  W S  t
jk
rd   tddd Y W  5 Q R  W S X d}|||g d}|||g |  tdd|p|dddW  5 Q R  W S Q R X W nB tk
r } z"td|  tddd W Y S d }~X Y nX d S )Nri   useridemailnameSecurity/UserPassword.htmlu-   Enlace de activación inválido o incompleto.)message
                SELECT ActivationTokenID, Token, ExpiresAt, IsUsed 
                FROM ActivationTokens 
                WHERE UserID = ? AND Token = ?
                Token no encontrado o inválido.u0   Este enlace de activación ya ha sido utilizado.u%   El enlace de activación ha expirado.rQ   
verify_expTr   optionsEl token ha expirado.   Token inválido.
                    UPDATE ActivationTokens 
                    SET IsUsed = 1 
                    WHERE ActivationTokenID = ?
                z
                    UPDATE Users 
                    SET Status = 'ACTIVO' 
                    WHERE UserID = ?
                exitorH   Usuario)r   r   u   Error durante la activación: u,   Ha ocurrido un error durante la activación.)r   argsr   r   r   cursorexecutefetchoner   utcnowru   rx   r   r   r   commitr7   r8   )ri   r   r   r   check_token_queryconnr   
token_datarj   db_token
expires_atis_useddecoded_tokenupdate_token_queryupdate_user_queryr@   r   r   r   activate_accountK  sn    

z1activate_account_routes.<locals>.activate_accountr   )r   r   r   r   r   activate_account_routesJ  s    
Rr   readc                 C   s   t  }| }d}||| f | }|s0dS d}|||f | }|sRdS |j}	|D ]~}
|
j}d}||||	f | }|r\|\}}}}|dkr|r dS |dkr|r dS |dkr|r dS |d	kr\|r\ dS q\dS )
u   
    Verifica si el usuario (user_id) tiene permiso de 'read', 'create',
    'edit' o 'delete' sobre el m¨®dulo (module_name).
    
    Retorna True/False.
    z
        SELECT r.RoleID, r.RoleName
        FROM Roles r
        INNER JOIN UserRoles ur ON r.RoleID = ur.RoleID
        WHERE ur.UserID = ?
    FzO
        SELECT ModuleID
        FROM Modules
        WHERE ModuleName = ?
    z
            SELECT CanRead, CanCreate, CanEdit, CanDelete
            FROM RoleModules
            WHERE RoleID = ? AND ModuleID = ?
        r   Tcreateeditdelete)r   r   r   fetchallr   ZModuleIDRoleID)r   module_nameactionr   r   Zsql_get_rolesrolesZsql_get_moduleZmod_rowZ	module_idrowZrole_idZsql_check_permissionZperm_rowZcan_readZ
can_createZcan_editZ
can_deleter   r   r   user_has_access  s:    	    r   c                 C   s4   | j ddgddd }| j ddgddd	 }|S )
Nz/password_confirmr   r   c               
   S   s
  t jd} t jd}t jd}t jd}| r8|sFtddddS zxd	}t b}| }|||| g | }|std
dddW  5 Q R  W S |\}}	}
}|rtddddW  5 Q R  W S t	 |
krtddddW  5 Q R  W S zBt
j| t dgddid}|s|dd}|s,|dd}W nf t
jk
rb   tdddd Y W  5 Q R  W S  t
jk
r   tdddd Y W  5 Q R  W S X tdd| |||dW  5 Q R  W S Q R X W nD tk
r } z$td|  tdddd W Y S d}~X Y nX dS )uz   
        Muestra la página para confirmar/establecer la contraseña.
        Verifica el token recibido por URL.
        ri   r   r   r   r   F1   Enlace incompleto. Faltan parámetros necesarios.token_validZerror_messager   zAccessDened.htmlr   !Este enlace ya ha sido utilizado.El enlace ha expirado.rQ   r   Tr   rH    rO   r   r   )r   ri   r   r   nombrezError al verificar el token: u8   Ha ocurrido un error durante la verificación del token.N)r   r   r   r   r   r   r   r   r   r   ru   rx   r   r   r   r7   r8   )ri   r   r   r   r   r   r   r   rj   r   r   r   r   r@   r   r   r   password_confirm  s|    
z1password_confirm_routes.<locals>.password_confirmz/set_passwordPOSTc               
   S   s\  zt  } | s$tddddfW S | d}| d}| d}| d}|r\|r\|r\|sptdd	ddfW S ||krtdd
ddfW S d}t d}| }||||g | }|stddddfW  5 Q R  W S |\}	}
}|rtddddfW  5 Q R  W S t	 |
kr<tddddfW  5 Q R  W S t
jdd}t
|d|d}td|  tdt| d t|dkrtddddfW  5 Q R  W S d}||||g d}|||	g |  tddddW  5 Q R  W S Q R X W nR tk
rV } z2td|  tddt| ddf W Y S d}~X Y nX dS )uM   
        Recibe y procesa la contraseña establecida por el usuario.
        FzNo se recibieron datos.successr   r   ri   ZuserIdpasswordZconfirmPasswordzFaltan datos requeridos.u   Las contraseñas no coinciden.z
                SELECT ActivationTokenID, ExpiresAt, IsUsed 
                FROM ActivationTokens 
                WHERE UserID = ? AND Token = ?
            u   Token no válido.r   r      )roundsrT   u   Contraseña cifrada generada: zLongitud del hash: z caracteresrg   uE   Error interno: El hash de la contraseña excede el tamaño permitido.z
                    UPDATE Users 
                    SET PasswordHash = ?, Status = 'ACTIVO', LastLogin = GETDATE()
                    WHERE UserID = ?
                r   TuN   Contraseña establecida correctamente. Serás redirigido al inicio de sesión.z/login)r   r   r   u$   Error al establecer la contraseña: N)r   get_jsonr   r   r   r   r   r   r   r   bcryptgensalthashpwrv   rx   r8   lenr   r7   r   )r_   ri   r   r   Zconfirm_passwordr   r   r   r   rj   r   r   saltZhashed_passwordr   r   r@   r   r   r   set_password9  s`    




 
   z-password_confirm_routes.<locals>.set_passwordr   )r   r   r   r   r   r   password_confirm_routes  s
    
U
Yr   c                    s:    j ddgd fdd} j ddgddd	 }tS )
Nz/auth/forgot-passwordr   r   c               
      sx  zt  } | r| ds.tddddfW S | d}t Z}| }d}|||g | }|stdddW  5 Q R  W S |\}}}d	}	||	|g | }
d
}|||g | }|rddd |D }|	 sd}nd}t
 tdd }|||dd}tj|t dd}d	}	||	|g | }
|
r^|
d }d}|||||g n0d}|||||g | }|r|d nd}|  W 5 Q R X |}d}dt d| d| d| d| 
}|||d} td | d!}t| jd" |gd#}||_| tdd$dW S  tk
rr } z<td%|  ddl}t|  tdd&dd'f W Y S d}~X Y nX dS )(u   
        Procesa la solicitud de recuperación de contraseña.
        Genera un token y envía un correo electrónico al usuario.
        r   Fu   Correo electrónico requeridor   r   z
                    SELECT UserID, Email, Status
                    FROM Users
                    WHERE Email = ?
                TuT   Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña.z
                    SELECT ActivationTokenID 
                    FROM ActivationTokens 
                    WHERE UserID = ? AND IsUsed = 0
                z
                    SELECT FirstName, MiddleName, LastName, SecondLastName
                    FROM Profiles
                    WHERE UserID = ?
                 c                 S   s   g | ]}|r|  r|qS r   )strip).0partr   r   r   
<listcomp>  s       zLpassword_recovery_routes.<locals>.request_password_reset.<locals>.<listcomp>r      )hourspassword_reset)rN   rO   rP   rt   rQ   rR   r   z
                        UPDATE ActivationTokens
                        SET Token = ?, ExpiresAt = ?, CreatedAt = GETDATE(), IsUsed = 0
                        WHERE ActivationTokenID = ?
                    z
                        INSERT INTO ActivationTokens (UserID, Token, CreatedAt, ExpiresAt, IsUsed)
                        OUTPUT INSERTED.ActivationTokenID
                        VALUES (?, ?, GETDATE(), ?, 0)
                    Nu   Restaure su contraseñazhttp://rW   rX   rY   rZ   )r[   r\   ri   r]   r^   r`   ra   ub   Se ha enviado un enlace para restablecer tu contraseña. Por favor, revisa tu correo electrónico.z(Error en solicitud de restablecimiento: uA   Error al procesar la solicitud. Por favor, inténtalo más tarde.rg   )r   r   r   r   r   r   r   r   r0   r   r   r   r   ru   rv   r   r   r   r   r
   r   r,   ry   r7   r8   r9   r:   )r_   r   r   r   check_query	user_datar   
user_emailstatusr   Zexisting_tokenZtraer_nombreZ
user_data2ZNombre_CompletoZexpiry_timer}   Z	jwt_tokenrj   r   Zinsert_token_queryr   r   r   r[   cuerpo_htmlr>   r@   r9   r   rz   r   r   request_password_reset  s    


"

z8password_recovery_routes.<locals>.request_password_resetz/reset-passwordr   c                  S   s  t jd} t jd}td|  d| d | r6|sLtd tddd	d
S zFd}t 0}| }|||| g | }|std| d |d|g |	 }td| d| d tdddd
W  5 Q R  W S |\}}}	}
td| d|	 d|
 d |
r4td| d tdddd
W  5 Q R  W S t
 }||	kr~td| d| d|	 d tdddd
W  5 Q R  W S ztj| t dgddid}td| d |d }|r|d!krtd"| d tddd#d
W W  5 Q R  W S |d$d%}d&}|||g | }|r2|\}}|s6|}nd%}td'| d(| d W n tjk
r } z4td)| d td*dd+d
 W Y W  5 Q R  W S d,}~X Y n tjk
r } z4td-| d td*dd.d
 W Y W  5 Q R  W S d,}~X Y nf tk
r^ } zFtd/| d d&}|||g | }|rF|\}}nd0\}}W 5 d,}~X Y nX td1 tdd| |||dd2W  5 Q R  W S Q R X W nd tk
r } zDtd3| d d4d,l}t|  td*dd5t| d
 W Y S d,}~X Y nX d,S )6uv   
        Muestra la página para establecer una nueva contraseña.
        Verifica que el token sea válido.
        ri   r   z!===== reset_password_page: token=z
, user_id=rl   z2===== Error: Falta token o user_id en la URL =====r   Fr   r   r   zB===== Error: Token no encontrado en la base de datos para user_id=zYSELECT ActivationTokenID, Token, ExpiresAt, IsUsed FROM ActivationTokens WHERE UserID = ?z&===== Tokens encontrados para user_id=z: r   z===== Token encontrado: ID=z	, expira=z, usado=z$===== Error: Token ya utilizado (ID=z) =====r   z ===== Error: Token expirado (ID=z). Actual: z
, Expira: r   rQ   r   Tr   z'===== Token decodificado exitosamente: rt   r   z'===== Error: Tipo de token incorrecto: u   Tipo de token inválido.rO   r   z^
                        SELECT Email, Nombre FROM Users WHERE UserID = ?
                    z ===== Usuario encontrado: email=z	, nombre=z!===== Error: Token JWT expirado: zSecurity/AccessDened.htmlr   Nu"   ===== Error: Token JWT inválido: r   z!===== Error decodificando token: )r   r   u/   ===== Token válido, mostrando formulario =====)r   ri   r   r   r   Zis_password_resetz/===== Error inesperado en reset_password_page: r   u/   Ha ocurrido un error durante la verificación: )r   r   r   r8   r   r   r   r   r   r   r   r   ru   rx   r   r   r   r7   r9   r:   r   )ri   r   r   r   r   r   Z
all_tokensrj   r   r   r   rq   r   
token_typer   Zget_user_queryZuser_rowdb_emailr   r@   r9   r   r   r   reset_password_page1  s    


((
z5password_recovery_routes.<locals>.reset_password_page)r   password_recovery_routes)r   rz   r   r   r   r   r   r     s     
 r   )r   )N).flaskr   r   r   r   r   r   r   r   r	   osr2   pytz
flask_mailr
   Z&Consultas_SQL.Security.UserPasswordSQLr   r   ru   r9   r   r   r   r   	functoolsr   r   r   r   r   Consultas_SQL.conexionr   email.mime.multipartr   email.mime.textr   secretsr   r   r   r   r   r   r   r   r   r   r   r   r   <module>   s@   $E KV
B 4