Edoruin commited on
Commit
411b9f2
·
1 Parent(s): 2c4a6cc

password recuperation scripts

Browse files
app/main.py CHANGED
@@ -62,9 +62,9 @@ class UserManager:
62
  with open(self.filename, "w") as f:
63
  json.dump(self.users, f, indent=4)
64
 
65
- def add_user(self, username, password, status="PENDING"):
66
- """Registra un nuevo usuario con contraseña hasheada y estado pendiente."""
67
- if self.get_by_username(username):
68
  return False
69
 
70
  user_id = str(uuid.uuid4())
@@ -72,8 +72,11 @@ class UserManager:
72
  self.users.append({
73
  "id": user_id,
74
  "username": username,
 
75
  "password": hashed_pw,
76
- "status": status
 
 
77
  })
78
  self.save()
79
  return True
@@ -100,6 +103,13 @@ class UserManager:
100
  return user
101
  return None
102
 
 
 
 
 
 
 
 
103
  def get_by_id(self, user_id):
104
  """Busca un usuario por su ID."""
105
  for user in self.users:
@@ -107,6 +117,40 @@ class UserManager:
107
  return user
108
  return None
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  user_mgr = UserManager()
111
 
112
  @login_manager.user_loader
@@ -335,6 +379,7 @@ def register():
335
 
336
  if request.method == 'POST':
337
  username = request.form.get('username')
 
338
  password = request.form.get('password')
339
  confirm_password = request.form.get('confirm_password')
340
 
@@ -342,7 +387,7 @@ def register():
342
  flash("Las contraseñas no coinciden", "red")
343
  return render_template('register.html')
344
 
345
- if user_mgr.add_user(username, password, status="PENDING"):
346
  # Notificar a Telegram sobre el nuevo registro
347
  if bot and TG_CHAT_ID:
348
  try:
@@ -370,6 +415,56 @@ def logout():
370
  flash("Has cerrado sesión", "blue")
371
  return redirect(url_for('index'))
372
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  @app.route('/prestamos')
374
  def prestamos():
375
  loans = loan_mgr.get_all()
 
62
  with open(self.filename, "w") as f:
63
  json.dump(self.users, f, indent=4)
64
 
65
+ def add_user(self, username, password, email, status="PENDING"):
66
+ """Registra un nuevo usuario con email, contraseña hasheada y estado pendiente."""
67
+ if self.get_by_username(username) or self.get_by_email(email):
68
  return False
69
 
70
  user_id = str(uuid.uuid4())
 
72
  self.users.append({
73
  "id": user_id,
74
  "username": username,
75
+ "email": email,
76
  "password": hashed_pw,
77
+ "status": status,
78
+ "reset_token": None,
79
+ "reset_expiry": None
80
  })
81
  self.save()
82
  return True
 
103
  return user
104
  return None
105
 
106
+ def get_by_email(self, email):
107
+ """Busca un usuario por su email."""
108
+ for user in self.users:
109
+ if user.get("email") == email:
110
+ return user
111
+ return None
112
+
113
  def get_by_id(self, user_id):
114
  """Busca un usuario por su ID."""
115
  for user in self.users:
 
117
  return user
118
  return None
119
 
120
+ def generate_reset_token(self, email):
121
+ """Genera un token de recuperación de contraseña."""
122
+ user = self.get_by_email(email)
123
+ if user:
124
+ token = str(uuid.uuid4())
125
+ expiry = (datetime.datetime.now() + datetime.timedelta(hours=1)).isoformat()
126
+ user["reset_token"] = token
127
+ user["reset_expiry"] = expiry
128
+ self.save()
129
+ return token
130
+ return None
131
+
132
+ def verify_reset_token(self, token):
133
+ """Verifica un token de recuperación y retorna el usuario si es válido."""
134
+ for user in self.users:
135
+ if user.get("reset_token") == token:
136
+ expiry_str = user.get("reset_expiry")
137
+ if expiry_str:
138
+ expiry = datetime.datetime.fromisoformat(expiry_str)
139
+ if datetime.datetime.now() < expiry:
140
+ return user
141
+ return None
142
+
143
+ def update_password(self, user_id, new_password):
144
+ """Actualiza la contraseña de un usuario y limpia el token."""
145
+ user = self.get_by_id(user_id)
146
+ if user:
147
+ user["password"] = generate_password_hash(new_password)
148
+ user["reset_token"] = None
149
+ user["reset_expiry"] = None
150
+ self.save()
151
+ return True
152
+ return False
153
+
154
  user_mgr = UserManager()
