Ich habe jetzt meine Lösung fertig:
1. Es gibt eine Passwort vergessen Seite
- Der Nutzer gibt seine Email, auf die die reset URL gesendet werden soll
- Es wird geprüft ob der Nutzer mit dieser Email existiert
- Es wird geprüft ob die Email verifiziert wurde (damals als er sich angemeldet hat)
- Es wird in der DB ein neuer reset password request angelegt
- Email wird versendet
Code:
Code: Alles auswählen
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data.lower().strip()).first()
if user is None:
flash('Nutzer existiert nicht', 'error')
return redirect(url_for('forgot_pw'))
if user.email_verified is False:
flash('Diese Email ist nicht verifiziert', 'error')
return redirect(url_for('forgot_pw'))
new_reset_req = ResetPasswordRequest(token=get_random_code(30), date_added=datetime.today(), requested_by=form.email.data.lower().strip(),
user_id=user.id)
db_session.add(new_reset_req)
db_session.commit()
reset_token = generate_confirmation_token(new_reset_req.token)
reset_url = url_for('get_new_pw', token=reset_token, _external=True)
html = render_template('emails/forgot_pw_email.html', reset_url=reset_url)
subject = "Passwort vergessen bei Monteurzimmer-1A"
send_email(user.email, subject, html, None)
flash('Wir haben Ihnen eine Email gesendet mit einem Änderungslink', 'success')
return redirect(url_for('forgot_pw'))
Code: Alles auswählen
def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
return serializer.dumps(email, salt=app.config['SECURITY_PASSWORD_SALT'])
- Hier wird das Token gecheckt, falls damit etwas nicht stimmt, dann lädt die Seite nicht
- Falls die Token Lebensdauer von 15 min. überschritten ist, dann lädt die Seite nicht
- Jetzt wird der reset request von der DB geholt über das token
- Es wird geprüft ob der reset request bereits eingelöst wurde, wenn ja gibt es eine Fehlermeldung
- Ansonsten kann jetzt das neue Passwort vergeben und gespeichert werden
Code:
Code: Alles auswählen
# Forgot password set new
@app.route('/passwort-vergessen/<token>', methods=["GET","POST"])
def get_new_pw(token):
form = GetNewPWForm()
form_contact_us = ContactForm(prefix="contact-us-form")
try:
token_status = confirm_token_for_pw(token)
if token_status is False:
count_error('ForgotPWTokenStatusFalse')
flash('Dieser Verifizierungslink ist nicht mehr gültig. Lassen Sie sich einen neuen Verifizierungslink zusenden', 'error')
return redirect(url_for('forgot_pw'))
else:
print (token_status)
if form.validate_on_submit():
check_link = ResetPasswordRequest.query.filter_by(token=token_status).first()
if check_link is None:
flash('Dieser Verifizierungslink ist nicht mehr gültig. Lassen Sie sich einen neuen Verifizierungslink zusenden', 'error')
return redirect(url_for('forgot_pw'))
if check_link.date_used is not None:
flash('Dieser Verifizierungslink wurde bereits eingelöst', 'error')
return redirect(url_for('forgot_pw'))
setattr(check_link, "date_used", datetime.today())
user = User.query.filter_by(id=check_link.user_id).first()
if user is None:
flash('Der Nutzer existiert nicht', 'error')
return redirect(url_for('forgot_pw'))
setattr(user, "password", make_hash(form.password.data))
db_session.commit()
flash('Passwort wurde geändert', 'success')
return redirect(url_for('login_a_user'))
return render_template('reset_password.html', form_contact_us=form_contact_us, form=form)
except Exception as e:
print ('token verify error', e)
count_error('ForgotPWTokenError')
flash('Dieser Verifizierungslink ist nicht mehr gültig. Lassen Sie sich einen neuen Verifizierungslink zusenden', 'error')
return redirect(url_for('forgot_pw'))
Code: Alles auswählen
def confirm_token_for_pw(token, expiration=1800): # old expiration was 3600 seconds = 1 hour
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
try:
confirm_token = serializer.loads(
token,
salt=app.config['SECURITY_PASSWORD_SALT'],
max_age=expiration
)
except:
return False
#print (confirm_token)
return confirm_token
Code: Alles auswählen
class ResetPasswordRequest(Base):
__tablename__ = 'resetpasswordrequests'
id = Column(Integer, primary_key=True)
token = Column(Text, nullable=False, unique=True)
date_added = Column(DateTime(timezone=True), nullable=False)
date_used = Column(DateTime(timezone=True), nullable=True)
requested_by = Column(Text, nullable=False)
# Foreign key
user_id = Column(Integer, ForeignKey('users.id'))
ResetPasswordRequest_addresses_User = relationship("User", back_populates="User_addresses_ResetPasswordRequest")
Das muss wohl so sein, aber es ist irgendwie kein gutes Gefühl. Ich verstehe, dass man den reset link nicht erraten kann, dieser ist nirgendswo verlinkt, aber jemand könnte ihn aus der Email klauen?
Ich nutze bei allen Mails:
Code: Alles auswählen
MAIL_USE_SSL = True
Ich habe auch gelesen, dass man einen token hash speicher kann, aber ich verstehe nicht wozu.
Der Link in der Email SOLL ja für 'anonyme' User funktionieren und das Passwort ändern können.
Wenn sich jemand die Zeit nimmt, um sich das anzugucken, wäre ich sehr dankbar.