Last active
May 17, 2016 03:27
-
-
Save VertigoRay/6a81325921e828dec7c754f0ddf0aa4c to your computer and use it in GitHub Desktop.
Typically, when you want to validate an e-mail address, you store an activation key and expiration date in the user profile table. So you can validate it one time. Then what? Just keep storing it forever? Clear it out and have the empty columns? I wanted a disposable key that didn't have to be stored in a database. This is my solution ... http:/…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
C:\Temp>env\Scripts\python.exe manage.py shell | |
Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32 | |
Type "help", "copyright", "credits" or "license" for more information. | |
(InteractiveConsole) | |
>>> from myapp.settings import EMAIL_KEY_EXPIRY_TIME | |
>>> from user.helpers import signing_dumps_w_entropy, signing_loads_w_entropy, get_uri | |
>>> import urllib.parse | |
>>> | |
>>> email = '[email protected]' | |
>>> key = signing_dumps_w_entropy(email) | |
>>> key | |
'IlZlcnRpZ29SYXlAZXhhbXBsZS5jb218YTIzd0lKekk4clhlVkVuSlhOdEt4N1kyYyI:1b2RGO:QuCLsDJvmTznZv-4IRrOKms6KAw' | |
>>> | |
>>> url = 'http://example.com/user/validate_email/{0}'.format( | |
... urllib.parse.quote(key), | |
... ) | |
>>> url | |
'http://example.com/user/validate_email/IlZlcnRpZ29SYXlAZXhhbXBsZS5jb218YTIzd0lKekk4clhlVkVuSlhOdEt4N1kyYyI%3A1b2RGO%3AQuCLsDJvmTznZv-4IRrOKms6KAw' | |
>>> | |
>>> # That's the url that we'll send via email | |
>>> # When a user clicks the link, `urls.py` will send the key portion as `key`; such as: | |
>>> # url(r'^validate_email/(?P<key>[a-zA-Z0-9-_=:]+)$', validate_email, name='validate_email'), | |
>>> | |
>>> key | |
'IlZlcnRpZ29SYXlAZXhhbXBsZS5jb218YTIzd0lKekk4clhlVkVuSlhOdEt4N1kyYyI:1b2RGO:QuCLsDJvmTznZv-4IRrOKms6KAw' | |
>>> signing_loads_w_entropy(key, max_age=EMAIL_KEY_EXPIRY_TIME) | |
'[email protected]' | |
>>> | |
>>> # At this point, we can lookup the account with this e-mail address | |
>>> # and mark it as validated in the database. | |
>>> | |
>>> # A couple more things ... | |
>>> # Let's break the key | |
>>> # (pretend word wrap wrapped the last character and a user messed up the copy & paste): | |
>>> email = signing_loads_w_entropy(key[:-1], max_age=EMAIL_KEY_EXPIRY_TIME) | |
django.core.signing.BadSignature: Signature "QuCLsDJvmTznZv-4IRrOKms6KA" does not match | |
>>> | |
>>> # Let's use an expired key | |
>>> from datetime import timedelta | |
>>> signing_loads_w_entropy(key, max_age=timedelta(seconds=1)) | |
django.core.signing.SignatureExpired: Signature age 412.71953558921814 > 1.0 seconds |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.core import signing | |
from string import ascii_lowercase, ascii_uppercase, digits | |
import random | |
def signing_dumps_w_entropy(string): | |
""" | |
This function just adds some entropy to the django.core.signing.dumps function. | |
""" | |
nonce = ''.join(random.SystemRandom().choice(ascii_lowercase + ascii_uppercase + digits) for _ in range(25)) | |
string_w_entropy = '|'.join((string, nonce)) | |
return signing.dumps(string_w_entropy, compress=True) | |
def signing_loads_w_entropy(string, max_age=None): | |
""" | |
This function just removes entropy to the django.core.signing.dumps function. | |
""" | |
return signing.loads(string, max_age=max_age).rsplit('|', 1)[0] | |
def get_uri(request, force_secure=False): | |
""" | |
Get the current URI; ie: http://localhost | |
""" | |
if force_secure or request.is_secure(): | |
return 'https://%s' % request.get_host() | |
else: | |
return 'http://%s' % request.get_host() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# myapp/settings.py | |
# Removed everything except what's needed for this demo. | |
from datetime import timedelta | |
EMAIL_KEY_EXPIRY_TIME = timedelta(days=2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I like the idea of adding some random data, so that no pattern, not even the time based one, shows up. Some thoughts:
You aren't building a full uri, just the scheme/hosts. I would handle the https stuff differently: have nginx redirect all requests to https, and then just use build_absolute_uri
"one|two|three|four".rsplit("|", 1)[0] = "one|two|three"
for a simpler. Your decoding function could be:One interesting thing (since you seem very worried about this) would be to stick the user's IP in there and check. Of course, issues with proxies/etc abound.
In general though it looks fine. I do feel you are a little over worried about this, considering how unlikely it is to be broken. One other thing you can do is encode a json string, in which case you are guaranteed that if you can encode it you can decode it. It gets large though, but you could encode other information in it.