155
 
156
  @login_manager.user_loader
 
379
 
380
  if request.method == 'POST':
381
  username = request.form.get('username')
382
+ email = request.form.get('email')
383
  password = request.form.get('password')
384
  confirm_password = request.form.get('confirm_password')
385
 
 
387
  flash("Las contraseñas no coinciden", "red")
388
  return render_template('register.html')
389
 
390
+ if user_mgr.add_user(username, password, email, status="PENDING"):
391
  # Notificar a Telegram sobre el nuevo registro
392
  if bot and TG_CHAT_ID:
393
  try:
 
415
  flash("Has cerrado sesión", "blue")
416
  return redirect(url_for('index'))
417
 
418
+ @app.route('/forgot-password', methods=['GET', 'POST'])
419
+ def forgot_password():
420
+ if current_user.is_authenticated:
421
+ return redirect(url_for('repos'))
422
+
423
+ if request.method == 'POST':
424
+ email = request.form.get('email')
425
+ token = user_mgr.generate_reset_token(email)
426
+
427
+ if token:
428
+ reset_url = url_for('reset_password', token=token, _external=True)
429
+ # Simulación de envío de correo
430
+ print(f"\n[EMAIL SIMULATION] Para: {email}")
431
+ print(f"[EMAIL SIMULATION] Enlace de recuperación: {reset_url}\n")
432
+
433
+ flash("Si el correo está registrado, recibirás un enlace de recuperación.", "blue")
434
+ else:
435
+ # Por seguridad, no revelamos si el email existe
436
+ flash("Si el correo está registrado, recibirás un enlace de recuperación.", "blue")
437
+
438
+ return redirect(url_for('login'))
439
+
440
+ return render_template('forgot_password.html', title="Recuperar Contraseña")
441
+
442
+ @app.route('/reset-password/<token>', methods=['GET', 'POST'])
443
+ def reset_password(token):
444
+ if current_user.is_authenticated:
445
+ return redirect(url_for('repos'))
446
+
447
+ user = user_mgr.verify_reset_token(token)
448
+ if not user:
449
+ flash("El enlace de recuperación es inválido o ha expirado.", "red")
450
+ return redirect(url_for('forgot_password'))
451
+
452
+ if request.method == 'POST':
453
+ password = request.form.get('password')
454
+ confirm_password = request.form.get('confirm_password')
455
+
456
+ if password != confirm_password:
457
+ flash("Las contraseñas no coinciden", "red")
458
+ return render_template('reset_password.html', title="Nueva Contraseña")
459
+
460
+ if user_mgr.update_password(user['id'], password):
461
+ flash("Contraseña actualizada correctamente. Ya puedes iniciar sesión.", "green")
462
+ return redirect(url_for('login'))
463
+ else:
464
+ flash("Error al actualizar la contraseña.", "red")
465
+
466
+ return render_template('reset_password.html', title="Nueva Contraseña")
467
+
468
  @app.route('/prestamos')
469
  def prestamos():
470
  loans = loan_mgr.get_all()
