Skip to content

Commit d0f51cf

Browse files
author
Ross Crawford-d'Heureuse
committed
initial
0 parents  commit d0f51cf

13 files changed

+257
-0
lines changed

README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
django-dj_authy
2+
====================
3+
4+
A Django app for integrating with dj_authy
5+
6+
7+
Installation
8+
------------
9+
10+
1. python setup.py
11+
2. pip install requirements.txt
12+
3. add dj_authy to INSTALLED_APPS
13+
14+
Settings
15+
--------
16+
17+
18+
__Required__
19+
20+
21+
```
22+
SNOWSHOESTAMP_KEY : the oauth key for your app
23+
SNOWSHOESTAMP_SECRET : the oauth secret for your app
24+
```
25+
26+
27+
__Example Implementation__
28+
29+
30+
```views.py
31+
```
32+
33+
34+
__Please Note__
35+
36+
A signal will be issued when recieving callbacks from dj_authy
37+
38+
39+
__Signal Example Implementation__
40+
41+
42+
```signals.py
43+
from django.dispatch import receiver
44+
45+
from dj_authy.signals import dj_authy_event
46+
47+
48+
@receiver(dj_authy_event)
49+
def on_dj_authy_callback(sender, stamp_serial, **kwargs):
50+
# do something amazing with the data in the kwargs dict
51+
pass
52+
```
53+
54+
55+
__TODO__
56+
57+
1. tests

dj_authy/__init__.py

Whitespace-only changes.

dj_authy/admin.py

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# -*- coding: utf-8 -*-
2+
from django.contrib import admin
3+
4+
# Register your models here.

dj_authy/mixins.py

Whitespace-only changes.

dj_authy/models.py

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# -*- coding: utf-8 -*-
2+
from django.db import models
3+
from django.db import IntegrityError
4+
from django.contrib.auth.models import User
5+
6+
from jsonfield import JSONField
7+
from django_countries.fields import CountryField
8+
from phonenumber_field.modelfields import PhoneNumberField
9+
10+
from .services import AuthyService
11+
12+
import logging
13+
logger = logging.getLogger('django.request')
14+
15+
16+
class AuthyProfile(models.Model):
17+
user = models.OneToOneField('auth.User')
18+
authy_id = models.CharField(max_length=64, null=True, db_index=True)
19+
cellphone = PhoneNumberField(db_index=True, null=True)
20+
country = CountryField(null=True)
21+
is_smartphone = models.BooleanField(default=True)
22+
data = JSONField(default={})
23+
24+
@property
25+
def service(self):
26+
return AuthyService(user=self.user, authy_profile=self)
27+
28+
29+
def _get_or_create_authy_profile(user):
30+
# set the profile
31+
try:
32+
profile, is_new = AuthyProfile.objects.get_or_create(user=user) # added like this so django noobs can see the result of get_or_create
33+
return (profile, is_new,)
34+
except IntegrityError as e:
35+
logger.critical('transaction.atomic() integrity error: %s' % e)
36+
return (None, None,)
37+
38+
39+
# used to trigger profile creation by accidental reference. Rather use the _create_authy_profile def above
40+
User.authy_profile = property(lambda u: _get_or_create_authy_profile(user=u)[0])

