1
0
mirror of https://github.com/krislamo/DJ-BaseSite synced 2024-12-15 23:50:35 +00:00

Added the deactivation and account recovery system

Added the deactivation and account recovery systems.  The variable
EMAIL_MESSAGE was replaced with ACTIVATE_EMAIL, then RECOVERY_EMAIL was
added. The response variable was changed in all views to the correct
spelling.  The validation file has a new function to validate the
regular expressions in an email string and not check the database. And
finally the function UserActivationKey in views was renamed to KeyGen
This commit is contained in:
Kris Lamoureux 2012-10-20 14:01:18 -07:00
parent 8c5b68fd0d
commit dce89a3acf
18 changed files with 511 additions and 93 deletions

49
README
View File

@ -1,49 +0,0 @@
DJ-BaseSite was written in Python 2.7 and Django 1.4
Description:
DJ-BaseSite is a customizable login and register system with required email activation. While most people just use django-registration, I decided to write my own to learn more about Django.
NOTE TO DEVS:
A deactivation system hasn't been added. The login system needs to check attempts and display a CAPTCHA after a certain amount, this isn't included either. It also doesn't have an account recovery option. I plan to implement all of these things in future updates.
DJ-BaseSite is released under the New BSD License (The BSD 3-Clause License), refer to the LICENSE file.
Instructions
1. Install Python 2.7 (python.org) and Django 1.4 (djangoproject.com)
2. Open up the config.txt file and change the data under CUSTOM VARIABLES to your information. The configuration variable meanings are below.
3. Execute the SetupProject.py script and enter the project name, it will create a project based on your configuration.
4. Run the SyncDB script or "python manage.py syncdb" and create a super user.
5. Run the RunServer script or "python manage.py runserver" and check out your new website at localhost:8000 or 127.0.0.1:8000 in your browser.
DJ-BaseSite uses Recaptcha to prevent bots from creating accounts, so you'll need to get private and public keys from the website: http://www.google.com/recaptcha
Configuration Variable Meanings.
===================================
-baseurl
It is used to create activation and deactivation links [in views.py in register_user()]
-admin_name/email
Adds a name and email to the ADMINS tuple in settings.py
http://docs.djangoproject.com/en/dev/ref/settings/#admins
-secret_key
A string used to provide cryptographic signing. (don't use spaces due to the configurtion system)
https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
http://www.random.org/passwords/?mode=advanced
-captcha_publickey/privatekey
DJ-BaseSite uses Recaptcha to prevent bots from creating accounts, get keys at http://www.google.com/recaptcha
-HOSTsmpt
The SMPT server address with the email used to send activation emails. If your email doesn't support SMPT I highly suggest GMAIL.
-HOSTemail
The email address
-HOSTpass
The email's password
===================================
NOTE: It creates a development project, DEBUG is set to True in the settings.py so it is in no condition for deployment.

50
README.md Normal file
View File

@ -0,0 +1,50 @@
# DJ-BaseSite
DJ-BaseSite is a base Django **development** website project that adds basic user interaction to the site. Features include: The Django admin site, a login and logout system, a user registration system with required activation (via email), deactivation (an option during activation) and account recovery.
#### _This project is currently in the Alpha phase. Therefore it is suggested you thoroughly read and test when forking, etc._
DJ-BaseSite was written with [Python 2.7](http://www.python.org/download/releases/2.7/) and [Django 1.4](https://www.djangoproject.com/download/) on Windows 7 Home Premium 64 bit (Service Pack 1)
## License
DJ-BaseSite is released under the New BSD License, refer to the LICENSE file in the root of the repository before continuing.
## Change Log
### `0.7` (Oct 20, 2012)
* Added the deactivation and account recovery systems.
* Variable `EMAIL_MESSAGE` was replaced with `ACTIVATE_EMAIL` & `RECOVERY_EMAIL` was added.
* The `response` variable was changed in all views to the correct spelling. derp.
* Function `clean_emailRE()` was added to `validation.py`
* The function `UserActivationKey()` in `views.py` was renamed to `KeyGen()`
### [`0.5`](https://github.com/Kris619/DJ-BaseSite/zipball/80cdb11749afa9d2ecfcbb0a91f3f867f183bfc3) (Oct 13, 2012) SHA: 80cdb11749afa9d2ecfcbb0a91f3f867f183bfc3
* login / registration system with Django's default authentication backend
* activation system (deactivation system not implemented)
* reCAPTCHA support for registration
## Quick Start
1. Open up the `config.txt` file and change the data under `CUSTOM VARIABLES` to your information. The configuration is explained below.
2. Execute the `SetupProject.py` script and enter a project name, it will replicate the project out of `/myproject/` to `/yourproject/` with your information.
3. Run syncdb via terminal/console in the root of the project: `python manage.py syncdb`
* Windows users will need to add the path of their Python 2.7 installation (example: `C:/Python27/`) to the [path variable](http://showmedo.com/videotutorials/video?name=960000&fromSeriesID=96)
4. Run the development server: `python manage.py runserver`
> You should be done at this point. So check out your new website at [http://localhost:8000](http://localhost:8000) or [http://127.0.0.1:8000](http://127.0.0.1:8000) in your browser.
## Configuration
* `baseurl`
* Used to create activation, deactivation and recovery links
* `admin_name/email` `(`[`official documentation`](https://docs.djangoproject.com/en/1.4/ref/settings/#admins)`)`
* Adds a name and email to the ADMINS tuple in settings. On an error your website will email you logged errors.
* `secret_key` `(`[`official documentation`](https://docs.djangoproject.com/en/1.4/ref/settings/#secret-key)`)`
* A secure string used to provide cryptographic signing. It is automatically added to a [default Django project](https://docs.djangoproject.com/en/dev/intro/tutorial01/#creating-a-project) in settings.
* `captcha_publickey/privatekey`
* DJ-BaseSite uses [reCAPTCHA](http://www.google.com/recaptcha/learnmore) to prevent bots from creating accounts, so you'll need to [get a private and public key from the website](http://www.google.com/recaptcha)
* `HOSTsmtp`
* The SMTP server
* `HOSTemail`
* The email address
* `HOSTpass`
* HOSTemail's password

View File

@ -69,6 +69,9 @@ dir templates/admin
// Activation Email
%here%/myproject/myproject/activation_email.html 2
// Recovery Email
%here%/myproject/myproject/recovery_email.html 2
// HTML/CSS
dir static
dir static/css
@ -80,12 +83,16 @@ dir static/css
// Auth
dir templates/auth
%here%/myproject/myproject/templates/auth/activated.html 2
%here%/myproject/myproject/templates/auth/deactivated.html 2
%here%/myproject/myproject/templates/auth/disabled.html 2
%here%/myproject/myproject/templates/auth/logged_in.html 2
%here%/myproject/myproject/templates/auth/logged_out.html 2
%here%/myproject/myproject/templates/auth/login.html 2
%here%/myproject/myproject/templates/auth/newaccount.html 2
%here%/myproject/myproject/templates/auth/registration.html 2
%here%/myproject/myproject/templates/auth/recovery.html 2
%here%/myproject/myproject/templates/auth/recoveryattempt.html 2
%here%/myproject/myproject/templates/auth/recoverysuccess.html 2
// Backend Django app
dir backends root

View File

@ -1 +1,2 @@
@python manage.py runserver
@pause

View File

@ -1 +1,2 @@
@python manage.py syncdb
@pause

View File

@ -4,4 +4,6 @@ from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
activated = models.BooleanField()
activatekey = models.CharField(max_length=25, blank=True)
activate_key = models.CharField(max_length=25, blank=True)
recovery_key = models.CharField(max_length=25, blank=True)
recovery_time = models.DateTimeField(blank=True)

View File

@ -0,0 +1,5 @@
Hello <$user>,
Seems you have lost access to your account. The recovery link expires at: <$time> UTC
Recovery links last 2 hours.
Recovery link: <$recoverylink>

View File

@ -18,6 +18,9 @@ TEMPLATE_DEBUG = DEBUG
baseurl = "<%baseurl%>" # "example.com"
base_title = "<%basetitle%>"
# Time zone support
USE_TZ = True
'''
You need to sign up at http://recaptcha.net/ for a public/private key to use their CAPTCHA service.
'''
@ -31,7 +34,8 @@ EMAIL_HOST_USER = "<%HOSTemail%>"
EMAIL_HOST_PASSWORD = "<%HOSTpass%>"
EMAIL_PORT = 587
EMAIL_MESSAGE = ROOTDIR + "/<%myproject%>/activation_email.html"
ACTIVATE_EMAIL = ROOTDIR + "/<%myproject%>/activation_email.html"
RECOVERY_EMAIL = ROOTDIR + "/<%myproject%>/recovery_email.html"
ADMINS = (
("<%admin_name%>", "<%admin_email%>"),

View File

@ -59,6 +59,11 @@ table.loginform td {
.returnlink a:hover {color:#000000;} /* mouse over link */
.returnlink a:active {color:#000000;} /* selected link */
.cannotlogin {
text-align: center;
}
/* REGISTER CSS */
.register_form {
text-align: center;

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<div class="activated">
The account "{{user_name}}" has been deactivated. No one with this email can reregister unless you contact the admin.
<div class="activated_login"><a href="/login/">Login</a></div>
</div>
{% endblock %}

View File

@ -19,7 +19,7 @@
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>Pass</td>
<td>Password</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
@ -27,4 +27,8 @@
</tr>
</table>
</form>
{% endblock %}
<div class="cannotlogin">
<a href="/recovery/">Forgot password?</a> | <a href="/register/">Need an account?</a>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block content %}
<h1>Account Recovery</h1>
<form action="/recovery/" method="post">
{% csrf_token %}
<table class="recovery_form" align="center">
<tr>
<td>User</td>
<td><input type="text" name="usern" /></td>
</tr>
<tr>
<td>Email</td>
<td><input type="text" name="email" /></td>
</tr>
<tr>
<td colspan="2" class="humantest">
{{captcha_test|safe}}
</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Recover"></td>
</tr>
</table>
</form>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<h1>Account Recovery</h1>
<form action="/recover/" method="post">
<input type="hidden" value="{{the_key|safe}}" name="key" />
<input type="hidden" value="{{the_user|safe}}" name="user" />
{% csrf_token %}
<table class="recovery_form" align="center">
<tr>
<td>Password</td>
<td><input type="password" name="p1" /></td>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="p2" /></td>
</tr>
<tr>
<td colspan="2" class="humantest">
{{captcha_test|safe}}
</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="Recover Account"></td>
</tr>
</table>
</form>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block content %}
<div class="recoverysuccess">
Password successfully changed. Recovery success. Next time just remember your password ;)
</div>
{% endblock %}

View File

@ -21,11 +21,11 @@
<td><input type="text" name="usern" /></td>
</tr>
<tr>
<td>Pass<span class="asterisk">*</span></td>
<td>Password<span class="asterisk">*</span></td>
<td><input type="password" name="passw" /></td>
</tr>
<tr>
<td>Pass (again)<span class="asterisk">*</span></td>
<td>Password (again)<span class="asterisk">*</span></td>
<td><input type="password" name="repassw" /></td>
</tr>
<tr>

View File

@ -25,7 +25,9 @@ urlpatterns = patterns('<%myproject%>.views',
url(r'^logout/$', 'logout_user'),
url(r'^register/$', 'register_user'),
url(r'^activate/$', 'activate_user'),
#url(r'^deactivate/$', 'activate_user'), Deactivate needs to be written.
url(r'^deactivate/$', 'deactivate_user'),
url(r'^recovery/$', 'recover_user'),
url(r'^recover/$', 'recover_attempt')
)
urlpatterns += staticfiles_urlpatterns()

View File

@ -68,5 +68,10 @@ def clean_usernameRE(data):
else:
return False
def clean_emailRE(data):
if match("^[\w\d._%-+]+@[\w\d._%-]+.[\w]{2,6}$", data):
return data
else:
return False

View File

@ -12,10 +12,11 @@ Please take a momemt to read the short 3 Clause LICENSE file.
# Built in imports
import random
import hashlib
import datetime
# Responce imports
from django.http import HttpResponseRedirect
# response imports
from django.shortcuts import render_to_response, RequestContext
from django.http import HttpResponseRedirect
# Authentication/Session/Validation imports
from django.contrib.auth import authenticate, login, logout
@ -26,12 +27,16 @@ from django.core.exceptions import ObjectDoesNotExist
import validation as v
import captcha
# Time related Django imports
from django.utils.timezone import now
# Email imports
from django.core.mail import EmailMessage
from django.core import mail
# Variables from Settings.py
from settings import EMAIL_HOST_USER, EMAIL_MESSAGE
from settings import EMAIL_HOST_USER, ACTIVATE_EMAIL, RECOVERY_EMAIL
from settings import captcha_publickey, captcha_privatekey
from settings import baseurl, base_title
# User Profile model
@ -55,7 +60,12 @@ def get_or_create_profile(user):
try:
profile = user.get_profile()
except ObjectDoesNotExist:
profile = UserProfile(activated=True, user=user)
profile = UserProfile(
activated=True,
recovery_time=now(),
user=user
)
profile.save()
return profile
@ -66,8 +76,9 @@ def get_ip(request):
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def UserActivationKey():
def KeyGen():
random.seed()
choices = "abcdefghijklmnopqrstuvwxyzABCDEFG0123456789"
@ -93,11 +104,12 @@ def index(request):
else:
user_navigation = user_nav(False)
responce = render_to_response('index.html', locals())
return responce
response = render_to_response('index.html', locals())
return response
def logout_user(request):
logout(request)
user_navigation = user_nav(False)
return render_to_response('auth/logged_out.html', locals())
def login_user(request):
@ -136,20 +148,21 @@ def login_user(request):
# User account is activated (via email)
login(request, user)
user_name = user.username
responce = render_to_response('auth/logged_in.html', locals())
user_navigation = user_nav(user.username)
response = render_to_response('auth/logged_in.html', locals())
else:
# The account is not activated via email
error = "Please activate your account through email."
responce = render_to_response('error.html', locals())
response = render_to_response('error.html', locals())
else:
# The account is disabled. No login.
message = "Your account has been disabled."
responce = render_to_response('auth/disabled.html', locals())
response = render_to_response('auth/disabled.html', locals())
else:
# No object so the username and password are invalid.
login_errors = True
responce = render_to_response(
response = render_to_response(
'auth/login.html',
locals(),
context_instance=RequestContext(request)
@ -157,7 +170,7 @@ def login_user(request):
else:
# User isn't online and hasn't sent any POST data, give them a login form.
responce = render_to_response(
response = render_to_response(
'auth/login.html',
locals(),
context_instance=RequestContext(request)
@ -167,9 +180,9 @@ def login_user(request):
# User is logged on, don't let them login until he's logged out.
user_navigation = user_nav(request.user.username)
error = "You're already logged on."
responce = render_to_response('error.html',locals())
response = render_to_response('error.html',locals())
return responce
return response
def register_user(request):
global base_title
@ -261,11 +274,18 @@ def register_user(request):
new_user.save()
# Create activation key and user profile
activation_key = UserActivationKey()
activation_key = KeyGen()
# Add 2 hours so a recovery key can be made instantly after
# account creation.
thetime = new_user.date_joined + datetime.timedelta(hours=2)
profile = UserProfile(
activatekey=activation_key,
activate_key=activation_key,
activated=False,
recovery_time=thetime,
user=new_user)
profile.save()
# User is created and saved. Send an activation link via email
@ -278,13 +298,15 @@ def register_user(request):
message_deactivateurl = baseurl+"/deactivate/?key="+str(activation_key)
message_deactivateurl = message_deactivateurl+"&user="+str(new_user.username)
f = open(EMAIL_MESSAGE, 'r')
# Open email and replace data
f = open(ACTIVATE_EMAIL, 'r')
message = f.read()
message = message.replace("<$user>", str(new_user.username))
message = message.replace("<$activatelink>", message_activateurl)
message = message.replace("<$disablelink>", message_deactivateurl)
# Send email
email = EmailMessage(
"Account Activation",
message,
@ -297,7 +319,7 @@ def register_user(request):
# Return new account page
accountname = new_user.username
responce = render_to_response(
response = render_to_response(
'auth/newaccount.html',
locals(),
context_instance=RequestContext(request)
@ -305,7 +327,7 @@ def register_user(request):
else:
# Return registration form with errors in registration_errors
responce = render_to_response(
response = render_to_response(
'auth/registration.html',
locals(),
context_instance=RequestContext(request)
@ -313,7 +335,7 @@ def register_user(request):
# If user hasn't sent POST data (not logged on)
else:
responce = render_to_response(
response = render_to_response(
'auth/registration.html',
locals(),
context_instance=RequestContext(request)
@ -323,14 +345,14 @@ def register_user(request):
else:
user_navigation = user_nav(request.user.username)
error = "You cannot register while logged in."
responce = render_to_response(
response = render_to_response(
'error.html',
locals()
)
return responce
return response
def activate_user(request):
if request.method == 'GET':
if request.method == 'GET' and not request.user.is_authenticated():
# Check if data could be valid through regex
key = v.clean_key(request.GET["key"])
u_name = v.clean_usernameRE(request.GET["user"])
@ -341,8 +363,16 @@ def activate_user(request):
# Check profile for key and compare.
user = User.objects.get(username=u_name)
user_profile = get_or_create_profile(user)
# You're already activated
if user_profile.activated:
key_correct = False
# You're disabled.
elif user.is_active == False:
key_correct = False
if user_profile.activatekey == key:
elif user_profile.activate_key == key:
# Activate user
user_profile.activated = True
user_profile.save()
@ -355,17 +385,295 @@ def activate_user(request):
else:
key_correct = False
if key_correct:
user_name = user.username
responce = render_to_response(
'auth/activated.html',
locals()
)
else:
error = "Activation failed."
responce = render_to_response(
'error.html',
locals()
)
user_navigation = user_nav(False)
if key_correct:
user_name = user.username
response = render_to_response(
'auth/activated.html',
locals()
)
else:
error = "Activation failed."
response = render_to_response(
'error.html',
locals()
)
return responce
return response
# Logged on or didn't give GET data.
return HttpResponseRedirect('/')
def deactivate_user(request):
if request.method == 'GET' and not request.user.is_authenticated():
# Check if data could be valid through regex
key = v.clean_key(request.GET["key"])
u_name = v.clean_usernameRE(request.GET["user"])
# If key and username are valid
if request.GET["key"] == key and u_name:
try:
# Check profile for key and compare.
user = User.objects.get(username=u_name)
user_profile = get_or_create_profile(user)
# If you wish to have your users deactivate with the same
# link sent in activation, remove this if statement
if user_profile.activated:
key_correct = False
elif user_profile.activate_key == key:
# Disable account.
user_profile.activated = False
user_profile.save()
user.is_active = False
user.save()
key_correct = True
else:
key_correct = False
except ObjectDoesNotExist:
key_correct = False
else:
key_correct = False
if key_correct:
user_name = user.username
response = render_to_response(
'auth/deactivated.html',
locals()
)
else:
error = "Deactivation failed."
response = render_to_response(
'error.html',
locals()
)
return response
# Logged on or didn't give GET data.
return HttpResponseRedirect('/')
def recover_user(request):
global base_title
global global_nav, user_nav
title = base_title + "Recovery"
global_navigation=global_nav()
# If user is not logged on
if not request.user.is_authenticated():
# Return user navigation for an anonymous session
user_navigation = user_nav(False)
# Set up captcha html.
captcha_test = captcha.displayhtml(captcha_publickey)
# If user has sent POST data (not logged in)
if request.method == 'POST':
# Check info via regex
u_name = v.clean_usernameRE(request.POST["usern"])
email = v.clean_emailRE(request.POST["email"])
if email == request.POST["email"] and u_name:
try:
user = User.objects.get(username__iexact=u_name)
user_profile = get_or_create_profile(user)
# Current time
time_now = now()
# Recovery time
recovery_time = user_profile.recovery_time
if time_now > recovery_time:
# Key has been requested too many times in 2 hours.
error = "Recovery keys can only be requested once every 2 hours."
response = render_to_response(
'error.html',
locals()
)
else:
# Connect to SMTP server
connection = mail.get_connection()
connection.open()
# Create a recovery key
user_profile.recovery_key = KeyGen()
user_profile.save()
# Create account recovery link
message_recoveryurl = baseurl+"/recover/?key="+str(user_profile.recovery_key)
message_recoveryurl = message_recoveryurl+"&user="+str(user.username)
# Open email template
f = open(RECOVERY_EMAIL, 'r')
message = f.read()
print message
# Replace information
message = message.replace("<$user>", str(user.username))
message = message.replace("<$recoverylink>", message_recoveryurl)
message = message.replace("<$time>", str(user_profile.recovery_time))
# Send email
email = EmailMessage(
"Account Recovery",
message,
EMAIL_HOST_USER,
[user.email]
)
email.send()
connection.close()
# Tell user to check their email.
error = "Check your email for a recovery link."
response = render_to_response(
'error.html',
locals()
)
except User.DoesNotExist:
error = "No user with that email exists."
response = render_to_response(
'error.html',
locals()
)
else:
error = "No user with that email exists."
response = render_to_response(
'error.html',
locals()
)
else:
# Didn't submit, give recovery form.
response = render_to_response(
'auth/recovery.html',
locals(),
context_instance=RequestContext(request)
)
# You're signed in, no recovery for you.
else:
return HttpResponseRedirect('/')
return response
def recover_attempt(request):
global base_title
global global_nav, user_nav
title = base_title + "Recovery"
global_navigation=global_nav()
# If user is not logged on
if request.method == 'GET' and not request.user.is_authenticated():
# Check if data could be valid through regex
key = v.clean_key(request.GET["key"])
u_name = v.clean_usernameRE(request.GET["user"])
# If valid data
if request.GET["key"] == key and u_name:
# return new password form
the_user = u_name
the_key = key
response = render_to_response(
'auth/recoveryattempt.html',
locals(),
context_instance=RequestContext(request)
)
else:
error = "User does not exist."
response = render_to_response(
'error.html',
locals()
)
# If user isn't online and is sending post data
elif request.method == 'POST' and not request.user.is_authenticated():
# Check if data could be valid through regex
key = v.clean_key(request.POST["key"])
u_name = v.clean_usernameRE(request.POST["user"])
# If key/username is validated by regex
if request.POST["key"] == key and u_name:
try:
# Check profile for key and compare.
user = User.objects.get(username=u_name)
user_profile = get_or_create_profile(user)
# Get database key and key time limit
key_db = user_profile.recovery_key
keylimit_db = user_profile.recovery_time
# Current time
time_now = now()
# If the key hasn't expired and is correct
if now() < keylimit_db and key_db == key:
password = v.clean_password(request.POST["p1"])
recover_error = ""
if not request.POST["p1"] == request.POST["p2"]:
recover_error = "Passwords don't match."
elif password == None:
recover_error = "No password entered."
elif password == -1:
recover_error = "Passwords have to be at least 5 characters."
# If there is an error
if recover_error != '':
# Set error variable for template
error = recover_error
response = render_to_response(
'error.html',
locals()
)
else:
# No errors, change password
user.set_password(password)
user.save()
# Expire recovery time.
user_profile.recovery_time = now()
user_profile.save()
response = render_to_response(
'auth/recoverysuccess.html',
locals()
)
else:
error = "Invalid key and/or username."
response = render_to_response(
'error.html',
locals()
)
except User.DoesNotExist:
error = "User doesn't exist."
response = render_to_response(
'error.html',
locals()
)
else:
error = "Invalid key and/or username."
response = render_to_response(
'error.html',
locals()
)
else:
# logged on, no recovery.
return HttpResponseRedirect('/')
return response