FlaskBB 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. celery_worker.py +18 -0
  2. flaskbb/__init__.py +19 -0
  3. flaskbb/app.py +551 -0
  4. flaskbb/auth/__init__.py +7 -0
  5. flaskbb/auth/forms.py +170 -0
  6. flaskbb/auth/plugins.py +119 -0
  7. flaskbb/auth/services/__init__.py +56 -0
  8. flaskbb/auth/services/activation.py +60 -0
  9. flaskbb/auth/services/authentication.py +188 -0
  10. flaskbb/auth/services/factories.py +52 -0
  11. flaskbb/auth/services/password.py +67 -0
  12. flaskbb/auth/services/reauthentication.py +87 -0
  13. flaskbb/auth/services/registration.py +261 -0
  14. flaskbb/auth/views.py +501 -0
  15. flaskbb/cli/__init__.py +24 -0
  16. flaskbb/cli/db.py +40 -0
  17. flaskbb/cli/main.py +705 -0
  18. flaskbb/cli/plugins.py +214 -0
  19. flaskbb/cli/themes.py +109 -0
  20. flaskbb/cli/translations.py +105 -0
  21. flaskbb/cli/users.py +113 -0
  22. flaskbb/cli/utils.py +203 -0
  23. flaskbb/configs/__init__.py +0 -0
  24. flaskbb/configs/config.cfg.template +281 -0
  25. flaskbb/configs/default.py +291 -0
  26. flaskbb/configs/testing.py +60 -0
  27. flaskbb/core/__init__.py +0 -0
  28. flaskbb/core/auth/__init__.py +7 -0
  29. flaskbb/core/auth/activation.py +60 -0
  30. flaskbb/core/auth/authentication.py +301 -0
  31. flaskbb/core/auth/password.py +54 -0
  32. flaskbb/core/auth/registration.py +112 -0
  33. flaskbb/core/changesets.py +96 -0
  34. flaskbb/core/exceptions.py +85 -0
  35. flaskbb/core/tokens.py +142 -0
  36. flaskbb/core/user/__init__.py +0 -0
  37. flaskbb/core/user/update.py +93 -0
  38. flaskbb/deprecation.py +106 -0
  39. flaskbb/display/__init__.py +0 -0
  40. flaskbb/display/navigation.py +109 -0
  41. flaskbb/email.py +89 -0
  42. flaskbb/exceptions.py +28 -0
  43. flaskbb/extensions.py +83 -0
  44. flaskbb/fixtures/__init__.py +0 -0
  45. flaskbb/fixtures/groups.py +137 -0
  46. flaskbb/fixtures/settings.py +325 -0
  47. flaskbb/forum/__init__.py +3 -0
  48. flaskbb/forum/forms.py +212 -0
  49. flaskbb/forum/locals.py +59 -0
  50. flaskbb/forum/models.py +1664 -0
  51. flaskbb/forum/utils.py +39 -0
  52. flaskbb/forum/views.py +1327 -0
  53. flaskbb/management/__init__.py +20 -0
  54. flaskbb/management/forms.py +516 -0
  55. flaskbb/management/models.py +150 -0
  56. flaskbb/management/plugins.py +46 -0
  57. flaskbb/management/views.py +1480 -0
  58. flaskbb/markup.py +148 -0
  59. flaskbb/migrations/201501082314_8ad96e49dc6_init.py +321 -0
  60. flaskbb/migrations/201503222157_514ca0a3282c_private_messages.py +99 -0
  61. flaskbb/migrations/201504082225_127be3fb000_added_m2m_forumgroups_table.py +37 -0
  62. flaskbb/migrations/201606061345_221d918aa9f0_add_user_authentication_infos.py +35 -0
  63. flaskbb/migrations/201606210939_d9530a529b3f_add_timezone_awareness_for_datetime.py +244 -0
  64. flaskbb/migrations/201611190919_d87cea4e995d_remove_timezone_info_from_birthday_field.py +38 -0
  65. flaskbb/migrations/201705041144_933bd7d807c4_add_more_non_nullables.py +192 -0
  66. flaskbb/migrations/201706300917_881dd22cab94_add_date_modified_to_conversations.py +50 -0
  67. flaskbb/migrations/201709041519_d0ffadc3ea48_add_hidden_columns.py +127 -0
  68. flaskbb/migrations/201711180953_7c3fcf8a3335_add_plugin_tables.py +66 -0
  69. flaskbb/migrations/201802021027_af3f5579c84d_add_cascades.py +483 -0
  70. flaskbb/migrations/201802282131_232e68a03aa2_change_emoji_shortcodes_to_characters.py +949 -0
  71. flaskbb/migrations/201803012138_5945d8081a95_remove_conversations.py +55 -0
  72. flaskbb/migrations/__init__.py +0 -0
  73. flaskbb/migrations/script.py.mako +24 -0
  74. flaskbb/plugins/__init__.py +10 -0
  75. flaskbb/plugins/manager.py +243 -0
  76. flaskbb/plugins/models.py +139 -0
  77. flaskbb/plugins/spec.py +1141 -0
  78. flaskbb/plugins/utils.py +86 -0
  79. flaskbb/static/app.css +19 -0
  80. flaskbb/static/app.css.map +1 -0
  81. flaskbb/static/app.js +2 -0
  82. flaskbb/static/app.js.map +1 -0
  83. flaskbb/static/avatar.svg +4 -0
  84. flaskbb/static/avatar100x100.png +0 -0
  85. flaskbb/static/avatar150x150.png +0 -0
  86. flaskbb/static/avatar400x400.png +0 -0
  87. flaskbb/static/avatar80x80.png +0 -0
  88. flaskbb/static/fa-regular-400.eot +0 -0
  89. flaskbb/static/fa-regular-400.svg +801 -0
  90. flaskbb/static/fa-regular-400.ttf +0 -0
  91. flaskbb/static/fa-regular-400.woff +0 -0
  92. flaskbb/static/fa-regular-400.woff2 +0 -0
  93. flaskbb/static/fa-solid-900.eot +0 -0
  94. flaskbb/static/fa-solid-900.svg +5034 -0
  95. flaskbb/static/fa-solid-900.ttf +0 -0
  96. flaskbb/static/fa-solid-900.woff +0 -0
  97. flaskbb/static/fa-solid-900.woff2 +0 -0
  98. flaskbb/static/favicon.ico +0 -0
  99. flaskbb/static/kuyN6Bh.jpg +0 -0
  100. flaskbb/static/vendors.js +3 -0
  101. flaskbb/static/vendors.js.LICENSE.txt +7 -0
  102. flaskbb/static/vendors.js.map +1 -0
  103. flaskbb/templates/_macros/form.html +218 -0
  104. flaskbb/templates/_macros/navigation.html +83 -0
  105. flaskbb/templates/_macros/pagination.html +109 -0
  106. flaskbb/templates/_macros/utils.html +11 -0
  107. flaskbb/templates/_partials/confirm_dialog.html +17 -0
  108. flaskbb/templates/_partials/editor_help.html +60 -0
  109. flaskbb/templates/_partials/flashed_messages.html +19 -0
  110. flaskbb/templates/auth/account_activation.html +22 -0
  111. flaskbb/templates/auth/forgot_password.html +27 -0
  112. flaskbb/templates/auth/login.html +35 -0
  113. flaskbb/templates/auth/reauth.html +22 -0
  114. flaskbb/templates/auth/register.html +34 -0
  115. flaskbb/templates/auth/request_account_activation.html +23 -0
  116. flaskbb/templates/auth/reset_password.html +25 -0
  117. flaskbb/templates/email/activate_account.html +10 -0
  118. flaskbb/templates/email/activate_account.txt +11 -0
  119. flaskbb/templates/email/reset_password.html +9 -0
  120. flaskbb/templates/email/reset_password.txt +11 -0
  121. flaskbb/templates/errors/forbidden_page.html +14 -0
  122. flaskbb/templates/errors/page_not_found.html +14 -0
  123. flaskbb/templates/errors/server_error.html +13 -0
  124. flaskbb/templates/errors/too_many_logins.html +14 -0
  125. flaskbb/templates/forum/_forum_meta.html +11 -0
  126. flaskbb/templates/forum/_forum_row.html +73 -0
  127. flaskbb/templates/forum/category.html +16 -0
  128. flaskbb/templates/forum/category_layout.html +124 -0
  129. flaskbb/templates/forum/edit_forum.html +109 -0
  130. flaskbb/templates/forum/forum.html +72 -0
  131. flaskbb/templates/forum/index.html +32 -0
  132. flaskbb/templates/forum/memberlist.html +62 -0
  133. flaskbb/templates/forum/new_post.html +49 -0
  134. flaskbb/templates/forum/new_topic.html +50 -0
  135. flaskbb/templates/forum/online_users.html +25 -0
  136. flaskbb/templates/forum/report_post.html +23 -0
  137. flaskbb/templates/forum/search_form.html +27 -0
  138. flaskbb/templates/forum/search_result.html +340 -0
  139. flaskbb/templates/forum/topic.html +244 -0
  140. flaskbb/templates/forum/topic_controls.html +119 -0
  141. flaskbb/templates/forum/topic_horizontal.html +201 -0
  142. flaskbb/templates/forum/topictracker.html +121 -0
  143. flaskbb/templates/layout.html +298 -0
  144. flaskbb/templates/management/banned_users.html +129 -0
  145. flaskbb/templates/management/category_form.html +46 -0
  146. flaskbb/templates/management/forum_form.html +51 -0
  147. flaskbb/templates/management/forums.html +191 -0
  148. flaskbb/templates/management/group_form.html +62 -0
  149. flaskbb/templates/management/groups.html +97 -0
  150. flaskbb/templates/management/management_layout.html +23 -0
  151. flaskbb/templates/management/overview.html +178 -0
  152. flaskbb/templates/management/plugins.html +96 -0
  153. flaskbb/templates/management/reports.html +116 -0
  154. flaskbb/templates/management/settings.html +74 -0
  155. flaskbb/templates/management/user_form.html +65 -0
  156. flaskbb/templates/management/users.html +165 -0
  157. flaskbb/templates/user/all_posts.html +48 -0
  158. flaskbb/templates/user/all_topics.html +56 -0
  159. flaskbb/templates/user/change_email.html +19 -0
  160. flaskbb/templates/user/change_password.html +19 -0
  161. flaskbb/templates/user/change_user_details.html +28 -0
  162. flaskbb/templates/user/general_settings.html +18 -0
  163. flaskbb/templates/user/profile.html +47 -0
  164. flaskbb/templates/user/profile_layout.html +124 -0
  165. flaskbb/templates/user/settings_layout.html +32 -0
  166. flaskbb/themes/aurora/Makefile +21 -0
  167. flaskbb/themes/aurora/README.md +41 -0
  168. flaskbb/themes/aurora/build_emoji_set.py +32 -0
  169. flaskbb/themes/aurora/info.json +11 -0
  170. flaskbb/themes/aurora/package-lock.json +4904 -0
  171. flaskbb/themes/aurora/package.json +60 -0
  172. flaskbb/themes/aurora/preview.png +0 -0
  173. flaskbb/themes/aurora/src/app/confirm_modal.js +45 -0
  174. flaskbb/themes/aurora/src/app/editor.js +142 -0
  175. flaskbb/themes/aurora/src/app/emoji.js +3515 -0
  176. flaskbb/themes/aurora/src/app/flaskbb.js +271 -0
  177. flaskbb/themes/aurora/src/app/utils.js +11 -0
  178. flaskbb/themes/aurora/src/app.js +26 -0
  179. flaskbb/themes/aurora/src/assets/avatar.svg +4 -0
  180. flaskbb/themes/aurora/src/assets/avatar100x100.png +0 -0
  181. flaskbb/themes/aurora/src/assets/avatar150x150.png +0 -0
  182. flaskbb/themes/aurora/src/assets/avatar400x400.png +0 -0
  183. flaskbb/themes/aurora/src/assets/avatar80x80.png +0 -0
  184. flaskbb/themes/aurora/src/assets/favicon.ico +0 -0
  185. flaskbb/themes/aurora/src/scss/_button.scss +39 -0
  186. flaskbb/themes/aurora/src/scss/_category.scss +67 -0
  187. flaskbb/themes/aurora/src/scss/_forum.scss +56 -0
  188. flaskbb/themes/aurora/src/scss/_management.scss +205 -0
  189. flaskbb/themes/aurora/src/scss/_misc.scss +181 -0
  190. flaskbb/themes/aurora/src/scss/_navigation.scss +130 -0
  191. flaskbb/themes/aurora/src/scss/_page.scss +51 -0
  192. flaskbb/themes/aurora/src/scss/_profile.scss +121 -0
  193. flaskbb/themes/aurora/src/scss/_pygments.scss +65 -0
  194. flaskbb/themes/aurora/src/scss/_text.scss +43 -0
  195. flaskbb/themes/aurora/src/scss/_topic.scss +172 -0
  196. flaskbb/themes/aurora/src/scss/_variables.scss +122 -0
  197. flaskbb/themes/aurora/src/scss/styles.scss +26 -0
  198. flaskbb/themes/aurora/tsconfig.json +24 -0
  199. flaskbb/themes/aurora/webpack.common.js +132 -0
  200. flaskbb/themes/aurora/webpack.dev.js +7 -0
  201. flaskbb/themes/aurora/webpack.prod.js +7 -0
  202. flaskbb/tokens/__init__.py +12 -0
  203. flaskbb/tokens/serializer.py +93 -0
  204. flaskbb/tokens/verifiers.py +34 -0
  205. flaskbb/translations/da/LC_MESSAGES/messages.po +3324 -0
  206. flaskbb/translations/de/LC_MESSAGES/messages.po +3359 -0
  207. flaskbb/translations/en/LC_MESSAGES/messages.po +4126 -0
  208. flaskbb/translations/es/LC_MESSAGES/messages.po +3363 -0
  209. flaskbb/translations/fr/LC_MESSAGES/messages.po +3398 -0
  210. flaskbb/translations/nb_NO/LC_MESSAGES/messages.po +3493 -0
  211. flaskbb/translations/pl/LC_MESSAGES/messages.po +3350 -0
  212. flaskbb/translations/pt/LC_MESSAGES/messages.po +3488 -0
  213. flaskbb/translations/pt_BR/LC_MESSAGES/messages.po +3350 -0
  214. flaskbb/translations/ru/LC_MESSAGES/messages.po +3335 -0
  215. flaskbb/translations/sv_SE/LC_MESSAGES/messages.po +3347 -0
  216. flaskbb/translations/ta/LC_MESSAGES/messages.po +3528 -0
  217. flaskbb/translations/uk/LC_MESSAGES/messages.po +3505 -0
  218. flaskbb/translations/zh_CN/LC_MESSAGES/messages.po +3325 -0
  219. flaskbb/translations/zh_TW/LC_MESSAGES/messages.po +3324 -0
  220. flaskbb/user/__init__.py +19 -0
  221. flaskbb/user/forms.py +131 -0
  222. flaskbb/user/models.py +527 -0
  223. flaskbb/user/plugins.py +87 -0
  224. flaskbb/user/services/__init__.py +0 -0
  225. flaskbb/user/services/factories.py +89 -0
  226. flaskbb/user/services/update.py +88 -0
  227. flaskbb/user/services/validators.py +107 -0
  228. flaskbb/user/views.py +235 -0
  229. flaskbb/utils/__init__.py +3 -0
  230. flaskbb/utils/alembic.py +79 -0
  231. flaskbb/utils/database.py +189 -0
  232. flaskbb/utils/datastructures.py +31 -0
  233. flaskbb/utils/forms.py +209 -0
  234. flaskbb/utils/helpers.py +940 -0
  235. flaskbb/utils/http.py +81 -0
  236. flaskbb/utils/populate.py +445 -0
  237. flaskbb/utils/queries.py +154 -0
  238. flaskbb/utils/requirements.py +352 -0
  239. flaskbb/utils/search.py +143 -0
  240. flaskbb/utils/settings.py +55 -0
  241. flaskbb/utils/translations.py +229 -0
  242. flaskbb-2.2.0.dist-info/METADATA +120 -0
  243. flaskbb-2.2.0.dist-info/RECORD +247 -0
  244. flaskbb-2.2.0.dist-info/WHEEL +4 -0
  245. flaskbb-2.2.0.dist-info/entry_points.txt +2 -0
  246. flaskbb-2.2.0.dist-info/licenses/LICENSE +33 -0
  247. wsgi.py +11 -0
