lunes, 25 de agosto de 2025

WEBPROPv2 (V). Validación de usuarios sin nginx (III). Log in. Segundo factor de autenticación

 0.Introdución

En el módulo routes01mnu.py, en el endpoint "/softprop/validate_login" que estaba definido en la función  post_login estaba este código que hace referencia al módulo xmtotp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        # 2. Comprobamos si tiene TOTP
	has_totp, totp, image_path=xmtotp.get_totp(login.usuari)
	
	# 3. Si no ha introducido el código TOTP mostramos la pantalla 
	#    para que lo ingrese
	no_digits=login.totp_digits is None
	if no_digits:
		print("Mostrando el input para introducir el TOTP")
		return xmother.get_totp_html(fh, has_totp, image_path)

	# 4. No ha introducido el TOTP correcto
	totp_correcto= xmtotp.validate_totp(totp, login.totp_digits, login.usuari)
	if not totp_correcto:
		print("Mostrando otra vez el input para introducir el TOTP")
		return xmother.get_totp_html(fh, has_totp, image_path, True)	


Veamos pues el módulo xmtotp.py

 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
import os
import pyotp
import qrcode

from basicutils import xmdb, xmother
from models.xmusermodels import UserStore, UserStoreHst

def get_totp(username:str, service:str='Softprop@Tav'):
	"""
	Generates a new TOTP secret and QR code for the given username and service.
	
	Args:
		username (str): The username for which to generate the TOTP secret.
		service (str): The service name for which the TOTP is being generated.
		
	Returns:
		tuple: A tuple containing:
			- has_totp (bool): True if the user already has a TOTP secret, False otherwise.
			- totp (pyotp.TOTP): The TOTP object.
			- image_path (str): The path to the saved QR code image.
	"""
	# Generate a new TOTP secret
	secret=pyotp.random_base32()
	has_totp=False
	has_2fa=False
	image_path_url=None
	myuser=None
	
	# Check if the user already has a TOTP secret
	with xmdb.session_local() as db:
		try:
			myuser=db.query(UserStore).filter(UserStore.ldap_user==username).first()
			#if myuser and myuser.totp_activated: 
			if myuser and myuser.totp_secret is not None and len(myuser.totp_secret.strip())>0: 
				secret=myuser.totp_secret
				has_2fa=True
				if myuser.totp_activated: 
					has_totp=True
		except Exception as e:
			print(f"An error occurred while querying the user: {e}")
			return False, None, None

		
		totp = pyotp.TOTP(secret)
		
		
		if not has_2fa:
			# Update the user with the new TOTP secret
			myuser.totp_secret=secret
			myuser.totp_user=username
			myuser.totp_service=service
			myuser.totp_activated=False
			
			xmdb.merge_to_main_and_histo(db, myuser, UserStore, UserStoreHst)
			db.commit() 

		# Si no se ha autenticado antes
		if not has_totp:	
			# Generate the provisioning URI and save image
			uri = totp.provisioning_uri(name=username, issuer_name=service)
			# Generate the QR code image	
			image_path_file=xmother.get_project_path()+os.sep+'static'+os.sep+'qrcodes'+os.sep+username+'_qrcode.png'
			image_path_url='/static'+os.sep+'qrcodes'+os.sep+username+'_qrcode.png'
			print (image_path_file, image_path_url)
			qrcode.make(uri).save(image_path_file)
		# Check if the image was saved successfully	
	return has_totp, totp, image_path_url

def validate_totp(totp, code:str, username:str)->bool:
	"""
	Validates the provided TOTP code against the generated TOTP object.
	
	Args:
		totp (pyotp.TOTP): The TOTP object.
		code (str): The TOTP code to validate.
		
	Returns:
		bool: True if the code is valid, False otherwise.
	"""
	si_verifica=totp.verify(code) or username=='eduard' # For testing purposes, allow 'eduard' to bypass TOTP verification
	if si_verifica:
		with xmdb.session_local() as db:
			try:
				myuser=db.query(UserStore).filter(UserStore.ldap_user==username).first()
				if myuser and myuser.totp_activated==False:
					myuser.totp_activated=True
					xmdb.merge_to_main_and_histo(db, myuser, UserStore, UserStoreHst)
					db.commit() 	
			except Exception as e:
				print(f"An error occurred while querying the user: {e}")
				return False
	return si_verifica








No hay comentarios :

Publicar un comentario