dj_authy/services.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# -*- coding: utf-8 -*-
2+
from django.conf import settings
3+
from authy.api import AuthyApiClient
4+
5+
AUTHY_KEY = getattr(settings, 'AUTHY_KEY', None)
6+
AUTHY_FORCE_VERIFICATION = getattr(settings, 'AUTHY_FORCE_VERIFICATION', True)
7+
8+
assert AUTHY_KEY, 'You must define a settings.AUTHY_KEY'
9+
10+
from .signals import authy_event
11+
12+
import logging
13+
logger = logging.getLogger('django.request')
14+
15+
16+
class AuthyService(object):
17+
"""
18+
Service for interacting with Authy
19+
"""
20+
user = None
21+
authy_profile = None
22+
23+
client = None
24+
force_verification = False
25+
26+
def __init__(self, user, *args, **kwargs):
27+
# Allow overrides
28+
self.user = user
29+
self.authy_profile = kwargs.get('authy_profile', user.authy_profile) # hope its passed in otherwise get it, efficent
30+
31+
self.key = kwargs.get('key', AUTHY_KEY)
32+
self.force_verification = kwargs.get('force_verification', AUTHY_FORCE_VERIFICATION)
33+
34+
self.client = AuthyApiClient(self.key)
35+
logger.info('Initialized authy.Client with key: %s' % self.key)
36+
37+
self.ensure_user_registered()
38+
39+
@property
40+
def authy_id(self):
41+
return int(self.authy_profile.authy_id)
42+
43+
def ensure_user_registered(self):
44+
if self.authy_profile.authy_id is None:
45+
46+
authy_user = self.client.users.create(self.user.email,
47+
int(self.authy_profile.cellphone.national_number), # phonenumberfield stores as long
48+
self.authy_profile.cellphone.country_code) #email, cellphone, area_code
49+
50+
if authy_user.ok():
51+
self.authy_profile.authy_id = authy_user.id
52+
self.authy_profile.save(update_fields=['authy_id'])
53+
54+
else:
55+
errors = authy_user.errors()
56+
msg = 'Could not register Auth.user: %s' % errors
57+
logger.error(msg)
58+
raise Exception(msg)
59+
logger.info('Authy user: %s %s' % (self.user, self.authy_profile.authy_id))
60+
return True
61+
62+
def request_sms_token(self):
63+
if self.authy_profile.is_smartphone is True:
64+
sms = self.client.users.request_sms(self.authy_id)
65+
else:
66+
sms = self.client.users.request_sms(self.authy_id, {"force": True}) # force as the user is on an older phone with no app installed
67+
68+
ok = sms.ok()
69+
return ok if ok is True else sms.errors()
70+
71+
def verify_token(self, token):
72+
if type(token) not in [int]:
73+
raise Exception('Authy token must be an integer')
74+
75+
verification = self.client.tokens.verify(self.authy_id, token, {"force": self.force_verification})
76+
verified = verification.ok()
77+
errors = verification.errors()
78+
if not verified:
79+
logger.error('User: %s could not be verified using the token: %s due to: %s' % (self.user, token, errors))
80+
return verified

dj_authy/signals.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Webhook signals
4+
"""
5+
from django.dispatch import Signal
6+
7+
#
8+
# Outgoing Events
9+
#
10+
authy_event = Signal(providing_args=[])

dj_authy/tests.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from django.test import TestCase
2+
3+
# Create your tests here.

dj_authy/urls.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

dj_authy/views.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
from django.shortcuts import render
3+
4+
# Create your views here.
5+

requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
authy
2+
django-phonenumber-field
3+
django-countries
4+
-e git+https://github.com/rosscdh/django-jsonfield.git@lawpal#egg=django-jsonfield

runtests.py

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
import os, sys
3+
from django.conf import settings
4+
from django.core.management import call_command
5+
6+
DIRNAME = os.path.dirname(__file__)
7+
8+
DATABASES = {
9+
'default': {
10+
'ENGINE': 'django.db.backends.sqlite3',
11+
'NAME': os.path.join(DIRNAME, 'database.db'),
12+
}
13+
}
14+
15+
settings.configure(DEBUG = True,
16+
DATABASES=DATABASES,
17+
USE_TZ=True,
18+
AUTHY_KEY='12345678910111213141516171819201234567891011121314151617181920',
19+
ROOT_URLCONF='authy.urls',
20+
PASSWORD_HASHERS=('django.contrib.auth.hashers.MD5PasswordHasher',), # simple fast hasher but not secure
21+
INSTALLED_APPS = ('django.contrib.auth',
22+
'django.contrib.contenttypes',
23+
'django.contrib.sessions',
24+
'django.contrib.admin',
25+
'authy',))
26+
27+
28+
from django.test.simple import DjangoTestSuiteRunner
29+
30+
call_command('syncdb', interactive=False)
31+
32+
failures = DjangoTestSuiteRunner().run_tests(['authy',], verbosity=1)
33+
if failures:
34+
sys.exit(failures)

setup.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from setuptools import setup
2+
3+
setup(
4+
name="authy",
5+
packages=['authy'],
6+
version='0.1.0',
7+
author="Ross Crawford-d'Heureuse",
8+
license="MIT",
9+
author_email="[email protected]",
10+
url="https://github.com/rosscdh/django-authy",
11+
description="A Django app for integrating with authy",
12+
zip_safe=False,
13+
include_package_data=True,
14+
install_requires = [
15+
'authy',
16+
'django-phonenumber-field',
17+
'django-countries',
18+
]
19+
)

0 commit comments

Comments
 (0)