If the user hasn't verified their email or account inactive give a special warning
[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
db1a438f 157 user.save()
fe80cb06 158 messages.add_message(
7b31a11c
CAW
159 request,
160 messages.SUCCESS,
4b1adc13
CAW
161 _("Your email address has been verified. "
162 "You may now login, edit your profile, and submit images!"))
db1a438f 163 else:
4b1adc13
CAW
164 messages.add_message(
165 request,
166 messages.ERROR,
167 _('The verification key or user id is incorrect'))
7b31a11c 168
269943a6
CAW
169 return redirect(
170 request, 'mediagoblin.user_pages.user_home',
788272f3 171 user=user['username'])
28afb47c 172
5866d1a8 173
b93a6a22
AM
174def resend_activation(request):
175 """
176 The reactivation view
177
178 Resend the activation email.
179 """
a77d952a
CAW
180 request.user['verification_key'] = unicode(uuid.uuid4())
181 request.user.save()
b93a6a22 182
02d80437 183 send_verification_email(request.user, request)
b93a6a22 184
61927e6e
CAW
185 messages.add_message(
186 request,
187 messages.INFO,
4b1adc13 188 _('Resent your verification email.'))
61927e6e
CAW
189 return redirect(
190 request, 'mediagoblin.user_pages.user_home',
191 user=request.user['username'])
25ba955e
AV
192
193
194def forgot_password(request):
195 """
196 Forgot password view
197
198 Sends an email whit an url to renew forgoten password
199 """
200 fp_form = auth_forms.ForgotPassForm(request.POST)
201
202 if request.method == 'POST' and fp_form.validate():
d1a64326
CAW
203 # '$or' not available till mongodb 1.5.3
204 user = request.db.User.find_one(
205 {'username': request.POST['username']})
206 if not user:
207 user = request.db.User.find_one(
208 {'email': request.POST['username']})
25ba955e 209
24966c43 210 if user:
a85a2110
CAW
211 if user['email_verified'] and user['status'] == 'active':
212 user['fp_verification_key'] = unicode(uuid.uuid4())
213 user['fp_token_expire'] = datetime.datetime.now() + \
214 datetime.timedelta(days=10)
215 user.save()
216
217 send_fp_verification_email(user, request)
218 else:
219 # special case... we can't send the email because the
220 # username is inactive / hasn't verified their email
221 messages.add_message(
222 request,
223 messages.WARNING,
224 _("Could not send password recovery email as "
225 "your username is inactive or your account's "
226 "email address has not been verified."))
227
228 return redirect(
229 request, 'mediagoblin.user_pages.user_home',
230 user=user['username'])
25ba955e 231
25ba955e 232
24966c43
CFD
233 # do not reveal whether or not there is a matching user, just move along
234 return redirect(request, 'mediagoblin.auth.fp_email_sent')
25ba955e
AV
235
236 return render_to_response(
2c9e8184
CAW
237 request,
238 'mediagoblin/auth/forgot_password.html',
239 {'fp_form': fp_form})
25ba955e
AV
240
241
242def verify_forgot_password(request):
8d1c9863
CFD
243 # get session variables, and specifically check for presence of token
244 mysession = _process_for_token(request)
4bcaf9f3 245 if not mysession['has_userid_and_token']:
8d1c9863
CFD
246 return render_404(request)
247
248 session_token = mysession['vars']['token']
249 session_userid = mysession['vars']['userid']
250 session_vars = mysession['vars']
251
252 # check if it's a valid Id
253 try:
254 user = request.db.User.find_one(
255 {'_id': ObjectId(unicode(session_userid))})
256 except InvalidId:
257 return render_404(request)
258
259 # check if we have a real user and correct token
73fffbb8
CAW
260 if ((user and user['fp_verification_key'] and
261 user['fp_verification_key'] == unicode(session_token) and
a85a2110
CAW
262 datetime.datetime.now() < user['fp_token_expire']
263 and user['email_verified'] and user['status'] == 'active')):
73fffbb8 264
8d1c9863
CFD
265 cp_form = auth_forms.ChangePassForm(session_vars)
266
267 if request.method == 'POST' and cp_form.validate():
268 user['pw_hash'] = auth_lib.bcrypt_gen_password_hash(
269 request.POST['password'])
270 user['fp_verification_key'] = None
271 user['fp_token_expire'] = None
272 user.save()
273
274 return redirect(request, 'mediagoblin.auth.fp_changed_success')
25ba955e 275 else:
73fffbb8
CAW
276 return render_to_response(
277 request,
278 'mediagoblin/auth/change_fp.html',
279 {'cp_form': cp_form})
280
8d1c9863
CFD
281 # in case there is a valid id but no user whit that id in the db
282 # or the token expired
283 else:
284 return render_404(request)
285
286
287def _process_for_token(request):
288 """
289 Checks for tokens in session without prior knowledge of request method
290
291 For now, returns whether the userid and token session variables exist, and
292 the session variables in a hash. Perhaps an object is warranted?
293 """
294 # retrieve the session variables
295 if request.method == 'GET':
296 session_vars = request.GET
297 else:
298 session_vars = request.POST
299
2c9e8184
CAW
300 mysession = {
301 'vars': session_vars,
302 'has_userid_and_token':
303 session_vars.has_key('userid') and session_vars.has_key('token')}
304
8d1c9863 305 return mysession