Include Airy theme by default
[mediagoblin.git] / mediagoblin / auth / views.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
24181820
CAW
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
a77d952a 17import uuid
25ba955e 18import datetime
a77d952a 19
1c63ad5d 20from webob import exc
24181820 21
cfe46f3e 22from mediagoblin import messages
13677ef9 23from mediagoblin import mg_globals
152a3bfa 24from mediagoblin.tools.response import render_to_response, redirect, render_404
ae3bc7fa 25from mediagoblin.tools.translate import pass_to_ugettext as _
25ba955e 26from mediagoblin.db.util import ObjectId, InvalidId
24181820
CAW
27from mediagoblin.auth import lib as auth_lib
28from mediagoblin.auth import forms as auth_forms
25ba955e
AV
29from mediagoblin.auth.lib import send_verification_email, \
30 send_fp_verification_email
24181820
CAW
31
32
bf33272f
E
33def email_debug_message(request):
34 """
35 If the server is running in email debug mode (which is
36 the current default), give a debug message to the user
37 so that they have an idea where to find their email.
38 """
39 if mg_globals.app_config['email_debug_mode']:
40 # DEBUG message, no need to translate
41 messages.add_message(request, messages.DEBUG,
42 u"This instance is running in email debug mode. "
43 u"The email will be on the console of the server process.")
44
45
24181820
CAW
46def register(request):
47 """
48 Your classic registration view!
49 """
13677ef9
RL
50 # Redirects to indexpage if registrations are disabled
51 if not mg_globals.app_config["allow_registration"]:
166dc91a
CAW
52 messages.add_message(
53 request,
54 messages.WARNING,
4b1adc13 55 _('Sorry, registration is disabled on this instance.'))
13677ef9
RL
56 return redirect(request, "index")
57
24181820
CAW
58 register_form = auth_forms.RegistrationForm(request.POST)
59
60 if request.method == 'POST' and register_form.validate():
61 # TODO: Make sure the user doesn't exist already
08750772 62 username = unicode(request.POST['username'].lower())
53280164
E
63 em_user, em_dom = unicode(request.POST['email']).split("@", 1)
64 em_dom = em_dom.lower()
65 email = em_user + "@" + em_dom
dc49cf60 66 users_with_username = request.db.User.find(
08750772 67 {'username': username}).count()
0bf099d7 68 users_with_email = request.db.User.find(
08750772 69 {'email': email}).count()
24181820 70
9f6ea475
CAW
71 extra_validation_passes = True
72
24181820
CAW
73 if users_with_username:
74 register_form.username.errors.append(
4b1adc13 75 _(u'Sorry, a user with that name already exists.'))
9f6ea475
CAW
76 extra_validation_passes = False
77 if users_with_email:
0bf099d7 78 register_form.email.errors.append(
5ab3855e 79 _(u'Sorry, a user with that email address already exists.'))
9f6ea475 80 extra_validation_passes = False
24181820 81
9f6ea475 82 if extra_validation_passes:
24181820 83 # Create the user
0bc03620 84 user = request.db.User()
5a4e3ff1 85 user.username = username
809cbfc5 86 user.email = email
9047b254 87 user.pw_hash = auth_lib.bcrypt_gen_password_hash(
24181820 88 request.POST['password'])
479e8a83 89 user.verification_key = unicode(uuid.uuid4())
0bc03620
CAW
90 user.save(validate=True)
91
f73f4c4b 92 # log the user in
eabe6b67 93 request.session['user_id'] = unicode(user._id)
f73f4c4b
CAW
94 request.session.save()
95
96 # send verification email
bf33272f 97 email_debug_message(request)
0bc03620
CAW
98 send_verification_email(user, request)
99
dce5c9cb
CAW
100 # redirect the user to their homepage... there will be a
101 # message waiting for them to verify their email
0bc03620
CAW
102 return redirect(
103 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 104 user=user.username)
24181820 105
9038c9f9
CAW
106 return render_to_response(
107 request,
c9c24934
E
108 'mediagoblin/auth/register.html',
109 {'register_form': register_form})
24181820
CAW
110
111
692fd1c9 112def login(request):
a3776717 113 """
8e1e744d 114 MediaGoblin login view.
a3776717
CAW
115
116 If you provide the POST with 'next', it'll redirect to that view.
117 """
692fd1c9
CAW
118 login_form = auth_forms.LoginForm(request.POST)
119
a3776717
CAW
120 login_failed = False
121
692fd1c9 122 if request.method == 'POST' and login_form.validate():
84812db5 123 user = request.db.User.find_one(
ce72a1bb 124 {'username': request.POST['username'].lower()})
692fd1c9 125
d1938963 126 if user and user.check_login(request.POST['password']):
692fd1c9 127 # set up login in session
eabe6b67 128 request.session['user_id'] = unicode(user._id)
a3776717 129 request.session.save()
692fd1c9 130
574d1511 131 if request.POST.get('next'):
a3776717
CAW
132 return exc.HTTPFound(location=request.POST['next'])
133 else:
9150244a 134 return redirect(request, "index")
692fd1c9
CAW
135
136 else:
137 # Prevent detecting who's on this system by testing login
138 # attempt timings
139 auth_lib.fake_login_attempt()
a3776717 140 login_failed = True
692fd1c9 141
9038c9f9
CAW
142 return render_to_response(
143 request,
c9c24934
E
144 'mediagoblin/auth/login.html',
145 {'login_form': login_form,
146 'next': request.GET.get('next') or request.POST.get('next'),
13bb1d67
RL
147 'login_failed': login_failed,
148 'allow_registration': mg_globals.app_config["allow_registration"]})
692fd1c9
CAW
149
150
151def logout(request):
b97232fa
CAW
152 # Maybe deleting the user_id parameter would be enough?
153 request.session.delete()
7b31a11c 154
9150244a 155 return redirect(request, "index")
db1a438f 156
5866d1a8 157
db1a438f 158def verify_email(request):
4c093e85
JW
159 """
160 Email verification view
161
162 validates GET parameters against database and unlocks the user account, if
163 you are lucky :)
164 """
155f24f9 165 # If we don't have userid and token parameters, we can't do anything; 404
285ffedd 166 if not 'userid' in request.GET or not 'token' in request.GET:
de12b4e7 167 return render_404(request)
155f24f9 168
db1a438f 169 user = request.db.User.find_one(
e0f84870 170 {'_id': ObjectId(unicode(request.GET['userid']))})
db1a438f 171
00bb9550 172 if user and user.verification_key == unicode(request.GET['token']):
7a3d00ec 173 user.status = u'active'
4facc7a0 174 user.email_verified = True
00bb9550 175 user.verification_key = None
daf02964 176
db1a438f 177 user.save()
daf02964 178
fe80cb06 179 messages.add_message(
7b31a11c
CAW
180 request,
181 messages.SUCCESS,
4b1adc13
CAW
182 _("Your email address has been verified. "
183 "You may now login, edit your profile, and submit images!"))
db1a438f 184 else:
4b1adc13
CAW
185 messages.add_message(
186 request,
187 messages.ERROR,
188 _('The verification key or user id is incorrect'))
7b31a11c 189
269943a6
CAW
190 return redirect(
191 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 192 user=user.username)
28afb47c 193
5866d1a8 194
b93a6a22
AM
195def resend_activation(request):
196 """
197 The reactivation view
198
199 Resend the activation email.
200 """
84a7e770 201
2fe69916 202 if request.user is None:
7903a14f
AW
203 messages.add_message(
204 request,
205 messages.ERROR,
2fe69916 206 _('You must be logged in so we know who to send the email to!'))
7903a14f 207
5dbeda8a 208 return redirect(request, 'mediagoblin.auth.login')
7903a14f 209
0ab21f98 210 if request.user.email_verified:
84a7e770
AW
211 messages.add_message(
212 request,
213 messages.ERROR,
2fe69916 214 _("You've already verified your email address!"))
84a7e770 215
2fe69916 216 return redirect(request, "mediagoblin.user_pages.user_home", user=request.user['username'])
84a7e770 217
00bb9550 218 request.user.verification_key = unicode(uuid.uuid4())
a77d952a 219 request.user.save()
84a7e770 220
bf33272f 221 email_debug_message(request)
02d80437 222 send_verification_email(request.user, request)
b93a6a22 223
61927e6e
CAW
224 messages.add_message(
225 request,
226 messages.INFO,
4b1adc13 227 _('Resent your verification email.'))
61927e6e
CAW
228 return redirect(
229 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 230 user=request.user.username)
25ba955e
AV
231
232
233def forgot_password(request):
234 """
235 Forgot password view
236
992e4f80 237 Sends an email with an url to renew forgotten password
25ba955e 238 """
952b97d5
JK
239 fp_form = auth_forms.ForgotPassForm(request.POST,
240 username=request.GET.get('username'))
25ba955e
AV
241
242 if request.method == 'POST' and fp_form.validate():
bf33272f 243
d1a64326
CAW
244 # '$or' not available till mongodb 1.5.3
245 user = request.db.User.find_one(
246 {'username': request.POST['username']})
247 if not user:
248 user = request.db.User.find_one(
249 {'email': request.POST['username']})
25ba955e 250
24966c43 251 if user:
7a3d00ec 252 if user.email_verified and user.status == 'active':
dc39e455 253 user.fp_verification_key = unicode(uuid.uuid4())
2d540fed 254 user.fp_token_expire = datetime.datetime.now() + \
a85a2110
CAW
255 datetime.timedelta(days=10)
256 user.save()
257
258 send_fp_verification_email(user, request)
992e4f80
JS
259
260 messages.add_message(
261 request,
262 messages.INFO,
263 _("An email has been sent with instructions on how to "
264 "change your password."))
265 email_debug_message(request)
266
a85a2110
CAW
267 else:
268 # special case... we can't send the email because the
269 # username is inactive / hasn't verified their email
270 messages.add_message(
271 request,
272 messages.WARNING,
273 _("Could not send password recovery email as "
274 "your username is inactive or your account's "
275 "email address has not been verified."))
276
277 return redirect(
278 request, 'mediagoblin.user_pages.user_home',
5a4e3ff1 279 user=user.username)
992e4f80
JS
280 return redirect(request, 'mediagoblin.auth.login')
281 else:
282 messages.add_message(
283 request,
284 messages.WARNING,
285 _("Couldn't find someone with that username or email."))
286 return redirect(request, 'mediagoblin.auth.forgot_password')
25ba955e
AV
287
288 return render_to_response(
2c9e8184
CAW
289 request,
290 'mediagoblin/auth/forgot_password.html',
291 {'fp_form': fp_form})
25ba955e
AV
292
293
294def verify_forgot_password(request):
961fe381
CAW
295 """
296 Check the forgot-password verification and possibly let the user
297 change their password because of it.
298 """
f7ab6670
CAW
299 # get form data variables, and specifically check for presence of token
300 formdata = _process_for_token(request)
301 if not formdata['has_userid_and_token']:
8d1c9863
CFD
302 return render_404(request)
303
f7ab6670
CAW
304 formdata_token = formdata['vars']['token']
305 formdata_userid = formdata['vars']['userid']
306 formdata_vars = formdata['vars']
8d1c9863
CFD
307
308 # check if it's a valid Id
309 try:
310 user = request.db.User.find_one(
f7ab6670 311 {'_id': ObjectId(unicode(formdata_userid))})
8d1c9863
CFD
312 except InvalidId:
313 return render_404(request)
314
315 # check if we have a real user and correct token
dc39e455
E
316 if ((user and user.fp_verification_key and
317 user.fp_verification_key == unicode(formdata_token) and
2d540fed 318 datetime.datetime.now() < user.fp_token_expire
7a3d00ec 319 and user.email_verified and user.status == 'active')):
73fffbb8 320
f7ab6670 321 cp_form = auth_forms.ChangePassForm(formdata_vars)
8d1c9863
CFD
322
323 if request.method == 'POST' and cp_form.validate():
9047b254 324 user.pw_hash = auth_lib.bcrypt_gen_password_hash(
8d1c9863 325 request.POST['password'])
dc39e455 326 user.fp_verification_key = None
2d540fed 327 user.fp_token_expire = None
8d1c9863
CFD
328 user.save()
329
35149b11
JS
330 messages.add_message(
331 request,
332 messages.INFO,
333 _("You can now log in using your new password."))
334 return redirect(request, 'mediagoblin.auth.login')
25ba955e 335 else:
73fffbb8
CAW
336 return render_to_response(
337 request,
338 'mediagoblin/auth/change_fp.html',
339 {'cp_form': cp_form})
340
8d1c9863
CFD
341 # in case there is a valid id but no user whit that id in the db
342 # or the token expired
343 else:
344 return render_404(request)
345
346
347def _process_for_token(request):
348 """
f7ab6670 349 Checks for tokens in formdata without prior knowledge of request method
8d1c9863 350
f7ab6670
CAW
351 For now, returns whether the userid and token formdata variables exist, and
352 the formdata variables in a hash. Perhaps an object is warranted?
8d1c9863 353 """
f7ab6670 354 # retrieve the formdata variables
8d1c9863 355 if request.method == 'GET':
f7ab6670 356 formdata_vars = request.GET
8d1c9863 357 else:
f7ab6670 358 formdata_vars = request.POST
8d1c9863 359
f7ab6670
CAW
360 formdata = {
361 'vars': formdata_vars,
2c9e8184 362 'has_userid_and_token':
285ffedd 363 'userid' in formdata_vars and 'token' in formdata_vars}
2c9e8184 364
f7ab6670 365 return formdata