]> git.ipfire.org Git - ipfire.org.git/blob - src/web/auth.py
Use Python's internal asyncio stuff instead of Tornado's
[ipfire.org.git] / src / web / auth.py
1 #!/usr/bin/python
2
3 import logging
4 import tornado.web
5
6 from . import base
7
8 class CacheMixin(object):
9 def prepare(self):
10 # Mark this as private when someone is logged in
11 if self.current_user:
12 self.add_header("Cache-Control", "private")
13
14 self.add_header("Vary", "Cookie")
15
16
17 class AuthenticationMixin(CacheMixin):
18 def login(self, account):
19 # User has logged in, create a session
20 session_id, session_expires = self.backend.accounts.create_session(
21 account, self.request.host)
22
23 # Check if a new session was created
24 if not session_id:
25 raise tornado.web.HTTPError(500, "Could not create session")
26
27 # Send session cookie to the client
28 self.set_cookie("session_id", session_id,
29 domain=self.request.host, expires=session_expires)
30
31 def logout(self):
32 session_id = self.get_cookie("session_id")
33 if not session_id:
34 return
35
36 success = self.backend.accounts.destroy_session(session_id, self.request.host)
37 if success:
38 self.clear_cookie("session_id")
39
40
41 class LoginHandler(AuthenticationMixin, base.BaseHandler):
42 @base.blacklisted
43 def get(self):
44 next = self.get_argument("next", None)
45
46 self.render("auth/login.html", next=next,
47 incorrect=False, username=None)
48
49 @base.blacklisted
50 @base.ratelimit(minutes=60, requests=5)
51 def post(self):
52 username = self.get_argument("username")
53 password = self.get_argument("password")
54 next = self.get_argument("next", "/")
55
56 # Find user
57 account = self.backend.accounts.auth(username, password)
58 if not account:
59 logging.error("Unknown user or invalid password: %s" % username)
60
61 # Set status to 401
62 self.set_status(401)
63
64 # Render login page again
65 return self.render("auth/login.html",
66 incorrect=True, username=username, next=next,
67 )
68
69 # Create session
70 with self.db.transaction():
71 self.login(account)
72
73 # Redirect the user
74 return self.redirect(next)
75
76
77 class LogoutHandler(AuthenticationMixin, base.BaseHandler):
78 def get(self):
79 with self.db.transaction():
80 self.logout()
81
82 # Get back to the start page
83 self.redirect("/")
84
85
86 class RegisterHandler(base.BaseHandler):
87 @base.blacklisted
88 def get(self):
89 # Redirect logged in users away
90 if self.current_user:
91 self.redirect("/")
92
93 self.render("auth/register.html")
94
95 @base.ratelimit(minutes=24*60, requests=5)
96 async def post(self):
97 uid = self.get_argument("uid")
98 email = self.get_argument("email")
99
100 first_name = self.get_argument("first_name")
101 last_name = self.get_argument("last_name")
102
103 # Check if this is a spam account
104 is_spam = await self.backend.accounts.check_spam(uid, email,
105 address=self.get_remote_ip())
106
107 if is_spam:
108 self.render("auth/register-spam.html")
109 return
110
111 # Register account
112 try:
113 with self.db.transaction():
114 self.backend.accounts.register(uid, email,
115 first_name=first_name, last_name=last_name,
116 country_code=self.current_country_code)
117 except ValueError as e:
118 raise tornado.web.HTTPError(400, "%s" % e) from e
119
120 self.render("auth/register-success.html")
121
122
123 class ActivateHandler(AuthenticationMixin, base.BaseHandler):
124 def get(self, uid, activation_code):
125 self.render("auth/activate.html")
126
127 def post(self, uid, activation_code):
128 password1 = self.get_argument("password1")
129 password2 = self.get_argument("password2")
130
131 if not password1 == password2:
132 raise tornado.web.HTTPError(400, "Passwords do not match")
133
134 with self.db.transaction():
135 account = self.backend.accounts.activate(uid, activation_code)
136 if not account:
137 raise tornado.web.HTTPError(400, "Account not found: %s" % uid)
138
139 # Set the new password
140 account.passwd(password1)
141
142 # Create session
143 self.login(account)
144
145 # Redirect to success page
146 self.render("auth/activated.html", account=account)
147
148
149 class PasswordResetInitiationHandler(base.BaseHandler):
150 def get(self):
151 username = self.get_argument("username", None)
152
153 self.render("auth/password-reset-initiation.html", username=username)
154
155 @base.ratelimit(minutes=60, requests=5)
156 def post(self):
157 username = self.get_argument("username")
158
159 # Fetch account and submit password reset
160 account = self.backend.accounts.get_by_uid(username)
161 if account:
162 with self.db.transaction():
163 account.request_password_reset()
164
165 self.render("auth/password-reset-successful.html")
166
167
168 class PasswordResetHandler(AuthenticationMixin, base.BaseHandler):
169 def get(self, uid, reset_code):
170 account = self.backend.accounts.get_by_uid(uid)
171 if not account:
172 raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
173
174 self.render("auth/password-reset.html", account=account)
175
176 def post(self, uid, reset_code):
177 account = self.backend.accounts.get_by_uid(uid)
178 if not account:
179 raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
180
181 password1 = self.get_argument("password1")
182 password2 = self.get_argument("password2")
183
184 if not password1 == password2:
185 raise tornado.web.HTTPError(400, "Passwords do not match")
186
187 # Try to perform password reset
188 with self.db.transaction():
189 account.reset_password(reset_code, password1)
190
191 # Login the user straight away after reset was successful
192 self.login(account)
193
194 # Redirect back to /
195 self.redirect("/")
196
197
198 class APICheckUID(base.APIHandler):
199 @base.ratelimit(minutes=10, requests=100)
200 def get(self):
201 uid = self.get_argument("uid")
202 result = None
203
204 if not uid:
205 result = "empty"
206
207 # Check if the username is syntactically valid
208 elif not self.backend.accounts.uid_is_valid(uid):
209 result = "invalid"
210
211 # Check if the username is already taken
212 elif self.backend.accounts.uid_exists(uid):
213 result = "taken"
214
215 # Username seems to be okay
216 self.finish({ "result" : result or "ok" })