Implemented TOTP

Implemented currencies and taxes
This commit is contained in:
Kumi 2020-05-22 18:13:23 +02:00
parent 89f971117f
commit 85fe13edcf
13 changed files with 112 additions and 23 deletions

View file

@ -17,6 +17,10 @@ class BaseOTPProvider:
'''Returns True if the provider is properly configured and ready to use.'''
raise NotImplementedError(f"{type(self)} does not implement is_active!")
def active_for_user(self, user):
'''Returns True if the provider is active and ready to be used by user.'''
return self.is_active
def start_authentication(self, user):
return "Authentication started, please enter your 2FA token."

View file

@ -1,17 +1,10 @@
from core.models import OTPUser
from core.modules.otp import providers
def get_user_otps(user):
try:
all_otps = OTPUser.objects.filter(user=user)
except:
return {}
user_providers = [otp.provider for otp in all_otps]
active_providers = {}
for name, provider in providers.items():
if name in user_providers:
if provider().active_for_user(user):
active_providers[name] = provider
return active_providers

View file

@ -1,3 +1,4 @@
from core.models.files import *
from core.models.profiles import *
from core.models.auth import *
from core.models.auth import *
from core.models.local import *

View file

@ -3,10 +3,6 @@ from django.contrib.auth import get_user_model
from uuid import uuid4
class OTPUser(Model):
user = ForeignKey(get_user_model(), CASCADE)
provider = CharField(max_length=255)
class LoginSession(Model):
uuid = UUIDField(default=uuid4, primary_key=True)
user = ForeignKey(get_user_model(), CASCADE)

34
core/models/local.py Normal file
View file

@ -0,0 +1,34 @@
from django.db.models import Model, CharField, BooleanField, DecimalField, ForeignKey, CASCADE
from django_countries.fields import CountryField
class Currency(Model):
name = CharField(max_length=255, unique=True)
code = CharField(max_length=16, unique=True)
symbol = CharField(max_length=8)
base = BooleanField(default=False)
rate = DecimalField(default=1, max_digits=30, decimal_places=10)
def set_base(self):
type(self).get_base().update(base=False)
self.update(base=True)
@classmethod
def get_base(cls):
return cls.objects.get(base=True)
class TaxPolicy(Model):
name = CharField(max_length=255, blank=True)
default_rate = DecimalField(default=0, max_digits=10, decimal_places=5)
def get_applicable_rate(self, country, reverse_charge=False):
rule = self.taxrule_set.get(destination_country=country)
if reverse_charge:
return rule.tax_rate if not rule.reverse_charge else 0
return rule.tax_rate
class TaxRule(Model):
policy = ForeignKey(TaxPolicy, on_delete=CASCADE)
destination_country = CountryField()
tax_rate = DecimalField(max_digits=10, decimal_places=5)
reverse_charge = BooleanField(default=False)

View file

@ -166,8 +166,6 @@ class PWRequestView(FormView):
token = PWResetToken.objects.create(user=user)
mail = generate_pwreset_mail(user, token)
simple_send_mail("Password Reset", mail, user.email)
except:
raise
# finally:
# messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
# return redirect("login")
finally:
messages.success(self.request, "If a matching account was found, you should shortly receive an email containing password reset instructions. If you have not received this message after five minutes, please verify that you have entered the correct email address, or contact support.")
return redirect("login")

View file

@ -22,6 +22,7 @@ INSTALLED_APPS = [
'dbsettings',
'django_celery_results',
'django_celery_beat',
'django_countries',
] + EXPEPHALON_MODULES
MIDDLEWARE = [
@ -153,4 +154,4 @@ CELERY_TASK_RESULT_EXPIRES = 12 * 60 * 60
LOGIN_REDIRECT_URL = reverse_lazy('dashboard')
LOGIN_URL = reverse_lazy('login')
LOGOUT_URL = reverse_lazy('logout')
LOGOUT_URL = reverse_lazy('logout')

View file

@ -12,3 +12,5 @@ celery
django-celery-results
django-celery-beat
python-memcached
django-countries
pyuca

View file

@ -0,0 +1,22 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from totp.models import TOTPUser
from totp.otp import TOTP
class Command(BaseCommand):
help = 'Disables TOTP for the specified user (identified by username)'
def add_arguments(self, parser):
parser.add_argument('user', type=str)
def handle(self, *args, **options):
try:
user = get_user_model().objects.get(username=options["user"])
except get_user_model().DoesNotExist:
raise ValueError(f"User {options['user']} does not exist")
try:
TOTPUser.objects.get(user=user).delete()
except TOTPUser.DoesNotExist:
raise ValueError(f"TOTP not enabled for user {options['user']}")

View file

@ -0,0 +1,32 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth import get_user_model
from totp.models import TOTPUser
from totp.otp import TOTP
from dbsettings.functions import getValue
import pyqrcode
import pyotp
class Command(BaseCommand):
help = 'Enables TOTP for the specified user (identified by username)'
def add_arguments(self, parser):
parser.add_argument('user', type=str)
def handle(self, *args, **options):
try:
user = get_user_model().objects.get(username=options["user"])
except get_user_model().DoesNotExist:
raise ValueError(f"User {options['user']} does not exist")
if TOTP().active_for_user(user):
raise ValueError(f"TOTP already enabled for user {options['user']}")
totu = TOTPUser.objects.create(user=user)
uri = pyotp.totp.TOTP(totu.secret).provisioning_uri(options["user"], issuer_name=getValue("core.title", "Expephalon"))
print(pyqrcode.create(uri).terminal())
print(uri)

View file

@ -5,8 +5,6 @@ from dbsettings.functions import getValue
import pyotp
# Create your models here.
class TOTPUser(Model):
secret = CharField(max_length=32, default=pyotp.random_base32())
secret = CharField(max_length=32, default=pyotp.random_base32)
user = ForeignKey(get_user_model(), CASCADE)

View file

@ -16,6 +16,13 @@ class TOTP(BaseOTPProvider):
def is_active(self):
return True
def active_for_user(self, user):
try:
TOTPUser.objects.get(user=user)
return super().active_for_user(user)
except TOTPUser.DoesNotExist:
return False
def start_authentication(self, user):
return "Please enter the token displayed in your app."
@ -23,7 +30,7 @@ class TOTP(BaseOTPProvider):
try:
otpuser = TOTPUser.objects.get(user=user)
return pyotp.TOTP(otpuser.secret).verify(token)
except OTPUser.DoesNotExist:
except TOTPUser.DoesNotExist:
return False
OTPPROVIDERS = {"totp": TOTP}

View file

@ -1 +1,2 @@
pyotp
pyqrcode