Skip to content

Commit aaa91e9

Browse files
committed
In order to validate the email address of the user provided during registration, a confirmation email with activation link is sent to
the email address and when the user clicks the link, the email address is verified and the account gets activated. fixes #360
1 parent 6a3d298 commit aaa91e9

7 files changed

Lines changed: 77 additions & 16 deletions

File tree

applications/home/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@
1414
url(r'^about_us/$', views.about_us_page, name='about_us'),
1515
url(r'^forgot_password/$', views.forgot_password_page, name='forgot_password'),
1616
url(r'^reset_password/$', views.reset_password_page, name='reset_password'),
17+
url(r'^activate_account/$', views.activate_account_page, name='activate_account'),
18+
1719
]

applications/home/views.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from django.template import RequestContext
77
from graphspace.utils import *
88
from graphspace.exceptions import *
9+
from graphspace.utils import generate_uid
910

1011

1112
def home_page(request):
@@ -225,12 +226,14 @@ def login(request):
225226
request_body = json.loads(request.body)
226227
user = users.authenticate_user(request, username=request_body['user_id'], password=request_body['pw'])
227228

228-
if user is not None:
229+
if user is not None and user['user_account_status'] == 1:
229230
request.session['uid'] = user['user_id']
230231
request.session['admin'] = user['admin']
231232
return HttpResponse(
232233
json.dumps(json_success_response(200, message='%s, Welcome to GraphSpace!' % user['user_id'])),
233234
content_type="application/json")
235+
elif user is not None and user['user_account_status'] is not 1:
236+
raise ValidationError(request, ErrorCodes.Validation.UserUnVerified)
234237
else:
235238
raise ValidationError(request, ErrorCodes.Validation.UserPasswordMisMatch)
236239
else:
@@ -252,20 +255,42 @@ def register(request):
252255
# RegisterForm is bound to POST data
253256
register_form = RegisterForm(request_body)
254257
if register_form.is_valid():
258+
token = generate_uid()
255259
user = users.register(request, username=register_form.cleaned_data['user_id'],
256-
password=register_form.cleaned_data['password'])
257-
if user is not None:
258-
request.session['uid'] = user.email
259-
request.session['admin'] = user.is_admin
260+
password=register_form.cleaned_data['password'], user_account_status=0, email_confirmation_code=token)
260261

261-
return HttpResponse(json.dumps(json_success_response(200, message='Registered!')),
262-
content_type="application/json")
262+
users.send_confirmation_email(request, request_body['user_id'], token)
263+
return HttpResponse(json.dumps(json_success_response(200, message='A verification link has been sent to your email account.'+
264+
'Please click on the link to verify your email and continue '+
265+
'the registration process.')),
266+
content_type="application/json")
263267
else:
264268
raise BadRequest(request)
265269
else:
266270
raise MethodNotAllowed(request) # Handle other type of request methods like GET, PUT, UPDATE.
267271

268272

