]> git.ipfire.org Git - ipfire.org.git/blob - src/web/auth.py
cookie: Set secure attribute
[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("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" })