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']:
|
||||
return False
|
||||
|
||||
if pass_field not in ['password', 'otp']:
|
||||
if pass_field not in ['password', 'otp', 'ott']:
|
||||
return False
|
||||
|
||||
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 string
|
||||
from datetime import timedelta
|
||||
|
||||
# import service_clients
|
||||
|
|
@ -112,6 +113,9 @@ class User(AbstractUser):
|
|||
otp_expire = models.DateTimeField(_('otp expire'), blank=True, null=True)
|
||||
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)
|
||||
balance = models.IntegerField(_('balance'), default=0)
|
||||
|
||||
|
|
@ -144,9 +148,22 @@ class User(AbstractUser):
|
|||
self.otp_expire = timezone.now() + timedelta(minutes=5)
|
||||
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):
|
||||
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):
|
||||
if self.otp_try <= MAX_OTP_TRY:
|
||||
result = otp and self.otp == otp and self.otp_is_valid()
|
||||
|
|
@ -163,6 +180,20 @@ class User(AbstractUser):
|
|||
self.save()
|
||||
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):
|
||||
result = False
|
||||
if field == 'otp':
|
||||
|
|
@ -171,6 +202,9 @@ class User(AbstractUser):
|
|||
elif field == 'password':
|
||||
result = self.check_password(value)
|
||||
|
||||
elif field == 'ott':
|
||||
result = self.check_ott(value)
|
||||
|
||||
if result:
|
||||
self.last_login = timezone.now()
|
||||
self.save()
|
||||
|
|
|
|||
|
|
@ -84,6 +84,18 @@ class RequestOTPSerializer(serializers.ModelSerializer):
|
|||
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):
|
||||
phone_number = serializers.CharField(required=True, validators=[phone_number_validator])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from django.urls import path
|
||||
from django.contrib.auth.views import LogoutView
|
||||
from .views import UserListView, UserDetailView, AccountView, RequestOTPView, ChangePasswordView, \
|
||||
OTPLoginView, ProfileDetailView, ProfileUpdateView
|
||||
OTPLoginView, ProfileDetailView, ProfileUpdateView, RequestOTTView
|
||||
|
||||
app_name = "users"
|
||||
|
||||
|
|
@ -14,5 +14,6 @@ urlpatterns = [
|
|||
path('api/users/', UserListView.as_view(), name='user_list_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_ott/', RequestOTTView.as_view(), name='request_ott_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.models import User
|
||||
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
|
||||
|
||||
UserModel = get_user_model()
|
||||
|
|
@ -63,6 +63,22 @@ class RequestOTPView(generics.CreateAPIView):
|
|||
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):
|
||||
serializer_class = UserInquirySerializer
|
||||
permission_classes = [IsAuthenticatedOrTokenHasScope]
|
||||
|
|
|
|||
37
client.py
37
client.py
|
|
@ -18,7 +18,7 @@ class ApiClient():
|
|||
|
||||
self.phone_number = phone_number
|
||||
|
||||
def login(self, password):
|
||||
def otp_login(self, password):
|
||||
phone_number = self.phone_number
|
||||
|
||||
data = {
|
||||
|
|
@ -26,7 +26,7 @@ class ApiClient():
|
|||
"username": phone_number,
|
||||
"password": password,
|
||||
# "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 = (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)
|
||||
|
|
@ -41,6 +41,28 @@ class ApiClient():
|
|||
|
||||
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):
|
||||
if files:
|
||||
header = {}
|
||||
|
|
@ -144,6 +166,10 @@ class ApiClient():
|
|||
# return response and 'code' in response, response
|
||||
|
||||
# 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):
|
||||
data = {
|
||||
'delay': delay,
|
||||
|
|
@ -191,6 +217,9 @@ class ApiClient():
|
|||
|
||||
client = ApiClient('+989106853582')
|
||||
client.request_otp()
|
||||
client.login('12345')
|
||||
print(client.create_invoice(2000, 10))
|
||||
client.otp_login('12345')
|
||||
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'))
|
||||
|
|
|
|||
|
|
@ -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