]>
git.ipfire.org Git - ipfire.org.git/blob - src/web/auth.py
b178af1525255005e8169f55a19eaa291e703fa0
8 from . import ui_modules
10 class AuthenticationMixin(object):
11 def login(self
, account
):
12 # User has logged in, create a session
13 session_id
, session_expires
= self
.backend
.accounts
.create_session(
14 account
, self
.request
.host
)
16 # Check if a new session was created
18 raise tornado
.web
.HTTPError(500, "Could not create session")
20 # Send session cookie to the client
21 self
.set_cookie("session_id", session_id
,
22 domain
=self
.request
.host
, expires
=session_expires
, secure
=True)
25 session_id
= self
.get_cookie("session_id")
29 success
= self
.backend
.accounts
.destroy_session(session_id
, self
.request
.host
)
31 self
.clear_cookie("session_id")
34 class LoginHandler(base
.AnalyticsMixin
, AuthenticationMixin
, base
.BaseHandler
):
36 next
= self
.get_argument("next", None)
38 self
.render("auth/login.html", next
=next
,
39 incorrect
=False, username
=None)
41 @base.ratelimit(minutes
=15, requests
=10)
43 username
= self
.get_argument("username")
44 password
= self
.get_argument("password")
45 next
= self
.get_argument("next", "/")
48 account
= self
.backend
.accounts
.auth(username
, password
)
50 logging
.error("Unknown user or invalid password: %s" % username
)
55 # Render login page again
56 return self
.render("auth/login.html",
57 incorrect
=True, username
=username
, next
=next
,
61 with self
.db
.transaction():
65 return self
.redirect(next
)
68 class LogoutHandler(AuthenticationMixin
, base
.BaseHandler
):
70 with self
.db
.transaction():
73 # Get back to the start page
77 class JoinHandler(base
.AnalyticsMixin
, base
.BaseHandler
):
79 # Redirect logged in users away
84 self
.render("auth/join.html")
86 @base.ratelimit(minutes
=15, requests
=5)
88 uid
= self
.get_argument("uid")
89 email
= self
.get_argument("email")
91 first_name
= self
.get_argument("first_name")
92 last_name
= self
.get_argument("last_name")
94 # If the honey field has been set, we probably have a bot
95 honey
= self
.get_argument("honey", None)
97 raise tornado
.web
.HTTPError(503)
99 # Fail if first name and last name match
100 # Some bots don't seem to be very creative when filling in the form
101 if first_name
== last_name
:
102 raise tornado
.web
.HTTPError(503)
104 # Fail if the email address isn't valid
105 if self
.backend
.accounts
.mail_is_spam(email
):
106 raise tornado
.web
.HTTPError(503, "Email address looks spammy")
110 with self
.db
.transaction():
111 self
.backend
.accounts
.join(uid
, email
,
112 first_name
=first_name
, last_name
=last_name
,
113 country_code
=self
.current_country_code
)
114 except ValueError as e
:
115 raise tornado
.web
.HTTPError(400, "%s" % e
) from e
117 self
.render("auth/join-success.html")
120 class ActivateHandler(AuthenticationMixin
, base
.BaseHandler
):
121 def get(self
, uid
, activation_code
):
122 self
.render("auth/activate.html")
124 def post(self
, uid
, activation_code
):
125 password1
= self
.get_argument("password1")
126 password2
= self
.get_argument("password2")
128 if not password1
== password2
:
129 raise tornado
.web
.HTTPError(400, "Passwords do not match")
131 with self
.db
.transaction():
132 account
= self
.backend
.accounts
.activate(uid
, activation_code
)
134 raise tornado
.web
.HTTPError(400, "Account not found: %s" % uid
)
136 # Set the new password
137 account
.passwd(password1
)
142 # Redirect to success page
143 self
.render("auth/activated.html", account
=account
)
146 class DebugActivatedHandler(base
.BaseHandler
):
147 @tornado.web
.authenticated
149 self
.render("auth/activated.html", account
=self
.current_user
)
152 class PasswordResetInitiationHandler(base
.BaseHandler
):
154 username
= self
.get_argument("username", None)
156 self
.render("auth/password-reset-initiation.html", username
=username
)
158 @base.ratelimit(minutes
=15, requests
=10)
160 username
= self
.get_argument("username")
162 # Fetch account and submit password reset
163 with self
.db
.transaction():
164 account
= self
.backend
.accounts
.find_account(username
)
166 account
.request_password_reset()
168 self
.render("auth/password-reset-successful.html")
171 class PasswordResetHandler(AuthenticationMixin
, base
.BaseHandler
):
172 def get(self
, uid
, reset_code
):
173 account
= self
.backend
.accounts
.get_by_uid(uid
)
175 raise tornado
.web
.HTTPError(404, "Could not find account: %s" % uid
)
177 self
.render("auth/password-reset.html", account
=account
)
179 def post(self
, uid
, reset_code
):
180 account
= self
.backend
.accounts
.get_by_uid(uid
)
182 raise tornado
.web
.HTTPError(404, "Could not find account: %s" % uid
)
184 password1
= self
.get_argument("password1")
185 password2
= self
.get_argument("password2")
187 if not password1
== password2
:
188 raise tornado
.web
.HTTPError(400, "Passwords do not match")
190 # Try to perform password reset
191 with self
.db
.transaction():
192 account
.reset_password(reset_code
, password1
)
194 # Login the user straight away after reset was successful
201 class ConfirmEmailHandler(base
.BaseHandler
):
202 def get(self
, uid
, token
):
203 account
= self
.backend
.accounts
.get_by_uid(uid
)
205 raise tornado
.web
.HTTPError(404, "Could not find account: %s" % uid
)
207 with self
.db
.transaction():
208 success
= account
.confirm_email(token
)
210 self
.render("auth/email-confirmed.html", success
=success
)
213 class WellKnownChangePasswordHandler(base
.BaseHandler
):
214 @tornado.web
.authenticated
217 Implements https://web.dev/articles/change-password-url
219 self
.redirect("/users/%s/passwd" % self
.current_user
.uid
)
222 class SSODiscourse(base
.BaseHandler
):
223 @base.ratelimit(minutes
=24*60, requests
=100)
224 @tornado.web
.authenticated
226 # Fetch Discourse's parameters
227 sso
= self
.get_argument("sso")
228 sig
= self
.get_argument("sig")
232 params
= self
.accounts
.decode_discourse_payload(sso
, sig
)
234 # Raise bad request if the signature is invalid
236 raise tornado
.web
.HTTPError(400)
238 # Redirect back if user is already logged in
240 "nonce" : params
.get("nonce"),
241 "external_id" : self
.current_user
.uid
,
244 "email" : self
.current_user
.email
,
245 "require_activation" : "false",
247 # More details about the user
248 "username" : self
.current_user
.uid
,
249 "name" : "%s" % self
.current_user
,
250 "bio" : self
.current_user
.description
or "",
253 "avatar_url" : self
.current_user
.avatar_url(absolute
=True),
254 "avatar_force_update" : "true",
256 # Send a welcome message
257 "suppress_welcome_message" : "false",
260 "groups" : ",".join((group
.gid
for group
in self
.current_user
.groups
)),
263 "admin" : "true" if self
.current_user
.is_admin() else "false",
266 "moderator" : "true" if self
.current_user
.is_moderator() else "false",
269 # Format payload and sign it
270 payload
= self
.accounts
.encode_discourse_payload(**args
)
271 signature
= self
.accounts
.sign_discourse_payload(payload
)
273 qs
= urllib
.parse
.urlencode({
279 self
.redirect("%s?%s" % (params
.get("return_sso_url"), qs
))
282 class PasswordModule(ui_modules
.UIModule
):
283 def render(self
, account
=None):
284 return self
.render_string("auth/modules/password.html", account
=account
)
286 def javascript_files(self
):
287 return "js/zxcvbn.js"
289 def embedded_javascript(self
):
290 return self
.render_string("auth/modules/password.js")
293 class APICheckUID(base
.APIHandler
):
294 @base.ratelimit(minutes
=1, requests
=100)
296 uid
= self
.get_argument("uid")
302 # Check if the username is syntactically valid
303 elif not self
.backend
.accounts
.uid_is_valid(uid
):
306 # Check if the username is already taken
307 elif self
.backend
.accounts
.uid_exists(uid
):
310 # Username seems to be okay
311 self
.finish({ "result" : result
or "ok" })
314 class APICheckEmail(base
.APIHandler
):
315 @base.ratelimit(minutes
=1, requests
=100)
317 email
= self
.get_argument("email")
323 elif not self
.backend
.accounts
.mail_is_valid(email
):
326 # Check if this email address is blacklisted
327 elif self
.backend
.accounts
.mail_is_blacklisted(email
):
328 result
= "blacklisted"
330 # Check if this email address is already useed
331 elif self
.backend
.accounts
.get_by_mail(email
):
334 self
.finish({ "result" : result
or "ok" })