a
    0i|                     @   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   5/var/www/html/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 z^t	| j
| j8}| jr|  || j| j || W d	   n1 s0    Y  W d
S  ty } z(td|  tt  W Y d	}~dS d	}~0 0 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htmlNTError al enviar correo: F)
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u 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y } zt	d|  zBt| jd | jd  | jd! | jd | jd" }|j|||d#}W nB ty } z(t	d$|  t	t   W Y d}~n
d}~0 0 W Y d}~n
d}~0 0 |s,tdd%id&fW W S t|||d'W W S  ty } zLt	d(| d) t	d*t  d) tdd+t| id&fW  Y d}~W S d}~0 0 W nx ty  } z^t	d,| d) t	d-t
| d) t	d*t  d) tdd.t| id&fW  Y d}~S d}~0 0 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   sJ   zt j| t dgd}|W S  t jy0   Y dS  t jyD   Y dS 0 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 u 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y }	 zft d |	 d
 t d!t|	 d
 d"d l}
t d#|
  d
 tdd$t|	 idfW  Y d }	~	S d }	~	0 0 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   sl    




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 zd}t p}| }|||| g | }|stdd	dW  d    W S |\}}	}
}|rtdd
dW  d    W S t	 |
krtdddW  d    W S zt
j| t dgddid}W nb t
jy:   tddd Y W  d    W S  t
jyj   tddd Y W  d    W S 0 d}|||g d}|||g |  tdd|p|dddW  d    W S 1 s0    Y  W n@ ty } z&td|  tdddW  Y d }~S d }~0 0 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  s2    	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 zd	}t j}| }|||| g | }|std
dddW  d   W S |\}}	}
}|rtddddW  d   W S t	 |
krtddddW  d   W S zBt
j| t dgddid}|s |dd}|s2|dd}W nf t
jyh   tdddd Y W  d   W S  t
jy   tdddd Y W  d   W S 0 tdd| |||dW  d   W S 1 s0    Y  W nB ty } z(td|  tddddW  Y d}~S d}~0 0 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   N!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.)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   sp  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 n}| }||||g | }|stddddfW  d   W S |\}	}
}|rtddddfW  d   W S t	 |
krBtddddfW  d   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  d   W S d}||||g d}|||	g |  tddddW  d   W S 1 s0    Y  W nP tyj } z6td|  tddt| ddfW  Y d}~S d}~0 0 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.Nr   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: )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               
      s  z,t  } | r| ds.tddddfW S | d}t f}| }d}|||g | }|stdddW  d	   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 d	   n1 s0    Y  |}d}dt d| d| d| d| 
}|||d} td | d!}t| jd" |gd#}||_| tdd$dW S  ty } z@td%|  dd	l}t|  tdd&dd'fW  Y d	}~S d	}~0 0 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.Nz
                    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>  r   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)
                    u   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 z^d}t <}| }|||| g | }|std| d |d|g |	 }td| d| d tdddd
W  d   W S |\}}}	}
td| d|	 d|
 d |
r8td| d tdddd
W  d   W S t
 }||	krtd| d| d|	 d tdddd
W  d   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  d   W S |d%d&}d'}|||g | }|r:|\}}|s>|}nd&}td(| d)| d W n tjy } z:td*| d td+dd,d
W  Y d}~W  d   W S d}~0  tjy } z:td-| d td+dd.d
W  Y d}~W  d   W S d}~0  tyh } zNtd/| d d'}|||g | }|rL|\}}nd0\}}W Y d}~n
d}~0 0 td1 tdd| |||dd2W  d   W S 1 s0    Y  W nb ty } zHtd3| d d4dl}t|  td+dd5t| d
W  Y d}~S d}~0 0 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   Nz===== 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   u"   ===== 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