app/templates/forgot_password.html ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="hero section" style="text-align: center; margin-bottom: 3rem;">
5
+ <h1>RECUPERAR CONTRASEÑA</h1>
6
+ <p class="text-dim">Ingresa tu correo electrónico para recibir un enlace de recuperación.</p>
7
+ </div>
8
+
9
+ <div class="glass" style="max-width: 400px; margin: 0 auto; padding: 2.5rem; border-radius: 24px;">
10
+ <form method="POST">
11
+ <div class="form-group">
12
+ <label for="email">Correo Electrónico</label>
13
+ <input type="email" id="email" name="email" placeholder="tu@email.com" required autofocus>
14
+ </div>
15
+
16
+ <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;">
17
+ ENVIAR ENLACE <i class="fas fa-paper-plane" style="margin-left: 0.5rem;"></i>
18
+ </button>
19
+ </form>
20
+
21
+ <div style="margin-top: 2rem; text-align: center; font-size: 0.85rem;" class="text-dim">
22
+ <p><a href="/login" style="color: #38bdf8; text-decoration: none;">Volver al inicio de sesión</a></p>
23
+ </div>
24
+ </div>
25
+ {% endblock %}
app/templates/login.html CHANGED
@@ -19,6 +19,10 @@
19
  <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;">
20
  ENTRAR <i class="fas fa-sign-in-alt" style="margin-left: 0.5rem;"></i>
21
  </button>
 
 
 
 
22
  </form>
23
 
24
  <div style="margin-top: 2rem; text-align: center; font-size: 0.85rem;" class="text-dim">
 
19
  <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;">
20
  ENTRAR <i class="fas fa-sign-in-alt" style="margin-left: 0.5rem;"></i>
21
  </button>
22
+ <div style="margin-top: 1rem; text-align: center;">
23
+ <a href="/forgot-password" style="color: #38bdf8; text-decoration: none; font-size: 0.85rem;">¿Olvidaste tu
24
+ contraseña?</a>
25
+ </div>
26
  </form>
27
 
28
  <div style="margin-top: 2rem; text-align: center; font-size: 0.85rem;" class="text-dim">
app/templates/register.html CHANGED
@@ -16,6 +16,12 @@
16
  <input type="text" id="username" name="username" placeholder="Elige un usuario" required autofocus>
17
  </div>
18
 
 
 
 
 
 
 
19
  <!-- Campo: Contraseña (Mínimo 8 caracteres por seguridad) -->
20
  <div class="form-group">
21
  <label for="password">Contraseña</label>
 
16
  <input type="text" id="username" name="username" placeholder="Elige un usuario" required autofocus>
17
  </div>
18
 
19
+ <!-- Campo: Correo Electrónico -->
20
+ <div class="form-group">
21
+ <label for="email">Correo Electrónico</label>
22
+ <input type="email" id="email" name="email" placeholder="tu@email.com" required>
23
+ </div>
24
+
25
  <!-- Campo: Contraseña (Mínimo 8 caracteres por seguridad) -->
26
  <div class="form-group">
27
  <label for="password">Contraseña</label>
app/templates/reset_password.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <div class="hero section" style="text-align: center; margin-bottom: 3rem;">
5
+ <h1>NUEVA CONTRASEÑA</h1>
6
+ <p class="text-dim">Ingresa tu nueva contraseña para acceder a Maker Space.</p>
7
+ </div>
8
+
9
+ <div class="glass" style="max-width: 400px; margin: 0 auto; padding: 2.5rem; border-radius: 24px;">
10
+ <form method="POST">
11
+ <div class="form-group">
12
+ <label for="password">Nueva Contraseña</label>
13
+ <input type="password" id="password" name="password" placeholder="Mínimo 8 caracteres" required
14
+ minlength="8" autofocus>
15
+ </div>
16
+
17
+ <div class="form-group">
18
+ <label for="confirm_password">Confirmar Contraseña</label>
19
+ <input type="password" id="confirm_password" name="confirm_password" placeholder="Repite tu contraseña"
20
+ required minlength="8">
21
+ </div>
22
+
23
+ <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 1rem;">
24
+ REABLECER CONTRASEÑA <i class="fas fa-key" style="margin-left: 0.5rem;"></i>
25
+ </button>
26
+ </form>
27
+ </div>
28
+ {% endblock %}