Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#115230531 Forgot Password #47

Open
wants to merge 14 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
56 changes: 56 additions & 0 deletions app/html_emails/forgot_password_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
password_request_email = """
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<table cellpadding="0" cellspacing="0" style="border-radius:4px;border:1px #dceaf5 solid" border="0" align="center">
<tbody>
<tr>
<td colspan="3" height="6"></td>
</tr>
<tr style="line-height:0px">
<td width="100%" style="font-size:0px" align="center" height="1">
<img width="40px" style="max-height:73px;width:40px" alt="" src="https://github.com/andela/limber/blob/develop/public/static/images/logo.png?raw=true" class="CToWUd">
</td>
</tr>
<tr>
<td>
<table cellpadding="0" cellspacing="0" style="line-height:25px" border="0" align="center">
<tbody>
<tr>
<td colspan="3" height="30"></td>
</tr>
<tr>
<td width="36"></td>
<td width="454" align="left" style="color:#444444;border-collapse:collapse;font-size:11pt;font-family:proxima_nova,'Open Sans','Lucida Grande','Segoe UI',Arial,Verdana,'Lucida Sans Unicode',Tahoma,'Sans Serif';max-width:454px" valign="top">
Hi there,
<br><br><p>Someone recently requested a password change for your Limber account. If this was you, you can set a new password using the button below.</p>
<br>
<br>
<center>
<a style="border-radius:3px;font-size:15px;color:white;border:1px #1373b5 solid;text-decoration:none;padding:14px 7px 14px 7px;width:280px;max-width:280px;font-family:proxima_nova,'Open Sans','lucida grande','Segoe UI',arial,verdana,'lucida sans unicode',tahoma,sans-serif;margin:6px auto;display:block;background-color:#007ee6;text-align:center" href={0} target="_blank">Reset Password</a></center>
<br>
<br>
<p>If you don't want to change your password or didn't request this, just ignore and delete this message.</p>
<p>To keep your account secure, please don't forward this email to anyone.</p>
<br>
<span>Thanks!</span>
<br>
<span>- The <span class="il">Limber</span> Team</span>
</td>
<td width="36"></td>
</tr>
<tr>
<td colspan="3" height="36"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
"""
135 changes: 0 additions & 135 deletions app/migrations/0001_initial.py

This file was deleted.

1 change: 1 addition & 0 deletions app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .org_invite import OrgInvites

from .invite import ProjectInvite
from .pass_reset import PasswordReset
85 changes: 85 additions & 0 deletions app/models/pass_reset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import os
import hashlib
from random import random

from django.db import models
from django.core import mail
from django.core.mail import EmailMultiAlternatives
from django.core.urlresolvers import reverse

from app.html_emails.forgot_password_email import password_request_email
from .user import UserAuthentication


class PasswordReset(models.Model):
"""Model to manage requests when user has 'forgotten password'."""

reset_code = models.CharField(primary_key=True, max_length=100, blank=False)
request_date = models.DateField(auto_now_add=True)
request_completed = models.BooleanField(default=False)
user = models.ForeignKey(UserAuthentication)

def send_password_reset_email(self):
"""Method to send password reset email."""
connection = mail.get_connection()

# set the reset code to the PasswordReset object
self.reset_code = self.create_request_code()

host = os.environ.get('LIMBER_HOST')

url = 'http://' + host + '/password/reset/complete/' + self.reset_code

body = """Hi there,\n
Someone recently requested a password change for your\n
Limber account. If this was you, you can set a new password <here>\n
If you don't want to change your password or didn't request this,
just ignore and delete this message.\n
To keep your account secure, please don't forward this email to anyone.\n
\n\n
Thanks!
- The Limber Team.
"""

html_content = password_request_email.format(
url,
)
email = EmailMultiAlternatives(
'Limber password reset',
body,
'[email protected]',
[self.user.email],
connection=connection
)
email.attach_alternative(html_content, 'text/html')
# send the email then save the invite to the database
email.send()

def create_request_code(self):
"""
Create a hash to be sent as part of the URL to later identify the user
when he/she responds to the password reset request.
"""

salt = random()
nonlat = "{0}4384834hhhhgsdhsdydsuyaisudhd {1}".format(
salt,
self.user.email
)
string = nonlat.encode()
i_code = hashlib.sha224(string).hexdigest()

return i_code

def save(self, *args, **kwargs):
"""
Customize the save process to send password request email every time an
instance of PasswordReset is saved.
"""
# if PasswordReset request hasn't been used before, send the reset email,
# otherwise, just save
if not self.request_completed:
# send the email
self.send_password_reset_email()
# save to DB
super(PasswordReset, self).save(*args, **kwargs)
12 changes: 12 additions & 0 deletions app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from app.models.project import Project, TeamMember
from app.models.org_invite import OrgInvites
from app.models.invite import ProjectInvite
from app.models.pass_reset import PasswordReset


class UserSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -112,6 +113,7 @@ class Meta:
model = Story
fields = '__all__'


class OrgInviteSerilizer (serializers.ModelSerializer):
''' Serializer for Invitation of Members to Organisations '''
code = serializers.CharField(required=False, read_only=True)
Expand Down Expand Up @@ -162,3 +164,13 @@ class ProjectInviteSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectInvite
fields = ('email', 'project', 'accept')


class PasswordResetSerializer(serializers.ModelSerializer):
"""Serializer to represent data from PasswordReset model in JSON format."""

class Meta:
model = PasswordReset
fields = ('user', 'request_date',)
# write_only_fields = ('email', 'password',)
read_only_fields = ('reset_code', 'request_date')
5 changes: 5 additions & 0 deletions app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
viewsets.OrgAssociationViewSet,
'org-associations'
)
router.register(
r'password/reset',
viewsets.PasswordResetViewSet,
'password-reset'
)

urlpatterns = [
url(r'^', include(router.urls)),
Expand Down
7 changes: 7 additions & 0 deletions app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,10 @@ def respond_project_invite(request, invite_code):
'limber/invite_response.html',
{'invite_code': invite_code}
)

def respond_password_reset(request, reset_code):
return render(
request,
'limber/password_reset_completion.html',
{'reset_code': reset_code}
)
Loading