]> git.ipfire.org Git - ipfire.org.git/blob - src/web/auth.py
56f4b32f211a4aa9ecfade330f5a3386250f3af5
[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(base.BaseHandler):
85 def get(self):
86 # Redirect logged in users away
87 if self.current_user:
88 self.redirect("/")
89
90 self.render("auth/register.html")
91
92 @base.ratelimit(minutes=15, requests=5)
93 async def post(self):
94 uid = self.get_argument("uid")
95 email = self.get_argument("email")
96
97 first_name = self.get_argument("first_name")
98 last_name = self.get_argument("last_name")
99
100 # Check if this is a spam account
101 is_spam = await self.backend.accounts.check_spam(uid, email,
102 address=self.get_remote_ip())
103
104 if is_spam:
105 self.render("auth/register-spam.html")
106 return
107
108 # Register account
109 try:
110 with self.db.transaction():
111 self.backend.accounts.register(uid, email,
112 first_name=first_name, last_name=last_name,
113 country_code=self.current_country_code)
114 except ValueError as e:
115 raise tornado.web.HTTPError(400, "%s" % e) from e
116
117 self.render("auth/register-success.html")
118
119
120 class ActivateHandler(AuthenticationMixin, base.BaseHandler):
121 def get(self, uid, activation_code):
122 self.render("auth/activate.html")
123
124 def post(self, uid, activation_code):
125 password1 = self.get_argument("password1")
126 password2 = self.get_argument("password2")
127
128 if not password1 == password2:
129 raise tornado.web.HTTPError(400, "Passwords do not match")
130
131 with self.db.transaction():
132 account = self.backend.accounts.activate(uid, activation_code)
133 if not account:
134 raise tornado.web.HTTPError(400, "Account not found: %s" % uid)
135
136 # Set the new password
137 account.passwd(password1)
138
139 # Create session
140 self.login(account)
141
142 # Redirect to success page
143 self.render("auth/activated.html", account=account)
144
145
146 class PasswordResetInitiationHandler(base.BaseHandler):
147 def get(self):
148 username = self.get_argument("username", None)
149
150 self.render("auth/password-reset-initiation.html", username=username)
151
152 @base.ratelimit(minutes=15, requests=10)
153 def post(self):
154 username = self.get_argument("username")
155
156 # Fetch account and submit password reset
157 account = self.backend.accounts.get_by_uid(username)
158 if account:
159 with self.db.transaction():
160 account.request_password_reset()
161
162 self.render("auth/password-reset-successful.html")
163
164
165 class PasswordResetHandler(AuthenticationMixin, base.BaseHandler):
166 def get(self, uid, reset_code):
167 account = self.backend.accounts.get_by_uid(uid)
168 if not account:
169 raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
170
171 self.render("auth/password-reset.html", account=account)
172
173 def post(self, uid, reset_code):
174 account = self.backend.accounts.get_by_uid(uid)
175 if not account:
176 raise tornado.web.HTTPError(404, "Could not find account: %s" % uid)
177
178 password1 = self.get_argument("password1")
179 password2 = self.get_argument("password2")
180
181 if not password1 == password2:
182 raise tornado.web.HTTPError(400, "Passwords do not match")
183
184 # Try to perform password reset
185 with self.db.transaction():
186 account.reset_password(reset_code, password1)
187
188 # Login the user straight away after reset was successful
189 self.login(account)
190
191 # Redirect back to /
192 self.redirect("/")
193
194
195 class APICheckUID(base.APIHandler):
196 @base.ratelimit(minutes=5, requests=100)
197 def get(self):
198 uid = self.get_argument("uid")
199 result = None
200
201 if not uid:
202 result = "empty"
203
204 # Check if the username is syntactically valid
205 elif not self.backend.accounts.uid_is_valid(uid):
206 result = "invalid"
207
208 # Check if the username is already taken
209 elif self.backend.accounts.uid_exists(uid):
210 result = "taken"
211
212 # Username seems to be okay
213 self.finish({ "result" : result or "ok" })