Also nullify verification key after verifying in the email confirmation step
[mediagoblin.git] / mediagoblin / auth / views.py
CommitLineData
8e1e744d 1# GNU MediaGoblin -- federated, autonomous media hosting
24181820
CAW
2# Copyright (C) 2011 Free Software Foundation, Inc
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
de12b4e7 24from mediagoblin.util import render_to_response, redirect, render_404
4b1adc13 25from mediagoblin.util 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
33def register(request):
34 """
35 Your classic registration view!
36 """
13677ef9
RL
37 # Redirects to indexpage if registrations are disabled
38 if not mg_globals.app_config["allow_registration"]:
166dc91a
CAW
39 messages.add_message(
40 request,
41 messages.WARNING,
4b1adc13 42 _('Sorry, registration is disabled on this instance.'))
13677ef9
RL
43 return redirect(request, "index")
44
24181820
CAW
45 register_form = auth_forms.RegistrationForm(request.POST)
46
47 if request.method == 'POST' and register_form.validate():
48 # TODO: Make sure the user doesn't exist already
ce72a1bb 49
dc49cf60
CAW
50 users_with_username = request.db.User.find(
51 {'username': request.POST['username'].lower()}).count()
0bf099d7
AV
52 users_with_email = request.db.User.find(
53 {'email': request.POST['email'].lower()}).count()
24181820 54
9f6ea475
CAW
55 extra_validation_passes = True
56
24181820
CAW
57 if users_with_username:
58 register_form.username.errors.append(
4b1adc13 59 _(u'Sorry, a user with that name already exists.'))
9f6ea475
CAW
60 extra_validation_passes = False
61 if users_with_email:
0bf099d7
AV
62 register_form.email.errors.append(
63 _(u'Sorry, that email address has already been taken.'))
9f6ea475 64 extra_validation_passes = False
24181820 65
9f6ea475 66 if extra_validation_passes:
24181820 67 # Create the user
0bc03620
CAW
68 user = request.db.User()
69 user['username'] = request.POST['username'].lower()
873e4e9d 70 user['email'] = request.POST['email'].lower()
0bc03620 71 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
24181820 72 request.POST['password'])
0bc03620
CAW
73 user.save(validate=True)
74
f73f4c4b
CAW
75 # log the user in
76 request.session['user_id'] = unicode(user['_id'])
77 request.session.save()
78
79 # send verification email
0bc03620
CAW
80 send_verification_email(user, request)
81
dce5c9cb
CAW
82 # redirect the user to their homepage... there will be a
83 # message waiting for them to verify their email
0bc03620
CAW
84 return redirect(
85 request, 'mediagoblin.user_pages.user_home',
86 user=user['username'])
24181820 87
9038c9f9
CAW
88 return render_to_response(
89 request,
c9c24934
E
90 'mediagoblin/auth/register.html',
91 {'register_form': register_form})
24181820
CAW
92
93
692fd1c9 94def login(request):
a3776717 95 """
8e1e744d 96 MediaGoblin login view.
a3776717
CAW
97
98 If you provide the POST with 'next', it'll redirect to that view.
99 """
692fd1c9
CAW
100 login_form = auth_forms.LoginForm(request.POST)
101
a3776717
CAW
102 login_failed = False
103
692fd1c9 104 if request.method == 'POST' and login_form.validate():
b058cf15 105 user = request.db.User.one(
ce72a1bb 106 {'username': request.POST['username'].lower()})
692fd1c9 107
d1938963 108 if user and user.check_login(request.POST['password']):
692fd1c9
CAW
109 # set up login in session
110 request.session['user_id'] = unicode(user['_id'])
a3776717 111 request.session.save()
692fd1c9 112
574d1511 113 if request.POST.get('next'):
a3776717
CAW
114 return exc.HTTPFound(location=request.POST['next'])
115 else:
9150244a 116 return redirect(request, "index")
692fd1c9
CAW
117
118 else:
119 # Prevent detecting who's on this system by testing login
120 # attempt timings
121 auth_lib.fake_login_attempt()
a3776717 122 login_failed = True
692fd1c9 123
9038c9f9
CAW
124 return render_to_response(
125 request,
c9c24934
E
126 'mediagoblin/auth/login.html',
127 {'login_form': login_form,
128 'next': request.GET.get('next') or request.POST.get('next'),
13bb1d67
RL
129 'login_failed': login_failed,
130 'allow_registration': mg_globals.app_config["allow_registration"]})
692fd1c9
CAW
131
132
133def logout(request):
b97232fa
CAW
134 # Maybe deleting the user_id parameter would be enough?
135 request.session.delete()
7b31a11c 136
9150244a 137 return redirect(request, "index")
db1a438f 138
5866d1a8 139
db1a438f 140def verify_email(request):
4c093e85
JW
141 """
142 Email verification view
143
144 validates GET parameters against database and unlocks the user account, if
145 you are lucky :)
146 """
155f24f9
CAW
147 # If we don't have userid and token parameters, we can't do anything; 404
148 if not request.GET.has_key('userid') or not request.GET.has_key('token'):
de12b4e7 149 return render_404(request)
155f24f9 150
db1a438f 151 user = request.db.User.find_one(
e0f84870 152 {'_id': ObjectId(unicode(request.GET['userid']))})
db1a438f 153
155f24f9 154 if user and user['verification_key'] == unicode(request.GET['token']):
db1a438f
JW
155 user['status'] = u'active'
156 user['email_verified'] = True
daf02964
CAW
157 user[u'verification_key'] = None
158
db1a438f 159 user.save()
daf02964 160
fe80cb06 161 messages.add_message(
7b31a11c
CAW
162 request,
163 messages.SUCCESS,
4b1adc13
CAW
164 _("Your email address has been verified. "
165 "You may now login, edit your profile, and submit images!"))
db1a438f 166 else:
4b1adc13
CAW
167 messages.add_message(
168 request,
169 messages.ERROR,
170 _('The verification key or user id is incorrect'))
7b31a11c 171
269943a6
CAW
172 return redirect(
173 request, 'mediagoblin.user_pages.user_home',
788272f3 174 user=user['username'])
28afb47c 175
5866d1a8 176
b93a6a22
AM
177def resend_activation(request):
178 """
179 The reactivation view
180
181 Resend the activation email.
182 """
a77d952a
CAW
183 request.user['verification_key'] = unicode(uuid.uuid4())
184 request.user.save()
b93a6a22 185
02d80437 186 send_verification_email(request.user, request)
b93a6a22 187
61927e6e
CAW
188 messages.add_message(
189 request,
190 messages.INFO,
4b1adc13 191 _('Resent your verification email.'))
61927e6e
CAW
192 return redirect(
193 request, 'mediagoblin.user_pages.user_home',
194 user=request.user['username'])
25ba955e
AV
195
196
197def forgot_password(request):
198 """
199 Forgot password view
200
201 Sends an email whit an url to renew forgoten password
202 """
203 fp_form = auth_forms.ForgotPassForm(request.POST)
204
205 if request.method == 'POST' and fp_form.validate():
d1a64326
CAW
206 # '$or' not available till mongodb 1.5.3
207 user = request.db.User.find_one(
208 {'username': request.POST['username']})
209 if not user:
210 user = request.db.User.find_one(
211 {'email': request.POST['username']})
25ba955e 212
24966c43 213 if user:
a85a2110
CAW
214 if user['email_verified'] and user['status'] == 'active':
215 user['fp_verification_key'] = unicode(uuid.uuid4())
216 user['fp_token_expire'] = datetime.datetime.now() + \
217 datetime.timedelta(days=10)
218 user.save()
219
220 send_fp_verification_email(user, request)
221 else:
222 # special case... we can't send the email because the
223 # username is inactive / hasn't verified their email
224 messages.add_message(
225 request,
226 messages.WARNING,
227 _("Could not send password recovery email as "
228 "your username is inactive or your account's "
229 "email address has not been verified."))
230
231 return redirect(
232 request, 'mediagoblin.user_pages.user_home',
233 user=user['username'])
25ba955e 234
25ba955e 235
24966c43
CFD
236 # do not reveal whether or not there is a matching user, just move along
237 return redirect(request, 'mediagoblin.auth.fp_email_sent')
25ba955e
AV
238
239 return render_to_response(
2c9e8184
CAW
240 request,
241 'mediagoblin/auth/forgot_password.html',
242 {'fp_form': fp_form})
25ba955e
AV
243
244
245def verify_forgot_password(request):
8d1c9863
CFD
246 # get session variables, and specifically check for presence of token
247 mysession = _process_for_token(request)
4bcaf9f3 248 if not mysession['has_userid_and_token']:
8d1c9863
CFD
249 return render_404(request)
250
251 session_token = mysession['vars']['token']
252 session_userid = mysession['vars']['userid']
253 session_vars = mysession['vars']
254
255 # check if it's a valid Id
256 try:
257 user = request.db.User.find_one(
258 {'_id': ObjectId(unicode(session_userid))})
259 except InvalidId:
260 return render_404(request)
261
262 # check if we have a real user and correct token
73fffbb8
CAW
263 if ((user and user['fp_verification_key'] and
264 user['fp_verification_key'] == unicode(session_token) and
a85a2110
CAW
265 datetime.datetime.now() < user['fp_token_expire']
266 and user['email_verified'] and user['status'] == 'active')):
73fffbb8 267
8d1c9863
CFD
268 cp_form = auth_forms.ChangePassForm(session_vars)
269
270 if request.method == 'POST' and cp_form.validate():
271 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
272 request.POST['password'])
273 user['fp_verification_key'] = None
274 user['fp_token_expire'] = None
275 user.save()
276
277 return redirect(request, 'mediagoblin.auth.fp_changed_success')
25ba955e 278 else:
73fffbb8
CAW
279 return render_to_response(
280 request,
281 'mediagoblin/auth/change_fp.html',
282 {'cp_form': cp_form})
283
8d1c9863
CFD
284 # in case there is a valid id but no user whit that id in the db
285 # or the token expired
286 else:
287 return render_404(request)
288
289
290def _process_for_token(request):
291 """
292 Checks for tokens in session without prior knowledge of request method
293
294 For now, returns whether the userid and token session variables exist, and
295 the session variables in a hash. Perhaps an object is warranted?
296 """
297 # retrieve the session variables
298 if request.method == 'GET':
299 session_vars = request.GET
300 else:
301 session_vars = request.POST
302
2c9e8184
CAW
303 mysession = {
304 'vars': session_vars,
305 'has_userid_and_token':
306 session_vars.has_key('userid') and session_vars.has_key('token')}
307
8d1c9863 308 return mysession