User profile models
SMS OTP provider Login template (without css)
This commit is contained in:
parent
9798849aab
commit
d74a4c1b8b
20 changed files with 183 additions and 23 deletions
21
core/classes/otp.py
Normal file
21
core/classes/otp.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
class BaseOTPProvider:
|
||||
'''OTP providers must be subclasses of BaseOTPProvider and implement at least validate_token().'''
|
||||
|
||||
@property
|
||||
def get_name(self):
|
||||
return "Base OTP Provider"
|
||||
|
||||
@property
|
||||
def get_logo(self):
|
||||
return ""
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
'''Returns True if the provider is properly configured and ready to use.'''
|
||||
raise NotImplementedError(f"{type(self)} does not implement is_active!")
|
||||
|
||||
def start_authentication(self, user):
|
||||
return "Authentication started, please enter your 2FA token."
|
||||
|
||||
def validate_token(self, user, token):
|
||||
raise NotImplementedError(f"{type(self)} does not implement validate_token()!")
|
|
@ -1 +1,2 @@
|
|||
from core.models.files import *
|
||||
from core.models.profiles import *
|
12
core/models/profiles.py
Normal file
12
core/models/profiles.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from polymorphic.models import PolymorphicModel
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
|
||||
from django.db.models import OneToOneField, CASCADE, CharField
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
class Profile(PolymorphicModel):
|
||||
user = OneToOneField(get_user_model(), CASCADE)
|
||||
mobile = PhoneNumberField(blank=True)
|
||||
|
||||
class AdminProfile(Profile):
|
||||
role = CharField(max_length=64)
|
13
core/modules/otp.py
Normal file
13
core/modules/otp.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import importlib
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
providers = []
|
||||
|
||||
for module in settings.EXPEPHALON_MODULES:
|
||||
try:
|
||||
moo = importlib.import_module(f"{module}.otp")
|
||||
for provider in moo.OTPPROVIDERS:
|
||||
providers.append(provider)
|
||||
except (AttributeError, ModuleNotFoundError):
|
||||
continue
|
|
@ -2,9 +2,7 @@ import importlib
|
|||
|
||||
from django.conf import settings
|
||||
|
||||
from core.classes.navigation import NavItem, NavSection, Navigation
|
||||
|
||||
from dbsettings import getValue
|
||||
from dbsettings.functions import getValue
|
||||
|
||||
providers = []
|
||||
|
||||
|
@ -16,7 +14,7 @@ for module in settings.EXPEPHALON_MODULES:
|
|||
for provider in mos.SMSPROVIDERS:
|
||||
providers.append(provider)
|
||||
if mos.CREATE:
|
||||
modules_available = mos.CREATE
|
||||
modules_available.append(mos.CREATE)
|
||||
except (AttributeError, ModuleNotFoundError):
|
||||
continue
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ administration_section = NavSection("Administration", "")
|
|||
user_administration_item = NavItem("Administrator Users", "fa-users-cog", "backend")
|
||||
brand_administration_item = NavItem("Brands", "fa-code-branch", "backend")
|
||||
sms_administration_item = NavItem("SMS Gateway", "fa-sms", "backend")
|
||||
otp_administration_item = NavItem("Two-Factor Authentication", "fa-id-badge", "backend")
|
||||
backup_administration_item = NavItem("Backups", "fa-shield-alt", "backend")
|
||||
product_administration_item = NavItem("Products", "fa-cube", "backend")
|
||||
pgroup_administration_item = NavItem("Product Groups", "fa-cubes", "backend")
|
||||
|
@ -100,6 +101,7 @@ dbsettings_item = NavItem("Database Settings", "fa-database", "dbsettings")
|
|||
administration_section.add_item(user_administration_item)
|
||||
administration_section.add_item(brand_administration_item)
|
||||
administration_section.add_item(sms_administration_item)
|
||||
administration_section.add_item(otp_administration_item)
|
||||
administration_section.add_item(backup_administration_item)
|
||||
administration_section.add_item(product_administration_item)
|
||||
administration_section.add_item(pgroup_administration_item)
|
||||
|
|
|
@ -1,15 +1,3 @@
|
|||
"""
|
||||
Django settings for expephalon project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 3.0.4.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/3.0/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from expephalon.custom_settings import * # pylint: disable=unused-wildcard-import
|
||||
|
@ -26,6 +14,8 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'polymorphic',
|
||||
'phonenumber_field',
|
||||
'core',
|
||||
'dbsettings',
|
||||
] + EXPEPHALON_MODULES
|
||||
|
|
|
@ -2,3 +2,7 @@
|
|||
|
||||
* core
|
||||
|
||||
### smsotp
|
||||
|
||||
* core
|
||||
* any SMS provider (e.g. playsms)
|
|
@ -25,11 +25,11 @@ class PlaySMSServer(Model, BaseSMSProvider):
|
|||
|
||||
@staticmethod
|
||||
def getError(status):
|
||||
if status["error_message"]:
|
||||
return status["error_message"]
|
||||
if status["error_string"]:
|
||||
return status["error_string"]
|
||||
try:
|
||||
if status["data"]["error"]:
|
||||
return status["data"]["error"]
|
||||
if int(status["data"][0]["error"]):
|
||||
return int(status["data"][0]["error"])
|
||||
finally:
|
||||
return
|
||||
|
||||
|
@ -43,11 +43,13 @@ class PlaySMSServer(Model, BaseSMSProvider):
|
|||
if isinstance(recipients, list):
|
||||
recipients = ",".join(recipients)
|
||||
|
||||
url = 'http%s://%s/index.php?app=ws&u="%s"&h="%s"&op=pv&to=%s&msg=%s' % ("s" if self.https else "", self.host, self.username, self.token, recipients, quote_plus(message))
|
||||
url = 'http%s://%s/index.php?app=ws&u=%s&h=%s&op=pv&to=%s&msg=%s' % ("s" if self.https else "", self.host, self.username, self.token, recipients, quote_plus(message))
|
||||
|
||||
print(url)
|
||||
response = urlopen(url)
|
||||
|
||||
status = json.loads(response.read().decode())
|
||||
print(status)
|
||||
|
||||
error = PlaySMSServer.getError(status)
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
from playsms.models import PlaySMSServer
|
||||
|
||||
SMSPROVIDERS = list(PlaySMSServer.objects.all())
|
|
@ -3,3 +3,5 @@ mysqlclient
|
|||
django-storages
|
||||
boto3
|
||||
Pillow
|
||||
django-polymorphic
|
||||
django-phonenumber-field[phonenumbers]
|
||||
|
|
0
smsotp/__init__.py
Normal file
0
smsotp/__init__.py
Normal file
3
smsotp/admin.py
Normal file
3
smsotp/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
5
smsotp/apps.py
Normal file
5
smsotp/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SmsotpConfig(AppConfig):
|
||||
name = 'smsotp'
|
6
smsotp/helpers.py
Normal file
6
smsotp/helpers.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from dbsettings.functions import getValue
|
||||
|
||||
import random
|
||||
|
||||
def generate_token(len=getValue("smsotp.length", 6), chars=getValue("smsotp.chars", "0123456789")):
|
||||
return "".join(random.choices(chars, k=int(len)))
|
26
smsotp/models.py
Normal file
26
smsotp/models.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from django.db.models import Model, CharField, ForeignKey, CASCADE, DateTimeField
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from dbsettings.functions import getValue
|
||||
|
||||
from smsotp.helpers import generate_token
|
||||
from core.modules.sms import get_default_sms_provider
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class OTPToken(Model):
|
||||
token = CharField(max_length=32, default=generate_token)
|
||||
user = ForeignKey(get_user_model(), CASCADE)
|
||||
creation = DateTimeField(auto_now_add=True)
|
||||
|
||||
def send_sms(self, text=getValue("smsotp.text", "Your authentication token is:"), number=None):
|
||||
if "_TOKEN_" in text:
|
||||
text = text.replace("_TOKEN_", self.token)
|
||||
else:
|
||||
text = f"{text} {self.token}"
|
||||
|
||||
number = number or self.user.profile.mobile
|
||||
|
||||
provider = get_default_sms_provider()
|
||||
|
||||
provider.sendSMS(number, text)
|
40
smsotp/otp.py
Normal file
40
smsotp/otp.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from core.classes.otp import BaseOTPProvider
|
||||
from smsotp.models import OTPToken
|
||||
from core.modules.sms import get_default_sms_provider
|
||||
|
||||
from dbsettings.functions import getValue
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
class SMSOTP(BaseOTPProvider):
|
||||
@property
|
||||
def get_name(self):
|
||||
return "SMS OTP"
|
||||
|
||||
def create_token(self, user):
|
||||
token = OTPToken.objects.create(user=user)
|
||||
try:
|
||||
token.send_sms()
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return bool(get_default_sms_provider())
|
||||
|
||||
def start_authentication(self, user):
|
||||
if self.create_token(user):
|
||||
return "We have sent you an SMS containing your authentication token."
|
||||
else:
|
||||
return "An error has occurred, please try again later or contact the administrator."
|
||||
|
||||
def validate_token(self, user, token):
|
||||
try:
|
||||
max_age = timezone.now() - timezone.timedelta(seconds=int(getValue("smsotp.max_age", "300")))
|
||||
OTPToken.objects.get(user=user, token=token, creation__gte=max_age).delete()
|
||||
return True
|
||||
except OTPToken.DoesNotExist:
|
||||
return False
|
||||
|
||||
OTPPROVIDERS = [SMSOTP]
|
3
smsotp/tests.py
Normal file
3
smsotp/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
smsotp/views.py
Normal file
3
smsotp/views.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
26
templates/backend/login.html
Normal file
26
templates/backend/login.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<link rel="stylesheet" href="{% static "backend/css/login.css">
|
||||
<link href="https://fontproxy.kumi.systems/css?family=Ubuntu" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="https://fa.kumi.systems/css/all.css">
|
||||
<title>Sign in</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">
|
||||
<p class="sign" align="center">Sign in</p>
|
||||
{% csrf_token %}
|
||||
<form class="form1">
|
||||
<input class="un " type="text" align="center" name="username" placeholder="Username">
|
||||
<input class="pass" type="password" align="center" name="password" placeholder="Password">
|
||||
<a class="submit" align="center">Sign in</a>
|
||||
<p class="forgot" align="center"><a href="#">Forgot Password?</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in a new issue