donations: Remove organization step here because it makes everything so complicated
[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         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" })