273+
def activate_account_page(request):
274+
"""
275+
Activate a user account
276+
277+
:param request: HTTP GET Request containing:
278+
279+
{"activation_code": <activation_code>}
280+
"""
281+
282+
if 'GET' == request.method:
283+
user = users.get_email_confirmation_code(request, request.GET.get('activation_code', None))
284+
users.update_user(request, user.id, user_account_status=1)
285+
if user is not None:
286+
request.session['uid'] = user.email
287+
request.session['admin'] = user.is_admin
288+
return HttpResponseRedirect('/')
289+
290+
else:
291+
raise MethodNotAllowed(request) # Handle other type of request methods like GET, PUT, UPDATE.
292+
293+
269294
def logout(request):
270295
"""
271296
Log the user out and display logout page.

applications/users/controllers.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,23 @@ def authenticate_user(request, username=None, password=None):
3030
'id': user.id,
3131
'user_id': user.email,
3232
'password': user.password,
33-
'admin': user.is_admin
33+
'admin': user.is_admin,
34+
'user_account_status':user.user_account_status
3435
}
3536
else:
3637
return None
3738

3839

39-
def update_user(request, user_id, email=None, password=None, is_admin=None):
40+
def update_user(request, user_id, email=None, password=None, is_admin=None, user_account_status=None):
4041
user = {}
4142
if email is not None:
4243
user['email'] = email
4344
if password is not None:
4445
user['password'] = bcrypt.hashpw(password, bcrypt.gensalt())
4546
if is_admin is not None:
4647
user['is_admin'] = is_admin
48+
if user_account_status is not None:
49+
user['user_account_status'] = user_account_status
4750

4851
return db.update_user(request.db_session, id=user_id, updated_user=user)
4952

@@ -126,14 +129,15 @@ def search_users(request, email=None, limit=20, offset=0, order='desc', sort='na
126129
return total, users
127130

128131

129-
def register(request, username=None, password=None):
132+
def register(request, username=None, password=None, user_account_status=None, email_confirmation_code=None):
130133
if db.get_user(request.db_session, username):
131134
raise BadRequest(request, error_code=ErrorCodes.Validation.UserAlreadyExists, args=username)
132135

133-
return add_user(request, email=username, password=password)
136+
return add_user(request, email=username, password=password, user_account_status=user_account_status,
137+
email_confirmation_code=email_confirmation_code)
134138

135139

136-
def add_user(request, email=None, password="graphspace_public_user", is_admin=0):
140+
def add_user(request, email=None, password="graphspace_public_user", is_admin=0, user_account_status=None, email_confirmation_code=None):
137141
"""
138142
Add a new user. If email and password is not passed, it will create a user with default values.
139143
By default a user has no admin access.
@@ -146,7 +150,8 @@ def add_user(request, email=None, password="graphspace_public_user", is_admin=0)
146150
"""
147151
email = "public_user_%s@graphspace.com" % generate_uid(size=10) if email is None else email
148152

149-
return db.add_user(request.db_session, email=email, password=bcrypt.hashpw(password, bcrypt.gensalt()), is_admin=is_admin)
153+
return db.add_user(request.db_session, email=email, password=bcrypt.hashpw(password, bcrypt.gensalt()), is_admin=is_admin,
154+
user_account_status=user_account_status, email_confirmation_code=email_confirmation_code)
150155

151156

152157
def is_member_of_group(request, username, group_id):
@@ -290,6 +295,9 @@ def delete_group_graph(request, group_id, graph_id):
290295
def get_password_reset_by_code(request, code):
291296
return db.get_password_reset_by_code(request.db_session, code)
292297

298+
def get_email_confirmation_code(request, code):
299+
return db.get_email_confirmation_code(request.db_session, code)
300+
293301

294302
def delete_password_reset_code(request, id):
295303
return db.delete_password_reset(request.db_session, id)
@@ -312,3 +320,11 @@ def send_password_reset_email(request, password_reset_code):
312320
email_from = "GraphSpace Admin"
313321

314322
return send_mail(mail_title, message, email_from, [password_reset_code.email], fail_silently=False)
323+
324+
def send_confirmation_email(request, email, token):
325+
# Construct email message
326+
mail_title = 'Activate your account for GraphSpace!'
327+
message = 'Please confirm your email address to complete the registration ' + settings.URL_PATH + 'activate_account/?activation_code=' + token
328+
email_from = "GraphSpace Admin"
329+
330+
return send_mail(mail_title, message, email_from, [email], fail_silently=False)

applications/users/dal.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313

1414
@with_session
15-
def add_user(db_session, email, password, is_admin):
15+
def add_user(db_session, email, password, is_admin, user_account_status, email_confirmation_code):
1616
"""
1717
Add a new user.
1818
@@ -22,7 +22,8 @@ def add_user(db_session, email, password, is_admin):
2222
:param admin: 1 if user has admin access else 0.
2323
:return: User
2424
"""
25-
user = User(email=email, password=password, is_admin = is_admin)
25+
user = User(email=email, password=password, is_admin = is_admin,
26+
user_account_status=user_account_status, email_confirmation_code=email_confirmation_code)
2627
db_session.add(user)
2728
return user
2829

@@ -113,6 +114,15 @@ def get_password_reset_by_code(db_session, code):
113114
"""
114115
return db_session.query(PasswordResetCode).filter(PasswordResetCode.code == code).one_or_none()
115116

117+
@with_session
118+
def get_email_confirmation_code(db_session, code):
119+
"""
120+
:param db_session: Database session.
121+
:param code: PasswordReset code
122+
:return: PasswordReset if email exists else None
123+
"""
124+
return db_session.query(User).filter(User.email_confirmation_code == code).one_or_none()
125+
116126

117127
@with_session
118128
def update_password_reset(db_session, id, updated_password_reset):

applications/users/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class User(IDMixin, TimeStampMixin, Base):
2323
email = Column(String, nullable=False, unique=True, index=True)
2424
password = Column(String, nullable=False)
2525
is_admin = Column(Integer, nullable=False, default=0)
26+
user_account_status = Column(Integer, nullable=False, default=0)
27+
email_confirmation_code = Column(String, nullable=False, default=0)
2628

2729
password_reset_codes = relationship("PasswordResetCode", back_populates="user", cascade="all, delete-orphan")
2830
owned_groups = relationship("Group", back_populates="owner", cascade="all, delete-orphan")

graphspace/exceptions/error_codes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Validation(object):
1212
UserPasswordMisMatch = (1003, "User/Password not recognized")
1313
UserNotAuthorized = (1004, "You are not authorized to access this resource, create an account and contact resource's owner for permission to access this resource.")
1414
UserNotAuthenticated = (1005, "User authentication failed")
15+
UserUnVerified = (10015, "User not verified")
1516

1617
# Graphs API
1718
IsPublicNotSet = (1006, "`is_public` is required to be set to True when `owner_email` and `member_email` are not provided.")

static/js/main.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,12 @@ var header = {
105105
"password": password
106106
},
107107
successCallback = function (response) {
108-
window.location.reload();
108+
$('#signupModal').modal('hide');
109+
$.notify({
110+
message: response.Message
111+
}, {
112+
type: 'success'
113+
});
109114
},
110115
errorCallback = function (response) {
111116
$.notify({

0 commit comments

Comments
 (0)