views.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.auth.views
  4. ~~~~~~~~~~~~~~~~~~~~
  5. This view provides user authentication, registration and a view for
  6. resetting the password of a user if he has lost his password
  7. :copyright: (c) 2014 by the FlaskBB Team.
  8. :license: BSD, see LICENSE for more details.
  9. """
  10. from datetime import datetime
  11. from flask import Blueprint, flash, redirect, url_for, request, g
  12. from flask_login import (current_user, login_user, login_required,
  13. logout_user, confirm_login, login_fresh)
  14. from flask_babelplus import gettext as _
  15. from flaskbb.extensions import limiter
  16. from flaskbb.utils.helpers import (render_template, redirect_or_next,
  17. format_timedelta)
  18. from flaskbb.email import send_reset_token, send_activation_token
  19. from flaskbb.exceptions import AuthenticationError
  20. from flaskbb.auth.forms import (LoginForm, ReauthForm, ForgotPasswordForm,
  21. ResetPasswordForm, RegisterForm,
  22. AccountActivationForm, RequestActivationForm)
  23. from flaskbb.user.models import User
  24. from flaskbb.fixtures.settings import available_languages
  25. from flaskbb.utils.settings import flaskbb_config
  26. from flaskbb.utils.tokens import get_token_status
  27. auth = Blueprint("auth", __name__)
  28. @auth.before_request
  29. def check_rate_limiting():
  30. """Check the the rate limits for each request for this blueprint."""
  31. if not flaskbb_config["AUTH_RATELIMIT_ENABLED"]:
  32. return None
  33. return limiter.check()
  34. @auth.errorhandler(429)
  35. def login_rate_limit_error(error):
  36. """Register a custom error handler for a 'Too Many Requests'
  37. (HTTP CODE 429) error."""
  38. return render_template("errors/too_many_logins.html",
  39. timeout=error.description)
  40. def login_rate_limit():
  41. """Dynamically load the rate limiting config from the database."""
  42. # [count] [per|/] [n (optional)] [second|minute|hour|day|month|year]
  43. return "{count}/{timeout}minutes".format(
  44. count=flaskbb_config["AUTH_REQUESTS"],
  45. timeout=flaskbb_config["AUTH_TIMEOUT"]
  46. )
  47. def login_rate_limit_message():
  48. """Display the amount of time left until the user can access the requested
  49. resource again."""
  50. current_limit = getattr(g, 'view_rate_limit', None)
  51. if current_limit is not None:
  52. window_stats = limiter.limiter.get_window_stats(*current_limit)
  53. reset_time = datetime.utcfromtimestamp(window_stats[0])
  54. timeout = reset_time - datetime.utcnow()
  55. return "{timeout}".format(timeout=format_timedelta(timeout))
  56. # Activate rate limiting on the whole blueprint
  57. limiter.limit(login_rate_limit, error_message=login_rate_limit_message)(auth)
  58. @auth.route("/login", methods=["GET", "POST"])
  59. def login():
  60. """Logs the user in."""
  61. current_limit = getattr(g, 'view_rate_limit', None)
  62. login_recaptcha = False
  63. if current_limit is not None:
  64. window_stats = limiter.limiter.get_window_stats(*current_limit)
  65. stats_diff = flaskbb_config["AUTH_REQUESTS"] - window_stats[1]
  66. login_recaptcha = stats_diff >= flaskbb_config["LOGIN_RECAPTCHA"]
  67. if current_user is not None and current_user.is_authenticated:
  68. return redirect_or_next(url_for("forum.index"))
  69. form = LoginForm(request.form)
  70. if form.validate_on_submit():
  71. try:
  72. user = User.authenticate(form.login.data, form.password.data)
  73. login_user(user, remember=form.remember_me.data)
  74. return redirect_or_next(url_for("forum.index"))
  75. except AuthenticationError:
  76. flash(_("Wrong Username or Password."), "danger")
  77. return render_template("auth/login.html", form=form,
  78. login_recaptcha=login_recaptcha)
  79. @auth.route("/reauth", methods=["GET", "POST"])
  80. @limiter.exempt
  81. @login_required
  82. def reauth():
  83. """Reauthenticates a user."""
  84. if not login_fresh():
  85. form = ReauthForm(request.form)
  86. if form.validate_on_submit():
  87. if current_user.check_password(form.password.data):
  88. confirm_login()
  89. flash(_("Reauthenticated."), "success")
  90. return redirect_or_next(current_user.url)
  91. flash(_("Wrong password."), "danger")
  92. return render_template("auth/reauth.html", form=form)
  93. return redirect(request.args.get("next") or current_user.url)
  94. @auth.route("/logout")
  95. @limiter.exempt
  96. @login_required
  97. def logout():
  98. """Logs the user out."""
  99. logout_user()
  100. flash(_("Logged out"), "success")
  101. return redirect(url_for("forum.index"))
  102. @auth.route("/register", methods=["GET", "POST"])
  103. def register():
  104. """Register a new user."""
  105. if current_user is not None and current_user.is_authenticated:
  106. return redirect_or_next(url_for("forum.index"))
  107. if not flaskbb_config["REGISTRATION_ENABLED"]:
  108. flash(_("The registration has been disabled."), "info")
  109. return redirect_or_next(url_for("forum.index"))
  110. form = RegisterForm(request.form)
  111. form.language.choices = available_languages()
  112. form.language.default = flaskbb_config['DEFAULT_LANGUAGE']
  113. form.process(request.form) # needed because a default is overriden
  114. if form.validate_on_submit():
  115. user = form.save()
  116. if flaskbb_config["ACTIVATE_ACCOUNT"]:
  117. send_activation_token(user)
  118. flash(_("An account activation email has been sent to %(email)s",
  119. email=user.email), "success")
  120. else:
  121. login_user(user)
  122. flash(_("Thanks for registering."), "success")
  123. return redirect_or_next(current_user.url)
  124. return render_template("auth/register.html", form=form)
  125. @auth.route('/reset-password', methods=["GET", "POST"])
  126. def forgot_password():
  127. """Sends a reset password token to the user."""
  128. if not current_user.is_anonymous:
  129. return redirect(url_for("forum.index"))
  130. form = ForgotPasswordForm()
  131. if form.validate_on_submit():
  132. user = User.query.filter_by(email=form.email.data).first()
  133. if user:
  134. send_reset_token(user)
  135. flash(_("E-Mail sent! Please check your inbox."), "info")
  136. return redirect(url_for("auth.forgot_password"))
  137. else:
  138. flash(_("You have entered a Username or E-Mail Address that is "
  139. "not linked with your account."), "danger")
  140. return render_template("auth/forgot_password.html", form=form)
  141. @auth.route("/reset-password/<token>", methods=["GET", "POST"])
  142. def reset_password(token):
  143. """Handles the reset password process."""
  144. if not current_user.is_anonymous:
  145. return redirect(url_for("forum.index"))
  146. form = ResetPasswordForm()
  147. if form.validate_on_submit():
  148. expired, invalid, user = get_token_status(form.token.data,
  149. "reset_password")
  150. if invalid:
  151. flash(_("Your Password Token is invalid."), "danger")
  152. return redirect(url_for("auth.forgot_password"))
  153. if expired:
  154. flash(_("Your Password Token is expired."), "danger")
  155. return redirect(url_for("auth.forgot_password"))
  156. if user:
  157. user.password = form.password.data
  158. user.save()
  159. flash(_("Your Password has been updated."), "success")
  160. return redirect(url_for("auth.login"))
  161. form.token.data = token
  162. return render_template("auth/reset_password.html", form=form)
  163. @auth.route("/activate", methods=["GET", "POST"])
  164. def request_activation_token(token=None):
  165. """Requests a new account activation token."""
  166. if current_user.is_active or not flaskbb_config["ACTIVATE_ACCOUNT"]:
  167. flash(_("This account is already activated."), "info")
  168. return redirect(url_for('forum.index'))
  169. form = RequestActivationForm()
  170. if form.validate_on_submit():
  171. user = User.query.filter_by(email=form.email.data).first()
  172. send_activation_token(user)
  173. flash(_("A new account activation token has been sent to "
  174. "your email address."), "success")
  175. return redirect(url_for("auth.activate_account"))
  176. return render_template("auth/request_account_activation.html", form=form)
  177. @auth.route("/activate/<token>", methods=["GET", "POST"])
  178. def activate_account(token=None):
  179. """Handles the account activation process."""
  180. if current_user.is_active or not flaskbb_config["ACTIVATE_ACCOUNT"]:
  181. flash(_("This account is already activated."), "info")
  182. return redirect(url_for('forum.index'))
  183. form = None
  184. if token is not None:
  185. expired, invalid, user = get_token_status(token, "activate_account")
  186. else:
  187. form = AccountActivationForm()
  188. if form.validate_on_submit():
  189. expired, invalid, user = get_token_status(form.token.data,
  190. "activate_account")
  191. if invalid:
  192. flash(_("Your account activation token is invalid."), "danger")
  193. return redirect(url_for("auth.request_email_confirmation"))
  194. if expired:
  195. flash(_("Your account activation token is expired."), "danger")
  196. return redirect(url_for("auth.request_activation_token"))
  197. if user:
  198. user.activated = datetime.utcnow()
  199. user.save()
  200. if current_user != user:
  201. logout_user()
  202. login_user(user)
  203. flash(_("Your Account has been activated.", "success"))
  204. return redirect(url_for("forum.index"))
  205. return render_template("auth/account_activation.html", form=form)