]>
Commit | Line | Data |
---|---|---|
08df6527 MT |
1 | #!/usr/bin/python |
2 | ||
3 | import logging | |
4 | import tornado.web | |
5 | ||
124a8404 | 6 | from . import base |
08df6527 | 7 | |
170b63ba MT |
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): | |
d8a15b2e | 18 | def login(self, account): |
08df6527 | 19 | # User has logged in, create a session |
906e1e6a MT |
20 | session_id, session_expires = self.backend.accounts.create_session( |
21 | account, self.request.host) | |
08df6527 MT |
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 | ||
906e1e6a | 36 | success = self.backend.accounts.destroy_session(session_id, self.request.host) |
08df6527 MT |
37 | if success: |
38 | self.clear_cookie("session_id") | |
39 | ||
40 | ||
08df6527 | 41 | class LoginHandler(AuthenticationMixin, base.BaseHandler): |
cfe7d74c | 42 | @base.blacklisted |
08df6527 MT |
43 | def get(self): |
44 | next = self.get_argument("next", None) | |
45 | ||
a76ac21e MT |
46 | self.render("auth/login.html", next=next, |
47 | incorrect=False, username=None) | |
08df6527 | 48 | |
cfe7d74c | 49 | @base.blacklisted |
372ef119 | 50 | @base.ratelimit(minutes=60, requests=5) |
08df6527 MT |
51 | def post(self): |
52 | username = self.get_argument("username") | |
53 | password = self.get_argument("password") | |
a76ac21e | 54 | next = self.get_argument("next", "/") |
08df6527 | 55 | |
328a7710 MT |
56 | # Find user |
57 | account = self.backend.accounts.auth(username, password) | |
58 | if not account: | |
a76ac21e MT |
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 | ) | |
328a7710 MT |
68 | |
69 | # Create session | |
08df6527 | 70 | with self.db.transaction(): |
328a7710 | 71 | self.login(account) |
08df6527 | 72 | |
a76ac21e MT |
73 | # Redirect the user |
74 | return self.redirect(next) | |
08df6527 MT |
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("/") | |
9b8ff27d MT |
84 | |
85 | ||
f32dd17f MT |
86 | class RegisterHandler(base.BaseHandler): |
87 | @base.blacklisted | |
88 | def get(self): | |
d521c9df MT |
89 | # Redirect logged in users away |
90 | if self.current_user: | |
91 | self.redirect("/") | |
92 | ||
f32dd17f MT |
93 | self.render("auth/register.html") |
94 | ||
372ef119 | 95 | @base.ratelimit(minutes=24*60, requests=5) |
9fdf4fb7 | 96 | async def post(self): |
f32dd17f MT |
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 | ||
23f84bbc | 103 | # Check if this is a spam account |
9fdf4fb7 | 104 | is_spam = await self.backend.accounts.check_spam(uid, email, |
23f84bbc MT |
105 | address=self.get_remote_ip()) |
106 | ||
107 | if is_spam: | |
108 | self.render("auth/register-spam.html") | |
109 | return | |
110 | ||
f32dd17f | 111 | # Register account |
f2ba8a1f MT |
112 | try: |
113 | with self.db.transaction(): | |
114 | self.backend.accounts.register(uid, email, | |
757372cd MT |
115 | first_name=first_name, last_name=last_name, |
116 | country_code=self.current_country_code) | |
f2ba8a1f | 117 | except ValueError as e: |
09c67399 | 118 | raise tornado.web.HTTPError(400, "%s" % e) from e |
f32dd17f MT |
119 | |
120 | self.render("auth/register-success.html") | |
121 | ||
122 | ||
d8a15b2e MT |
123 | class ActivateHandler(AuthenticationMixin, base.BaseHandler): |
124 | def get(self, uid, activation_code): | |
b4d72c76 | 125 | self.render("auth/activate.html") |
d8a15b2e MT |
126 | |
127 | def post(self, uid, activation_code): | |
b4d72c76 MT |
128 | password1 = self.get_argument("password1") |
129 | password2 = self.get_argument("password2") | |
d8a15b2e | 130 | |
b4d72c76 MT |
131 | if not password1 == password2: |
132 | raise tornado.web.HTTPError(400, "Passwords do not match") | |
d8a15b2e | 133 | |
b4d72c76 MT |
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) | |
d8a15b2e | 138 | |
b4d72c76 MT |
139 | # Set the new password |
140 | account.passwd(password1) | |
d8a15b2e | 141 | |
b4d72c76 MT |
142 | # Create session |
143 | self.login(account) | |
d8a15b2e | 144 | |
b00cc400 MT |
145 | # Redirect to success page |
146 | self.render("auth/activated.html", account=account) | |
689effd0 MT |
147 | |
148 | ||
391ede9e | 149 | class PasswordResetInitiationHandler(base.BaseHandler): |
c7594d58 MT |
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(): | |
391ede9e | 163 | account.request_password_reset() |
c7594d58 MT |
164 | |
165 | self.render("auth/password-reset-successful.html") | |
166 | ||
167 | ||
391ede9e MT |
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 | ||
689effd0 MT |
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" }) |