get application token
This commit is contained in:
parent
7a6576fce1
commit
adf3d24bc5
8 changed files with 122 additions and 46 deletions
|
|
@ -42,7 +42,7 @@ class OAuth2Validator(BaseOAuth2Validator): # pylint: disable=w0223
|
||||||
if user_field not in ['phone_number', 'username', 'email']:
|
if user_field not in ['phone_number', 'username', 'email']:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if pass_field not in ['password', 'otp']:
|
if pass_field not in ['password', 'otp', 'ott']:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not username or not password:
|
if not username or not password:
|
||||||
|
|
|
||||||
23
apps/users/migrations/0003_user_ott_user_ott_expire.py
Normal file
23
apps/users/migrations/0003_user_ott_user_ott_expire.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.1 on 2022-09-15 07:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0002_user_email_verified'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='ott',
|
||||||
|
field=models.CharField(blank=True, max_length=36, null=True, verbose_name='one time token'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='ott_expire',
|
||||||
|
field=models.DateTimeField(blank=True, null=True, verbose_name='otp expire'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import random
|
import random
|
||||||
|
import string
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
# import service_clients
|
# import service_clients
|
||||||
|
|
@ -112,6 +113,9 @@ class User(AbstractUser):
|
||||||
otp_expire = models.DateTimeField(_('otp expire'), blank=True, null=True)
|
otp_expire = models.DateTimeField(_('otp expire'), blank=True, null=True)
|
||||||
otp_try = models.IntegerField(_('otp try'), blank=True, null=True, default=0)
|
otp_try = models.IntegerField(_('otp try'), blank=True, null=True, default=0)
|
||||||
|
|
||||||
|
ott = models.CharField(_('one time token'), max_length=36, blank=True, null=True)
|
||||||
|
ott_expire = models.DateTimeField(_('otp expire'), blank=True, null=True)
|
||||||
|
|
||||||
last_checkout_request = models.DateTimeField(_('otp expire'), max_length=30, blank=True, null=True)
|
last_checkout_request = models.DateTimeField(_('otp expire'), max_length=30, blank=True, null=True)
|
||||||
balance = models.IntegerField(_('balance'), default=0)
|
balance = models.IntegerField(_('balance'), default=0)
|
||||||
|
|
||||||
|
|
@ -144,9 +148,22 @@ class User(AbstractUser):
|
||||||
self.otp_expire = timezone.now() + timedelta(minutes=5)
|
self.otp_expire = timezone.now() + timedelta(minutes=5)
|
||||||
self.otp_try = 0
|
self.otp_try = 0
|
||||||
|
|
||||||
|
def set_ott(self):
|
||||||
|
# TODO: get and set application
|
||||||
|
if settings.DEBUG or self.phone_number in DEVELOPMENT_PHONE_NUMBERS:
|
||||||
|
self.ott = 'abcdef12345'
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.ott = ''.join(random.choice(string.ascii_letters+string.digits+string.punctuation) for _ in range(32))
|
||||||
|
|
||||||
|
self.ott_expire = timezone.now() + timedelta(seconds=300)
|
||||||
|
|
||||||
def otp_is_valid(self):
|
def otp_is_valid(self):
|
||||||
return bool(self.otp and timezone.now() <= self.otp_expire)
|
return bool(self.otp and timezone.now() <= self.otp_expire)
|
||||||
|
|
||||||
|
def ott_is_valid(self):
|
||||||
|
return bool(self.ott and timezone.now() <= self.ott_expire)
|
||||||
|
|
||||||
def check_otp(self, otp):
|
def check_otp(self, otp):
|
||||||
if self.otp_try <= MAX_OTP_TRY:
|
if self.otp_try <= MAX_OTP_TRY:
|
||||||
result = otp and self.otp == otp and self.otp_is_valid()
|
result = otp and self.otp == otp and self.otp_is_valid()
|
||||||
|
|
@ -163,6 +180,20 @@ class User(AbstractUser):
|
||||||
self.save()
|
self.save()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def check_ott(self, ott:str):
|
||||||
|
ott = ott.strip()
|
||||||
|
if ott and self.ott == ott and self.ott_is_valid():
|
||||||
|
self.ott = None
|
||||||
|
self.date_joined = timezone.now()
|
||||||
|
result = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.ott = None
|
||||||
|
result = False
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
return result
|
||||||
|
|
||||||
def check_auth(self, field, value):
|
def check_auth(self, field, value):
|
||||||
result = False
|
result = False
|
||||||
if field == 'otp':
|
if field == 'otp':
|
||||||
|
|
@ -171,6 +202,9 @@ class User(AbstractUser):
|
||||||
elif field == 'password':
|
elif field == 'password':
|
||||||
result = self.check_password(value)
|
result = self.check_password(value)
|
||||||
|
|
||||||
|
elif field == 'ott':
|
||||||
|
result = self.check_ott(value)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
self.last_login = timezone.now()
|
self.last_login = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,18 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class RequestOTTSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = (
|
||||||
|
'ott',
|
||||||
|
'ott_expire',
|
||||||
|
)
|
||||||
|
|
||||||
|
read_only_fields = ['ott', 'otp_expire']
|
||||||
|
write_only_fields = []
|
||||||
|
|
||||||
|
|
||||||
class UserInquirySerializer(serializers.ModelSerializer):
|
class UserInquirySerializer(serializers.ModelSerializer):
|
||||||
phone_number = serializers.CharField(required=True, validators=[phone_number_validator])
|
phone_number = serializers.CharField(required=True, validators=[phone_number_validator])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.contrib.auth.views import LogoutView
|
from django.contrib.auth.views import LogoutView
|
||||||
from .views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \
|
from .views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \
|
||||||
OTPLoginView, ProfileDetailView, ProfileUpdateView
|
OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView
|
||||||
|
|
||||||
app_name = "users"
|
app_name = "users"
|
||||||
|
|
||||||
|
|
@ -14,5 +14,6 @@ urlpatterns = [
|
||||||
path('api/users/', UserListView.as_view(), name='user_list_api'),
|
path('api/users/', UserListView.as_view(), name='user_list_api'),
|
||||||
path('api/users/<uuid>/', UserDetailView.as_view(), name='user_detail_api'),
|
path('api/users/<uuid>/', UserDetailView.as_view(), name='user_detail_api'),
|
||||||
path('api/request_otp/', RequestOTPView.as_view(), name='request_otp_api'),
|
path('api/request_otp/', RequestOTPView.as_view(), name='request_otp_api'),
|
||||||
|
path('api/request_ott/', RequestOTTView.as_view(), name='request_ott_api'),
|
||||||
path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'),
|
path('api/change_password/', ChangePasswordView.as_view(), name='change_password_api'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRe
|
||||||
from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm
|
from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
from apps.users.provinces_and_cities import State
|
from apps.users.provinces_and_cities import State
|
||||||
from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, \
|
from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer,\
|
||||||
ChangePasswordSerializer, UserInquirySerializer
|
ChangePasswordSerializer, UserInquirySerializer
|
||||||
|
|
||||||
UserModel = get_user_model()
|
UserModel = get_user_model()
|
||||||
|
|
@ -63,6 +63,22 @@ class RequestOTPView(generics.CreateAPIView):
|
||||||
required_scopes = []
|
required_scopes = []
|
||||||
|
|
||||||
|
|
||||||
|
class RequestOTTView(generics.RetrieveAPIView):
|
||||||
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
required_scopes = ['accounts.account:request_ott']
|
||||||
|
serializer_class = RequestOTTSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
# TODO: move it to query set
|
||||||
|
user = self.get_object()
|
||||||
|
user.set_ott()
|
||||||
|
user.save()
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class UserInquiryView(generics.CreateAPIView):
|
class UserInquiryView(generics.CreateAPIView):
|
||||||
serializer_class = UserInquirySerializer
|
serializer_class = UserInquirySerializer
|
||||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||||
|
|
|
||||||
37
client.py
37
client.py
|
|
@ -18,7 +18,7 @@ class ApiClient():
|
||||||
|
|
||||||
self.phone_number = phone_number
|
self.phone_number = phone_number
|
||||||
|
|
||||||
def login(self, password):
|
def otp_login(self, password):
|
||||||
phone_number = self.phone_number
|
phone_number = self.phone_number
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
|
|
@ -26,7 +26,7 @@ class ApiClient():
|
||||||
"username": phone_number,
|
"username": phone_number,
|
||||||
"password": password,
|
"password": password,
|
||||||
# "scope": 'introspection',
|
# "scope": 'introspection',
|
||||||
"scope": 'introspection accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
"scope": 'introspection accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||||
"auth_fields": 'phone_number:otp'
|
"auth_fields": 'phone_number:otp'
|
||||||
}
|
}
|
||||||
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||||
|
|
@ -41,6 +41,28 @@ class ApiClient():
|
||||||
|
|
||||||
return auth_data
|
return auth_data
|
||||||
|
|
||||||
|
def ott_login(self, token):
|
||||||
|
phone_number = self.phone_number
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"grant_type": "password",
|
||||||
|
"username": phone_number,
|
||||||
|
"password": token,
|
||||||
|
"scope": 'introspection accounts.account:request_ott accounts.profile:list accounts.profile:retrieve accounts.account:retrieve accounts.account:update accounts.profile:inquiry accounts.account:change_password wallet.transaction:list wallet.invoice:create wallet.transaction:retrieve wallet.invoice:pay wallet.invoice:receipt wallet.deposit:submit wallet.deposit:verify wallet.withdraw:submit wallet.withdraw:verify ',
|
||||||
|
"auth_fields": 'phone_number:ott'
|
||||||
|
}
|
||||||
|
auth = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||||
|
|
||||||
|
response = requests.post(f'{API_URI}/oauth2/token/',
|
||||||
|
data=data,
|
||||||
|
auth=auth)
|
||||||
|
|
||||||
|
auth_data = response.json()
|
||||||
|
if 'access_token' in auth_data:
|
||||||
|
self.auth_data = auth_data
|
||||||
|
|
||||||
|
return auth_data
|
||||||
|
|
||||||
def _request(self, path, data=None, files=None, method='get', with_auth=True, encode=True):
|
def _request(self, path, data=None, files=None, method='get', with_auth=True, encode=True):
|
||||||
if files:
|
if files:
|
||||||
header = {}
|
header = {}
|
||||||
|
|
@ -144,6 +166,10 @@ class ApiClient():
|
||||||
# return response and 'code' in response, response
|
# return response and 'code' in response, response
|
||||||
|
|
||||||
# create application invoice
|
# create application invoice
|
||||||
|
def get_application_token(self):
|
||||||
|
response = self._request('users/api/request_ott/', method='get')
|
||||||
|
return response and 'code' in response, response
|
||||||
|
|
||||||
def create_invoice(self, amount, delay=0):
|
def create_invoice(self, amount, delay=0):
|
||||||
data = {
|
data = {
|
||||||
'delay': delay,
|
'delay': delay,
|
||||||
|
|
@ -191,6 +217,9 @@ class ApiClient():
|
||||||
|
|
||||||
client = ApiClient('+989106853582')
|
client = ApiClient('+989106853582')
|
||||||
client.request_otp()
|
client.request_otp()
|
||||||
client.login('12345')
|
client.otp_login('12345')
|
||||||
print(client.create_invoice(2000, 10))
|
print(client.get_application_token())
|
||||||
|
print(client.ott_login('abcdef12345'))
|
||||||
|
|
||||||
|
# print(client.create_invoice(2000, 10))
|
||||||
# print(client.pay_invoice('7967a76a-5de1-48b2-92ac-1b0f39f7223b'))
|
# print(client.pay_invoice('7967a76a-5de1-48b2-92ac-1b0f39f7223b'))
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Login</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'bootstrap-5.1.3-dist/css/bootstrap.min.css' %}">
|
|
||||||
<link href="{% static 'bootstrap-icons-1.7.2/bootstrap-icons.css' %}" rel="stylesheet">
|
|
||||||
<script src="{% static 'jquery-3.6.0.min.js' %}"></script>
|
|
||||||
<script src="{% static 'bootstrap-5.1.3-dist/js/bootstrap.bundle.min.js' %}"></script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'login/css/util.css' %}">
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static 'login/css/main.css' %}">
|
|
||||||
<link href="{% static 'fonts.css' %}" rel="stylesheet">
|
|
||||||
<link href="{% static 'main.css' %}" rel="stylesheet">
|
|
||||||
<!--===============================================================================================-->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="limiter">
|
|
||||||
<div class="container-login100">
|
|
||||||
<div class="wrap-login100 p-t-50 p-b-90">
|
|
||||||
{# <div class="text-center pb-5">#}
|
|
||||||
{# <img src="{% static 'image/arian_saeed.png' %}" class="img-fluid">#}
|
|
||||||
{# </div>#}
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Loading…
Add table
Reference in a new issue