diff --git a/README.rst b/README.rst
index b280ef9..0d557e9 100644
--- a/README.rst
+++ b/README.rst
@@ -25,14 +25,14 @@ Open settings:
$ vim demo/settings.py
-Replace Twilio credentials in ``AUTH_SMS_BACKEND_AUTH`` to your demo account settings.
+Replace Twilio credentials in ``AUTH_SMS_BACKEND_AUTH`` to your demo account settings or change SMS_FORCE to False.
-Run RabbitMQ server and Celery daemon(or remove 'djcelery' from ``INSTALLED_APPS``):
+Run Redis server and Celery daemon(or remove 'djcelery' from ``INSTALLED_APPS``):
.. code-block:: bash
- $ rabbitmq-server -detached
- $ python manage.py celeryd --loglevel=info >& /dev/null &
+ $ redis-server >& /dev/null &
+ $ python manage.py celeryd --loglevel=info >& /tmp/celery.log &
Run test server:
@@ -45,7 +45,7 @@ Run test server:
Now you can open http://127.0.0.1:8000/accounts/register/ and register
new account and setup all available authentication methods.
-*Note: activation link will be output on console.*
+*Note: activation link will be output to console.*
Screenshots
diff --git a/demo/demo/settings.py b/demo/demo/settings.py
index b53cf66..b06cdc5 100644
--- a/demo/demo/settings.py
+++ b/demo/demo/settings.py
@@ -83,7 +83,7 @@
'django_extensions',
#'debug_toolbar',
'bootstrap',
- # 'djcelery',
+ 'djcelery',
'django_tables2',
'registration',
'south',
@@ -120,7 +120,7 @@
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# IMPORTANT: django-secure-auth required configs
-AUTH_SMS_FORCE = False
+AUTH_SMS_FORCE = True
AUTH_SMS_BACKEND = 'Twilio'
# You can register new account on https://www.twilio.com/ and enter
diff --git a/demo/demo/templates/base.html b/demo/demo/templates/base.html
index 9a0bad0..7fed9a6 100644
--- a/demo/demo/templates/base.html
+++ b/demo/demo/templates/base.html
@@ -95,7 +95,7 @@
{% block title %}{% endblock %}
diff --git a/demo/demo/templates/registration/activate.html b/demo/demo/templates/registration/activate.html
index 2c14c02..8cb5081 100644
--- a/demo/demo/templates/registration/activate.html
+++ b/demo/demo/templates/registration/activate.html
@@ -4,17 +4,10 @@
{% block title %}{% trans 'Registration' %}{% endblock %}
{% block content %}
-
-{% if account %}
-
-{% trans "Account successfully activated" %}
-
-{% trans "Log in" %}
-
-{% else %}
-
-{% trans "Account activation failed" %}
-
-{% endif %}
-
+ {% if account %}
+ {% trans "Account successfully activated" %}
+ {% trans "Log in" %}
+ {% else %}
+ {% trans "Account activation failed" %}
+ {% endif %}
{% endblock %}
diff --git a/secureauth/auth_forms.py b/secureauth/auth_forms.py
index c75467b..d5957bc 100644
--- a/secureauth/auth_forms.py
+++ b/secureauth/auth_forms.py
@@ -7,15 +7,7 @@
from defaults import CAPTCHA_ATTEMPT, CAPTCHA_ENABLED
from captcha.fields import CaptchaField
-
-
-AUTH_TYPES = (
- ('', '---'),
- ('code', _('by code')),
- ('token', _('by token')),
- ('phone', _('by sms')),
- ('question', _('by question')),
-)
+from models import AUTH_TYPES
def get_available_auth_methods(user):
@@ -55,12 +47,13 @@ def clean(self):
if auth_type and code:
from secureauth.backend import AuthBackend
+
backend = AuthBackend()
self.user_cache = backend.auth(self.credentials, auth_type, code)
if self.user_cache is None:
raise forms.ValidationError(_("Please enter correct code"))
elif not self.user_cache.is_active:
- raise forms.ValidationError(_("This account is inactive."))
+ raise forms.ValidationError(_("This account is inactive"))
return self.cleaned_data
def get_user(self):
diff --git a/secureauth/defaults.py b/secureauth/defaults.py
index 184695e..deff562 100644
--- a/secureauth/defaults.py
+++ b/secureauth/defaults.py
@@ -1,35 +1,38 @@
# -*- coding: utf-8 -*-
+from django.utils.translation import ugettext as _
from django.conf import settings
def get_settings(key, default):
return getattr(settings, key, default)
+
SMS_FORCE = get_settings('AUTH_SMS_FORCE', False)
SMS_BACKEND = get_settings('AUTH_SMS_BACKEND', 'Twilio')
SMS_BACKEND_AUTH = get_settings('AUTH_SMS_BACKEND_AUTH', [
'ACc73704107c6a5426b2157e279c485d32', 'a2949613dc22aa3df58ea813a6e0747f'
])
SMS_FROM = get_settings('AUTH_SMS_FROM', '+12242315966')
-SMS_MESSAGE = get_settings('AUTH_SMS_MESSAGE', 'Your code is: %s')
+SMS_MESSAGE = get_settings('AUTH_SMS_MESSAGE', _('Your code is: %s'))
SMS_CODE_LEN = get_settings('AUTH_SMS_CODE_LEN', 4)
SMS_AGE = get_settings('AUTH_SMS_AGE', 60)
SMS_ASCII = get_settings('AUTH_SMS_ASCII', False)
CODE_RANGES = get_settings('AUTH_CODE_RANGES', 20)
CODE_LEN = get_settings('AUTH_CODE_LEN', 6)
+TOTP_NAME = get_settings('AUTH_TOTP_NAME', "%(username)s@%(domain)s")
# Available: code, token, phone, question
DEFAULT_AUTH_TYPE = get_settings('AUTH_DEFAULT_TYPE', 'phone')
# Notification when user is authenticated on site
SMS_NOTIFICATION_SUBJECT = get_settings(
- 'AUTH_SMS_NOTIFICATION_SUBJECT', 'Auth activity')
+ 'AUTH_SMS_NOTIFICATION_SUBJECT', _('Auth activity'))
CODES_SUBJECT = get_settings(
- 'AUTH_CODES_SUBJECT', 'Your security codes')
+ 'AUTH_CODES_SUBJECT', _('Your security codes'))
SMS_NOTIFICATION_MESSAGE = get_settings(
'AUTH_SMS_NOTIFICATION_MESSAGE',
- "Authorization was made. If it's not you, then contact with us.")
+ _("Authorization was made. If it's not you, then contact with us."))
USE_CELERY = get_settings(
'AUTH_USE_CELERY', 'djcelery' in settings.INSTALLED_APPS)
diff --git a/secureauth/forms.py b/secureauth/forms.py
index 7afc7b8..2a69623 100644
--- a/secureauth/forms.py
+++ b/secureauth/forms.py
@@ -35,7 +35,7 @@ def __init__(self, request, model, *args, **kwargs):
def clean_current_password(self):
current_password = self.cleaned_data.get('current_password', '')
if not self.request.user.check_password(current_password):
- raise forms.ValidationError(_(u'Invalid password!'))
+ raise forms.ValidationError(_(u'Invalid password'))
def save(self):
if not self.user:
@@ -101,26 +101,32 @@ class QuestionForm(BasicForm):
question = forms.CharField(label=_('Question'), required=True)
code = forms.CharField(label=_('code'), required=True, max_length=16)
- def __init__(self, *args, **kwargs):
+ def __init__(self, request, *args, **kwargs):
self.decrypt('code', **kwargs)
self.decrypt('question', **kwargs)
- super(QuestionForm, self).__init__(*args, **kwargs)
+ super(QuestionForm, self).__init__(request, *args, **kwargs)
- if kwargs.get('initial') or (args[2] and args[2].get('code')):
- self.fields['code'].widget = forms.HiddenInput()
self.fields['code'].label = _('Answer')
+ try:
+ UserAuthQuestion.objects.get(user=request.user)
+ self.fields.pop('code')
+ except UserAuthQuestion.DoesNotExist:
+ pass
+
def save(self):
model = super(QuestionForm, self).save()
return model.set_data(
self.cleaned_data.get('question'), self.cleaned_data.get('code'))
-class PasswordCheckForm(forms.ModelForm):
+class BaseSettingsForm(forms.ModelForm):
+ enabled = forms.BooleanField(label=_('Enabled'), required=False)
+
def __init__(self, request, *args, **kwargs):
self.request = request
- super(PasswordCheckForm, self).__init__(*args, **kwargs)
+ super(BaseSettingsForm, self).__init__(*args, **kwargs)
if CHECK_PASSWORD is True:
self.fields['current_password'] = forms.CharField(
@@ -129,16 +135,16 @@ def __init__(self, request, *args, **kwargs):
def clean_current_password(self):
current_password = self.cleaned_data.get('current_password', '')
if not self.request.user.check_password(current_password):
- raise forms.ValidationError(_(u'Invalid password!'))
+ raise forms.ValidationError(_(u'Invalid password'))
-class NotificationForm(PasswordCheckForm):
+class NotificationForm(BaseSettingsForm):
class Meta:
model = UserAuthNotification
exclude = ('user',)
-class LoggingForm(PasswordCheckForm):
+class LoggingForm(BaseSettingsForm):
class Meta:
model = UserAuthLogging
exclude = ('user',)
@@ -184,7 +190,7 @@ def get_status(model):
def clean_current_password(self):
current_password = self.cleaned_data.get('current_password', '')
if not self._request.user.check_password(current_password):
- raise forms.ValidationError(_(u'Invalid password!'))
+ raise forms.ValidationError(_(u'Invalid password'))
def save(self):
def set_status(model, key):
diff --git a/secureauth/locale/ru/LC_MESSAGES/django.mo b/secureauth/locale/ru/LC_MESSAGES/django.mo
index 021c6e1..381e18b 100644
Binary files a/secureauth/locale/ru/LC_MESSAGES/django.mo and b/secureauth/locale/ru/LC_MESSAGES/django.mo differ
diff --git a/secureauth/locale/ru/LC_MESSAGES/django.po b/secureauth/locale/ru/LC_MESSAGES/django.po
index ca81571..731e18e 100644
--- a/secureauth/locale/ru/LC_MESSAGES/django.po
+++ b/secureauth/locale/ru/LC_MESSAGES/django.po
@@ -9,7 +9,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-11-24 04:00+0400\n"
-"PO-Revision-Date: 2013-11-24 21:43+0230\n"
+"PO-Revision-Date: 2014-06-04 13:04+0230\n"
"Last-Translator: \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-"X-Translated-Using: django-rosetta 0.7.3\n"
+"X-Translated-Using: django-rosetta 0.7.4\n"
msgid "Russian"
msgstr "Русский"
@@ -125,7 +125,7 @@ msgstr "код"
msgid "Please enter correct code"
msgstr "Пожалуйста, введите правильный код"
-msgid "This account is inactive."
+msgid "This account is inactive"
msgstr "Эта учетная запись неактивна."
msgid "Enabled"
@@ -373,7 +373,7 @@ msgid "SMS was sent!"
msgstr "Код был выслан к Вам на мобильный телефон"
msgid "Default backend can not be removed"
-msgstr "Нельзя удалить настройки"
+msgstr "Нельзя удалить настройки по умолчанию"
msgid "Auth method was disabled"
msgstr "Один из методов аутентификации был отключен"
@@ -395,3 +395,39 @@ msgstr "Уведомления отключены. Если это не Вы, п
msgid "Answer"
msgstr "Ответ"
+
+msgid "Current password:"
+msgstr "Текущий пароль:"
+
+msgid "Logging settings"
+msgstr "Настройка логирования"
+
+msgid "Confirm Method"
+msgstr "Метод подтверждения"
+
+msgid "Invalid password"
+msgstr "Неверный пароль"
+
+msgid "Successfully saved"
+msgstr "Успешно сохранено"
+
+msgid "Your settings has changed"
+msgstr "Настройки изменились"
+
+msgid "Codes were sent to the email"
+msgstr "Коды отправлены на email"
+
+msgid "Send codes to email"
+msgstr "Отправить коды на почту"
+
+msgid "Your code is: %s"
+msgstr "Ваш код: %s"
+
+msgid "Auth activity"
+msgstr "Код авторизации"
+
+msgid "Your security codes"
+msgstr "Ваши коды безопасности"
+
+msgid "Authorization was made. If it's not you, then contact with us."
+msgstr "Был произведен вход. Если это не Вы, то свяжитесь с нами."
diff --git a/secureauth/migrations/0006_auto__add_field_userauthactivity_confirm_method.py b/secureauth/migrations/0006_auto__add_field_userauthactivity_confirm_method.py
new file mode 100644
index 0000000..51e3785
--- /dev/null
+++ b/secureauth/migrations/0006_auto__add_field_userauthactivity_confirm_method.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding field 'UserAuthActivity.confirm_method'
+ db.add_column(u'secureauth_userauthactivity', 'confirm_method',
+ self.gf('django.db.models.fields.CharField')(max_length=10, null=True, blank=True),
+ keep_default=False)
+
+
+ def backwards(self, orm):
+ # Deleting field 'UserAuthActivity.confirm_method'
+ db.delete_column(u'secureauth_userauthactivity', 'confirm_method')
+
+
+ models = {
+ u'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ u'auth.permission': {
+ 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ u'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ u'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ u'secureauth.userauthactivity': {
+ 'Meta': {'ordering': "('-id',)", 'object_name': 'UserAuthActivity'},
+ 'agent': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'confirm_method': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}),
+ 'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'geo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"})
+ },
+ u'secureauth.userauthattempt': {
+ 'Meta': {'object_name': 'UserAuthAttempt'},
+ 'attempt': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'auto_now_add': 'True', 'blank': 'True'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'})
+ },
+ u'secureauth.userauthcode': {
+ 'Meta': {'object_name': 'UserAuthCode'},
+ 'code': ('django.db.models.fields.TextField', [], {}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_verified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'number': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ },
+ u'secureauth.userauthlogging': {
+ 'Meta': {'object_name': 'UserAuthLogging'},
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ },
+ u'secureauth.userauthnotification': {
+ 'Meta': {'object_name': 'UserAuthNotification'},
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ },
+ u'secureauth.userauthphone': {
+ 'Meta': {'object_name': 'UserAuthPhone'},
+ 'code': ('django.db.models.fields.TextField', [], {}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_verified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'phone': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ },
+ u'secureauth.userauthquestion': {
+ 'Meta': {'object_name': 'UserAuthQuestion'},
+ 'code': ('django.db.models.fields.TextField', [], {}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_verified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'question': ('django.db.models.fields.TextField', [], {}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ },
+ u'secureauth.userauthtoken': {
+ 'Meta': {'object_name': 'UserAuthToken'},
+ 'code': ('django.db.models.fields.TextField', [], {}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_verified': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True'})
+ }
+ }
+
+ complete_apps = ['secureauth']
\ No newline at end of file
diff --git a/secureauth/models.py b/secureauth/models.py
index bfcb382..3cf409a 100644
--- a/secureauth/models.py
+++ b/secureauth/models.py
@@ -7,6 +7,7 @@
from django.utils.translation import ugettext as _
from django.core.exceptions import ValidationError
+from django.forms.models import model_to_dict
from django.contrib.sites.models import Site
from django.utils.timezone import now
from django.contrib import messages
@@ -24,7 +25,16 @@
from secureauth.defaults import (
SMS_MESSAGE, SMS_CODE_LEN, SMS_AGE, SMS_FROM,
CODE_RANGES, CODE_LEN, SMS_NOTIFICATION_MESSAGE, SMS_NOTIFICATION_SUBJECT,
- LOGIN_ATTEMPT, BAN_TIME, CHECK_ATTEMPT, CODES_SUBJECT)
+ LOGIN_ATTEMPT, BAN_TIME, CHECK_ATTEMPT, CODES_SUBJECT, TOTP_NAME)
+
+
+AUTH_TYPES = (
+ ('', '---'),
+ ('code', _('by code')),
+ ('token', _('by token')),
+ ('phone', _('by sms')),
+ ('question', _('by question')),
+)
class UserAuthAbstract(models.Model):
@@ -115,11 +125,9 @@ def send_codes(cls, request):
class UserAuthToken(UserAuthAbstract):
def get_google_url(self):
- return get_google_url(
- Sign().unsign(self.code),
- "%s@%s" % (
- self.user.get_full_name(), Site.objects.get_current().domain)
- )
+ data = model_to_dict(Site.objects.get_current())
+ data.update(model_to_dict(self.user))
+ return get_google_url(Sign().unsign(self.code), TOTP_NAME % data)
def _code_is_valid(self, code):
return check_seed(Sign().unsign(self.code), int(code))
@@ -204,12 +212,15 @@ class UserAuthActivity(models.Model):
date = models.DateTimeField(_('Date'), auto_now_add=True)
agent = models.CharField(
_('Browser'), max_length=255, null=True, blank=True)
+ confirm_method = models.CharField(
+ _('Confirm method'), max_length=10, choices=AUTH_TYPES,
+ null=True, blank=True)
class Meta:
ordering = ('-id',)
@classmethod
- def log_auth(cls, request):
+ def log_auth(cls, request, confirm_method=''):
ip = get_ip(request)
user_agent = request.META.get('HTTP_USER_AGENT')
if user_agent is not None:
@@ -220,7 +231,7 @@ def log_auth(cls, request):
browser.get('name', ""), browser.get('version', ""))
cls.objects.create(
user=request.user, ip=get_ip(request), geo=get_geo(ip),
- agent=user_agent
+ agent=user_agent, confirm_method=confirm_method
)
@classmethod
diff --git a/secureauth/registration/views.py b/secureauth/registration/views.py
index 1e0e2fd..9380a10 100644
--- a/secureauth/registration/views.py
+++ b/secureauth/registration/views.py
@@ -8,9 +8,9 @@
from registration.backends.default import views
from secureauth.forms import ActivatePhoneForm, CodeForm
+from secureauth.defaults import SMS_AGE, SMS_FORCE
from secureauth.models import UserAuthPhone
from secureauth.utils.sign import Sign
-from secureauth.defaults import SMS_AGE
def _get_user(**kwargs):
@@ -83,3 +83,6 @@ def activate(self, request, activation_key):
def get_success_url(self, request, user):
return ('registration_activation_complete_view', (), {})
+
+
+ActivationView = ActivationView if SMS_FORCE else views.ActivationView
diff --git a/secureauth/tables.py b/secureauth/tables.py
index 35c7570..a6119d5 100644
--- a/secureauth/tables.py
+++ b/secureauth/tables.py
@@ -9,6 +9,6 @@
class UserAuthActivityTable(tables.Table):
class Meta:
model = UserAuthActivity
- fields = ('ip', 'geo', 'date', 'agent')
+ fields = ('ip', 'geo', 'date', 'agent', 'confirm_method')
order_by = ('id',)
per_page = ACTIVITY_PER_PAGE
diff --git a/secureauth/templates/secureauth/codes_settings/was_done.html b/secureauth/templates/secureauth/codes_settings/was_done.html
index c94fcca..535ffc6 100644
--- a/secureauth/templates/secureauth/codes_settings/was_done.html
+++ b/secureauth/templates/secureauth/codes_settings/was_done.html
@@ -6,5 +6,5 @@
{% block content %}
{% trans 'Codes authentication successfully enabled.' %}
- {% trans 'Send codes to email.' %}
+ {% trans 'Send codes to email' %}
{% endblock content %}
diff --git a/secureauth/views.py b/secureauth/views.py
index cdf3e2a..df685bd 100644
--- a/secureauth/views.py
+++ b/secureauth/views.py
@@ -137,14 +137,18 @@ def login_confirmation(request, template_name='secureauth/confirmation.html',
if UserAuthLogging.is_enabled(request):
UserAuthActivity.check_location(request)
- UserAuthActivity.log_auth(request)
+ UserAuthActivity.log_auth(
+ request, form.cleaned_data.get('auth_type'))
UserAuthNotification.notify(request)
UserAuthAttempt.remove(request)
return HttpResponseRedirect(data.get('redirect_to'))
else:
- raise Http404('ERROR')
+ return HttpResponseBadRequest()
+ elif CHECK_ATTEMPT is True:
+ UserAuthAttempt.clean()
+ UserAuthAttempt.store(request)
else:
form = authentication_form(data)
@@ -339,9 +343,13 @@ def codes_settings(request):
@login_required
@never_cache
def send_codes(request):
- if UserAuthCode.send_codes(request):
+ if request.session.get('step') != 3:
+ raise Http404
+ elif UserAuthCode.send_codes(request):
messages.info(request, _('Codes were sent to the email'))
UserAuthNotification.notify(request, _('Codes were sent to the email'))
+ if request.session.get('step'):
+ del request.session['step']
return redirect('codes_settings')
@@ -381,6 +389,7 @@ def _settings_view(request, model_class, form_class, template):
request, _('Your settings has changed'), force=True)
return render(request, template, {'form': form})
+
@login_required
@never_cache
def notify_settings(request):
diff --git a/setup.py b/setup.py
index a7ecb8c..4afa23b 100644
--- a/setup.py
+++ b/setup.py
@@ -8,10 +8,10 @@
name='django-secure-auth',
version=get_version(),
description='Secure authentication by TOTP, SMS, Codes & Question',
- keywords='django secure auth totp sms codes question',
+ keywords='django secure auth protection totp sms codes question',
long_description=open('README.rst').read(),
classifiers=[
- 'Development Status :: 4 - Beta',
+ 'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',