flaskbb/auth/forms.py ADDED
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.forms
4
+ ~~~~~~~~~~~~~~~~~~
5
+
6
+ It provides the forms that are needed for the auth views.
7
+
8
+ :copyright: (c) 2014 by the FlaskBB Team.
9
+ :license: BSD, see LICENSE for more details.
10
+ """
11
+
12
+ import logging
13
+
14
+ from flask_babelplus import lazy_gettext as _
15
+ from wtforms import (
16
+ BooleanField,
17
+ HiddenField,
18
+ PasswordField,
19
+ SelectField,
20
+ StringField,
21
+ SubmitField,
22
+ )
23
+ from wtforms.validators import (
24
+ DataRequired,
25
+ Email,
26
+ EqualTo,
27
+ InputRequired,
28
+ regexp,
29
+ )
30
+
31
+ from flaskbb.utils.forms import FlaskBBForm, add_recaptcha_field
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ USERNAME_RE = r"^[\w.+-]+$"
36
+ is_valid_username = regexp(
37
+ USERNAME_RE, message=_("You can only use letters, numbers or dashes.")
38
+ )
39
+
40
+
41
+ @add_recaptcha_field()
42
+ class LoginForm(FlaskBBForm):
43
+ login = StringField(
44
+ _("Username or Email address"),
45
+ validators=[
46
+ DataRequired(message=_("Please enter your username or email address."))
47
+ ],
48
+ )
49
+
50
+ password = PasswordField(
51
+ _("Password"),
52
+ validators=[DataRequired(message=_("Please enter your password."))],
53
+ )
54
+
55
+ remember_me = BooleanField(_("Remember me"), default=False)
56
+
57
+ submit = SubmitField(_("Login"))
58
+
59
+
60
+ @add_recaptcha_field(only_on_ratelimit=True)
61
+ class RegisterForm(FlaskBBForm):
62
+ username = StringField(
63
+ _("Username"),
64
+ validators=[
65
+ DataRequired(message=_("A valid username is required")),
66
+ is_valid_username,
67
+ ],
68
+ )
69
+
70
+ email = StringField(
71
+ _("Email address"),
72
+ validators=[
73
+ DataRequired(message=_("A valid email address is required.")),
74
+ Email(message=_("Invalid email address.")),
75
+ ],
76
+ )
77
+
78
+ password = PasswordField(
79
+ _("Password"),
80
+ validators=[
81
+ InputRequired(),
82
+ EqualTo("confirm_password", message=_("Passwords must match.")),
83
+ ],
84
+ )
85
+
86
+ confirm_password = PasswordField(_("Confirm password"))
87
+
88
+ language = SelectField(_("Language"))
89
+
90
+ accept_tos = BooleanField(
91
+ _("I accept the Terms of Service"),
92
+ validators=[DataRequired(message=_("Please accept the TOS."))],
93
+ default=True,
94
+ )
95
+
96
+ submit = SubmitField(_("Register"))
97
+
98
+
99
+ class ReauthForm(FlaskBBForm):
100
+ password = PasswordField(
101
+ _("Password"),
102
+ validators=[DataRequired(message=_("Please enter your password."))],
103
+ )
104
+
105
+ submit = SubmitField(_("Refresh Login"))
106
+
107
+
108
+ @add_recaptcha_field()
109
+ class ForgotPasswordForm(FlaskBBForm):
110
+ email = StringField(
111
+ _("Email address"),
112
+ validators=[
113
+ DataRequired(message=_("A valid email address is required.")),
114
+ Email(),
115
+ ],
116
+ )
117
+
118
+ submit = SubmitField(_("Request Password"))
119
+
120
+
121
+ class ResetPasswordForm(FlaskBBForm):
122
+ token = HiddenField("Token")
123
+
124
+ email = StringField(
125
+ _("Email address"),
126
+ validators=[
127
+ DataRequired(message=_("A valid email address is required.")),
128
+ Email(),
129
+ ],
130
+ )
131
+
132
+ password = PasswordField(
133
+ _("Password"),
134
+ validators=[
135
+ InputRequired(),
136
+ EqualTo("confirm_password", message=_("Passwords must match.")),
137
+ ],
138
+ )
139
+
140
+ confirm_password = PasswordField(_("Confirm password"))
141
+
142
+ submit = SubmitField(_("Reset password"))
143
+
144
+
145
+ class RequestActivationForm(FlaskBBForm):
146
+ username = StringField(
147
+ _("Username"),
148
+ validators=[
149
+ DataRequired(message=_("A valid username is required.")),
150
+ is_valid_username,
151
+ ],
152
+ )
153
+
154
+ email = StringField(
155
+ _("Email address"),
156
+ validators=[
157
+ DataRequired(message=_("A valid email address is required.")),
158
+ Email(message=_("Invalid email address.")),
159
+ ],
160
+ )
161
+
162
+ submit = SubmitField(_("Send Confirmation Mail"))
163
+
164
+
165
+ class AccountActivationForm(FlaskBBForm):
166
+ token = StringField(
167
+ _("Email confirmation token"),
168
+ validators=[DataRequired(_("Please enter the token we have sent to you."))],
169
+ )
170
+ submit = SubmitField(_("Confirm Email"))
@@ -0,0 +1,119 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.plugins
4
+ ~~~~~~~~~~~~~~~~~~~~
5
+ Plugin implementations for FlaskBB auth hooks
6
+
7
+ :copyright: (c) 2014-2018 the FlaskBB Team.
8
+ :license: BSD, see LICENSE for more details
9
+ """
10
+
11
+ from flask import flash, redirect, url_for
12
+ from flask_login import current_user, logout_user
13
+
14
+ from ..core.auth.authentication import ForceLogout
15
+ from ..extensions import db
16
+ from ..user.models import User
17
+ from ..utils.settings import flaskbb_config
18
+ from . import impl
19
+ from .services.authentication import (
20
+ BlockUnactivatedUser,
21
+ ClearFailedLogins,
22
+ DefaultFlaskBBAuthProvider,
23
+ MarkFailedLogin,
24
+ )
25
+ from .services.factories import account_activator_factory
26
+ from .services.reauthentication import (
27
+ ClearFailedLoginsOnReauth,
28
+ DefaultFlaskBBReauthProvider,
29
+ MarkFailedReauth,
30
+ )
31
+ from .services.registration import (
32
+ AutoActivateUserPostProcessor,
33
+ AutologinPostProcessor,
34
+ EmailUniquenessValidator,
35
+ SendActivationPostProcessor,
36
+ UsernameRequirements,
37
+ UsernameUniquenessValidator,
38
+ UsernameValidator,
39
+ )
40
+
41
+
42
+ @impl(trylast=True)
43
+ def flaskbb_authenticate(identifier, secret):
44
+ return DefaultFlaskBBAuthProvider().authenticate(identifier, secret)
45
+
46
+
47
+ @impl(tryfirst=True)
48
+ def flaskbb_post_authenticate(user):
49
+ handlers = [ClearFailedLogins()]
50
+
51
+ if flaskbb_config["ACTIVATE_ACCOUNT"]:
52
+ handlers.append(BlockUnactivatedUser())
53
+
54
+ for handler in handlers:
55
+ handler.handle_post_auth(user)
56
+
57
+
58
+ @impl
59
+ def flaskbb_authentication_failed(identifier):
60
+ MarkFailedLogin().handle_authentication_failure(identifier)
61
+
62
+
63
+ @impl(trylast=True)
64
+ def flaskbb_reauth_attempt(user, secret):
65
+ return DefaultFlaskBBReauthProvider().reauthenticate(user, secret)
66
+
67
+
68
+ @impl
69
+ def flaskbb_reauth_failed(user):
70
+ MarkFailedReauth().handle_reauth_failure(user)
71
+
72
+
73
+ @impl
74
+ def flaskbb_post_reauth(user):
75
+ ClearFailedLoginsOnReauth().handle_post_reauth(user)
76
+
77
+
78
+ @impl
79
+ def flaskbb_errorhandlers(app):
80
+ @app.errorhandler(ForceLogout)
81
+ def handle_force_logout(error):
82
+ if current_user:
83
+ logout_user()
84
+ if error.reason:
85
+ flash(error.reason, "danger")
86
+ return redirect(url_for("forum.index"))
87
+
88
+
89
+ @impl
90
+ def flaskbb_gather_registration_validators():
91
+ blacklist = [
92
+ w.strip() for w in flaskbb_config["AUTH_USERNAME_BLACKLIST"].split(",")
93
+ ]
94
+
95
+ requirements = UsernameRequirements(
96
+ min=flaskbb_config["AUTH_USERNAME_MIN_LENGTH"],
97
+ max=flaskbb_config["AUTH_USERNAME_MAX_LENGTH"],
98
+ blacklist=blacklist,
99
+ )
100
+
101
+ return [
102
+ EmailUniquenessValidator(User),
103
+ UsernameUniquenessValidator(User),
104
+ UsernameValidator(requirements),
105
+ ]
106
+
107
+
108
+ @impl
109
+ def flaskbb_registration_post_processor(user):
110
+ handlers = []
111
+
112
+ if flaskbb_config["ACTIVATE_ACCOUNT"]:
113
+ handlers.append(SendActivationPostProcessor(account_activator_factory()))
114
+ else:
115
+ handlers.append(AutologinPostProcessor())
116
+ handlers.append(AutoActivateUserPostProcessor(db, flaskbb_config))
117
+
118
+ for handler in handlers:
119
+ handler.post_process(user)
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.services
4
+ ~~~~~~~~~~~~~~~~~~~~~
5
+ Public module of implemenations of auth related services
6
+ in FlaskBB. If you are developing a plugin or extending
7
+ FlaskBB, you should import from this module rather than
8
+ submodules.
9
+
10
+ :copyright: (c) 2014-2018 the FlaskBB Team.
11
+ :license: BSD, see LICENSE for more details
12
+ """
13
+
14
+ from .activation import AccountActivator
15
+ from .authentication import (
16
+ BlockTooManyFailedLogins,
17
+ BlockUnactivatedUser,
18
+ DefaultFlaskBBAuthProvider,
19
+ FailedLoginConfiguration,
20
+ MarkFailedLogin,
21
+ PluginAuthenticationManager,
22
+ )
23
+ from .factories import (
24
+ account_activator_factory,
25
+ authentication_manager_factory,
26
+ reauthentication_manager_factory,
27
+ registration_service_factory,
28
+ reset_service_factory,
29
+ )
30
+ from .password import ResetPasswordService
31
+ from .registration import (
32
+ EmailUniquenessValidator,
33
+ UsernameRequirements,
34
+ UsernameUniquenessValidator,
35
+ UsernameValidator,
36
+ )
37
+
38
+ __all__ = (
39
+ "AccountActivator",
40
+ "account_activator_factory",
41
+ "authentication_manager_factory",
42
+ "BlockTooManyFailedLogins",
43
+ "BlockUnactivatedUser",
44
+ "DefaultFlaskBBAuthProvider",
45
+ "EmailUniquenessValidator",
46
+ "FailedLoginConfiguration",
47
+ "MarkFailedLogin",
48
+ "PluginAuthenticationManager",
49
+ "reauthentication_manager_factory",
50
+ "registration_service_factory",
51
+ "ResetPasswordService",
52
+ "reset_service_factory",
53
+ "UsernameRequirements",
54
+ "UsernameUniquenessValidator",
55
+ "UsernameValidator",
56
+ )
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.services.activation
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+ Handlers for activating accounts in FlaskBB
6
+
7
+ :copyright: (c) 2014-2018 the FlaskBB Team
8
+ :license: BSD, see LICENSE for more details
9
+ """
10
+
11
+ from flask_babelplus import gettext as _
12
+
13
+ from ...core.auth.activation import AccountActivator as _AccountActivator
14
+ from ...core.exceptions import ValidationError
15
+ from ...core.tokens import Token, TokenActions, TokenError
16
+ from ...email import send_activation_token
17
+ from ...extensions import db
18
+
19
+
20
+ class AccountActivator(_AccountActivator):
21
+ """
22
+ Default account activator for FlaskBB, handles the activation
23
+ process through email.
24
+ """
25
+
26
+ def __init__(self, token_serializer, users):
27
+ self.token_serializer = token_serializer
28
+ self.users = users
29
+
30
+ def initiate_account_activation(self, email: str):
31
+ user = db.session.execute(
32
+ db.select(self.users).filter_by(email=email)
33
+ ).scalar_one_or_none()
34
+
35
+ if user is None:
36
+ raise ValidationError("email", _("Entered email doesn't exist"))
37
+
38
+ if user.activated:
39
+ raise ValidationError("email", _("Account is already activated"))
40
+
41
+ token = self.token_serializer.dumps(
42
+ Token(user_id=user.id, operation=TokenActions.ACTIVATE_ACCOUNT)
43
+ )
44
+
45
+ send_activation_token.delay(
46
+ token=token, username=user.username, email=user.email
47
+ )
48
+
49
+ def activate_account(self, token):
50
+ token = self.token_serializer.loads(token)
51
+ if token.operation != TokenActions.ACTIVATE_ACCOUNT:
52
+ raise TokenError.invalid()
53
+ user = db.session.execute(
54
+ db.select(self.users).filter_by(id=token.user_id)
55
+ ).scalar_one_or_none()
56
+ if user is None:
57
+ raise TokenError.invalid()
58
+ if user.activated:
59
+ raise ValidationError("activated", _("Account is already activated"))
60
+ user.activated = True
@@ -0,0 +1,188 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.services.authentication
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+
6
+ Authentication providers, handlers and post-processors
7
+ in FlaskBB
8
+
9
+ :copyright: (c) 2014-2018 the FlaskBB Team.
10
+ :license: BSD, see LICENSE for more details
11
+ """
12
+
13
+ import logging
14
+ from datetime import datetime
15
+ from typing import TYPE_CHECKING
16
+
17
+ import attr
18
+ from flask_babelplus import gettext as _
19
+ from pytz import UTC
20
+ from werkzeug.security import check_password_hash
21
+
22
+ from ...core.auth.authentication import (
23
+ AuthenticationFailureHandler,
24
+ AuthenticationManager,
25
+ AuthenticationProvider,
26
+ PostAuthenticationHandler,
27
+ StopAuthentication,
28
+ )
29
+ from ...extensions import db
30
+ from ...user.models import User
31
+ from ...utils.helpers import time_utcnow
32
+
33
+ if TYPE_CHECKING:
34
+ from flask_sqlalchemy.session import Session
35
+
36
+ from flaskbb.plugins.manager import FlaskBBPluginManager
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ @attr.s(frozen=True)
42
+ class FailedLoginConfiguration(object):
43
+ """
44
+ Used to configure how many failed logins are accepted until an account
45
+ is temporarily locked out and how long to temporarily lock the account
46
+ out for.
47
+ """
48
+
49
+ limit = attr.ib()
50
+ lockout_window = attr.ib()
51
+
52
+
53
+ class BlockTooManyFailedLogins(AuthenticationProvider):
54
+ """
55
+ Pre authentication check to block a login from an account that has too
56
+ many failed login attempts in place.
57
+ """
58
+
59
+ def __init__(self, configuration):
60
+ self.configuration = configuration
61
+
62
+ def authenticate(self, identifier, secret):
63
+ user = db.session.execute(
64
+ db.select(User).filter(
65
+ db.or_(User.username == identifier, User.email == identifier)
66
+ )
67
+ ).scalar_one_or_none()
68
+
69
+ if user is not None:
70
+ attempts = user.login_attempts
71
+ last_attempt = user.last_failed_login or datetime.min.replace(tzinfo=UTC)
72
+ reached_attempt_limit = attempts >= self.configuration.limit
73
+ inside_lockout = (
74
+ last_attempt + self.configuration.lockout_window
75
+ ) >= time_utcnow()
76
+
77
+ if reached_attempt_limit and inside_lockout:
78
+ raise StopAuthentication(
79
+ _(
80
+ "Your account is currently locked out due to too many "
81
+ "failed login attempts"
82
+ )
83
+ )
84
+
85
+
86
+ class DefaultFlaskBBAuthProvider(AuthenticationProvider):
87
+ """
88
+ This is the default username/email and password authentication checker,
89
+ locates the user based on the identifer passed -- either username or email
90
+ -- and compares the supplied password to the hash connected to the matching
91
+ user (if any).
92
+
93
+ Offers protection against timing attacks that would rely on the difference
94
+ in response time from not matching a password hash.
95
+ """
96
+
97
+ def authenticate(self, identifier: str, secret: str):
98
+ user = db.session.execute(
99
+ db.select(User).filter(
100
+ db.or_(User.username == identifier, User.email == identifier)
101
+ )
102
+ ).scalar_one_or_none()
103
+
104
+ if user is not None:
105
+ if check_password_hash(user.password, secret):
106
+ return user
107
+ return None
108
+
109
+ check_password_hash("dummy password", secret)
110
+ return None
111
+
112
+
113
+ class MarkFailedLogin(AuthenticationFailureHandler):
114
+ """
115
+ Failure handler that marks the login attempt on the user and sets the
116
+ last failed date when it happened.
117
+ """
118
+
119
+ def handle_authentication_failure(self, identifier: str):
120
+ user = db.session.execute(
121
+ db.select(User).filter(
122
+ db.or_(User.username == identifier, User.email == identifier)
123
+ )
124
+ ).scalar_one_or_none()
125
+
126
+ if user is not None:
127
+ user.login_attempts += 1
128
+ user.last_failed_login = time_utcnow()
129
+
130
+
131
+ class BlockUnactivatedUser(PostAuthenticationHandler):
132
+ """
133
+ Post auth handler that will block a user that has managed to pass the
134
+ authentication check but has not actually activated their account yet.
135
+ """
136
+
137
+ def handle_post_auth(self, user: "User"):
138
+ if not user.activated: # pragma: no branch
139
+ raise StopAuthentication(
140
+ _(
141
+ "In order to use your account you have to "
142
+ "activate it through the link we have sent to "
143
+ "your email address."
144
+ )
145
+ )
146
+
147
+
148
+ class ClearFailedLogins(PostAuthenticationHandler):
149
+ """
150
+ Post auth handler that clears all failed login attempts from a user's
151
+ account.
152
+ """
153
+
154
+ def handle_post_auth(self, user: "User"):
155
+ user.login_attempts = 0
156
+
157
+
158
+ class PluginAuthenticationManager(AuthenticationManager):
159
+ """
160
+ Authentication manager relying on plugin hooks to manage the authentication
161
+ process. This is the default authentication manager for FlaskBB.
162
+ """
163
+
164
+ def __init__(self, plugin_manager: "FlaskBBPluginManager", session: Session):
165
+ self.plugin_manager = plugin_manager
166
+ self.session = session
167
+
168
+ def authenticate(self, identifier: str, secret: str):
169
+ try:
170
+ user = self.plugin_manager.hook.flaskbb_authenticate(
171
+ identifier=identifier, secret=secret
172
+ )
173
+ if user is None:
174
+ raise StopAuthentication(_("Wrong username or password."))
175
+ self.plugin_manager.hook.flaskbb_post_authenticate(user=user)
176
+ return user
177
+ except StopAuthentication:
178
+ self.plugin_manager.hook.flaskbb_authentication_failed(
179
+ identifier=identifier
180
+ )
181
+ raise
182
+ finally:
183
+ try:
184
+ self.session.commit()
185
+ except Exception:
186
+ logger.exception("Exception while processing login")
187
+ self.session.rollback()
188
+ raise
@@ -0,0 +1,52 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ flaskbb.auth.services.factories
4
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+ Factory functions for various FlaskBB auth services
6
+
7
+ These factories are provisional.
8
+
9
+ :copyright: 2014-2018 the FlaskBB Team.
10
+ :license: BSD, see LICENSE for more details
11
+ """
12
+
13
+ from datetime import timedelta
14
+
15
+ from flask import current_app
16
+
17
+ from ...extensions import db, pluggy
18
+ from ...tokens import FlaskBBTokenSerializer
19
+ from ...tokens.verifiers import EmailMatchesUserToken
20
+ from ...user.models import User
21
+ from .activation import AccountActivator
22
+ from .authentication import PluginAuthenticationManager
23
+ from .password import ResetPasswordService
24
+ from .reauthentication import PluginReauthenticationManager
25
+ from .registration import RegistrationService
26
+
27
+
28
+ def registration_service_factory():
29
+ return RegistrationService(pluggy, User, db)
30
+
31
+
32
+ def reset_service_factory():
33
+ token_serializer = FlaskBBTokenSerializer(
34
+ current_app.config["SECRET_KEY"], expiry=timedelta(hours=1)
35
+ )
36
+ verifiers = [EmailMatchesUserToken(User)]
37
+ return ResetPasswordService(token_serializer, User, token_verifiers=verifiers)
38
+
39
+
40
+ def account_activator_factory():
41
+ token_serializer = FlaskBBTokenSerializer(
42
+ current_app.config["SECRET_KEY"], expiry=timedelta(hours=1)
43
+ )
44
+ return AccountActivator(token_serializer, User)
45
+
46
+
47
+ def authentication_manager_factory():
48
+ return PluginAuthenticationManager(pluggy, db.session)
49
+
50
+
51
+ def reauthentication_manager_factory():
52
+ return PluginReauthenticationManager(pluggy, db.session)