]>
Commit | Line | Data |
---|---|---|
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("Cache-Control", "no-store") | |
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, secure=True) | |
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 | def get(self): | |
43 | next = self.get_argument("next", None) | |
44 | ||
45 | self.render("auth/login.html", next=next, | |
46 | incorrect=False, username=None) | |
47 | ||
48 | @base.ratelimit(minutes=15, requests=10) | |
49 | def post(self): | |
50 | username = self.get_argument("username") | |
51 | password = self.get_argument("password") | |
52 | next = self.get_argument("next", "/") | |
53 | ||
54 | # Find user | |
55 | account = self.backend.accounts.auth(username, password) | |
56 | if not account: | |
57 | logging.error("Unknown user or invalid password: %s" % username) | |
58 | ||
59 | # Set status to 401 | |
60 | self.set_status(401) | |
61 | ||
62 | # Render login page again | |
63 | return self.render("auth/login.html", | |
64 | incorrect=True, username=username, next=next, | |
65 | ) | |
66 | ||
67 | # Create session | |
68 | with self.db.transaction(): | |
69 | self.login(account) | |
70 | ||
71 | # Redirect the user | |
72 | return self.redirect(next) | |
73 | ||
74 | ||
75 | class LogoutHandler(AuthenticationMixin, base.BaseHandler): | |
76 | def get(self): | |
77 | with self.db.transaction(): | |
78 | self.logout() | |
79 | ||
80 | # Get back to the start page | |
81 | self.redirect("/") | |
82 | ||
83 | ||
84 | class RegisterHandler(CacheMixin, base.BaseHandler): | |
85 | def get(self): | |
86 | # Redirect logged in users away | |
87 | if self.current_user: | |
88 | self.redirect("/") | |
89 | return | |
90 | ||
91 | self.render("auth/register.html") | |
92 | ||
93 | @base.ratelimit(minutes=15, requests=5) | |
94 | async def post(self): | |
95 | uid = self.get_argument("uid") | |
96 | email = self.get_argument("email") | |
97 | ||
98 | first_name = self.get_argument("first_name") | |
99 | last_name = self.get_argument("last_name") | |
100 | ||
101 | # Check if this is a spam account | |
102 | is_spam = await self.backend.accounts.check_spam(email, | |
103 | address=self.get_remote_ip()) | |
104 | ||
105 | if is_spam: | |
106 | self.render("auth/register-spam.html") | |
107 | return | |
108 | ||
109 | # Register account | |
110 | try: | |
111 | with self.db.transaction(): | |
112 | self.backend.accounts.register(uid, email, | |
113 | first_name=first_name, last_name=last_name, | |
114 | country_code=self.current_country_code) | |
115 | except ValueError as e: | |
116 | raise tornado.web.HTTPError(400, "%s" % e) from e | |
117 | ||
118 | self.render("auth/register-success.html") | |
119 | ||
120 | ||
121 | class ActivateHandler(AuthenticationMixin, base.BaseHandler): | |
122 | def get(self, uid, activation_code): | |
123 | self.render("auth/activate.html") | |
124 | ||
125 | def post(self, uid, activation_code): | |
126 | password1 = self.get_argument("password1") | |
127 | password2 = self.get_argument("password2") | |
128 | ||
129 | if not password1 == password2: | |
130 | raise tornado.web.HTTPError(400, "Passwords do not match") | |
131 | ||
132 | with self.db.transaction(): | |
133 | account = self.backend.accounts.activate(uid, activation_code) | |
134 | if not account: | |
135 | raise tornado.web.HTTPError(400, "Account not found: %s" % uid) | |
136 | ||
137 | # Set the new password | |
138 | account.passwd(password1) | |
139 | ||
140 | # Create session | |
141 | self.login(account) | |
142 | ||
143 | # Redirect to success page | |
144 | self.render("auth/activated.html", account=account) | |
145 | ||
146 | ||
147 | class PasswordResetInitiationHandler(CacheMixin, base.BaseHandler): | |
148 | def get(self): | |
149 | username = self.get_argument("username", None) | |
150 | ||
151 | self.render("auth/password-reset-initiation.html", username=username) | |
152 | ||
153 | @base.ratelimit(minutes=15, requests=10) | |
154 | def post(self): | |
155 | username = self.get_argument("username") | |
156 | ||
157 | # Fetch account and submit password reset | |
158 | account = self.backend.accounts.get_by_uid(username) | |
159 | if account: | |
160 | with self.db.transaction(): | |
161 | account.request_password_reset() | |
162 | ||
163 | self.render("auth/password-reset-successful.html") | |
164 | ||
165 | ||
166 | class PasswordResetHandler(AuthenticationMixin, base.BaseHandler): | |
167 | def get(self, uid, reset_code): | |
168 | account = self.backend.accounts.get_by_uid(uid) | |
169 | if not account: | |
170 | raise tornado.web.HTTPError(404, "Could not find account: %s" % uid) | |
171 | ||
172 | self.render("auth/password-reset.html", account=account) | |
173 | ||
174 | def post(self, uid, reset_code): | |
175 | account = self.backend.accounts.get_by_uid(uid) | |
176 | if not account: | |
177 | raise tornado.web.HTTPError(404, "Could not find account: %s" % uid) | |
178 | ||
179 | password1 = self.get_argument("password1") | |
180 | password2 = self.get_argument("password2") | |
181 | ||
182 | if not password1 == password2: | |
183 | raise tornado.web.HTTPError(400, "Passwords do not match") | |
184 | ||
185 | # Try to perform password reset | |
186 | with self.db.transaction(): | |
187 | account.reset_password(reset_code, password1) | |
188 | ||
189 | # Login the user straight away after reset was successful | |
190 | self.login(account) | |
191 | ||
192 | # Redirect back to / | |
193 | self.redirect("/") | |
194 | ||
195 | ||
196 | class APICheckUID(base.APIHandler): | |
197 | @base.ratelimit(minutes=1, requests=100) | |
198 | def get(self): | |
199 | uid = self.get_argument("uid") | |
200 | result = None | |
201 | ||
202 | if not uid: | |
203 | result = "empty" | |
204 | ||
205 | # Check if the username is syntactically valid | |
206 | elif not self.backend.accounts.uid_is_valid(uid): | |
207 | result = "invalid" | |
208 | ||
209 | # Check if the username is already taken | |
210 | elif self.backend.accounts.uid_exists(uid): | |
211 | result = "taken" | |
212 | ||
213 | # Username seems to be okay | |
214 | self.finish({ "result" : result or "ok" }) | |
215 | ||
216 | ||
217 | class APICheckEmail(base.APIHandler): | |
218 | @base.ratelimit(minutes=1, requests=100) | |
219 | def get(self): | |
220 | email = self.get_argument("email") | |
221 | result = None | |
222 | ||
223 | if not email: | |
224 | result = "empty" | |
225 | ||
226 | elif not self.backend.accounts.mail_is_valid(email): | |
227 | result = "invalid" | |
228 | ||
229 | # Check if this email address is blacklisted | |
230 | elif self.backend.accounts.mail_is_blacklisted(email): | |
231 | result = "blacklisted" | |
232 | ||
233 | # Check if this email address is already useed | |
234 | elif self.backend.accounts.get_by_mail(email): | |
235 | result = "taken" | |
236 | ||
237 | self.finish({ "result" : result or "ok" }) |