From bac279ae9efd51908b43af6e86f6ee52eb70bf24 Mon Sep 17 00:00:00 2001 From: b1ek Date: Tue, 16 May 2023 23:40:32 +1000 Subject: [PATCH 1/9] basic registration and auth tokens --- .../pairent_app/migrations/0005_user.py | 25 +++++++++++----- pairent_backend/pairent_app/models.py | 8 ++++- pairent_backend/pairent_app/views.py | 30 ++++++++++++------- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/pairent_backend/pairent_app/migrations/0005_user.py b/pairent_backend/pairent_app/migrations/0005_user.py index f3951ef..0fa7ad4 100644 --- a/pairent_backend/pairent_app/migrations/0005_user.py +++ b/pairent_backend/pairent_app/migrations/0005_user.py @@ -17,22 +17,31 @@ class Migration(migrations.Migration): name='User', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('favorites_apartments', models.CharField(max_length=100, help_text="Избранные квартиры (CSV)")), - ('comparison_apartments', models.CharField(max_length=100, help_text="Квартиры для сравнения (CSV)")), - ('name', models.CharField(max_length=256, help_text='ФИО Пользователя')), - ('date_of_birth', models.DateField(help_text='Дата рождения пользователя')), - ('about_me', models.CharField(max_length=1000, help_text='Поле "О Себе"')), - ('gender', models.CharField(max_length=1, help_text='Пол пользователя (f,m,n,?)')), + ('favorites_apartments', models.CharField(max_length=100, help_text="Избранные квартиры (CSV)", null=True)), + ('comparison_apartments', models.CharField(max_length=100, help_text="Квартиры для сравнения (CSV)", null=True)), + ('name', models.CharField(max_length=500, help_text='ФИО Пользователя', null=True)), + ('date_of_birth', models.DateField(help_text='Дата рождения пользователя', null=True)), + ('about_me', models.CharField(max_length=1000, help_text='Поле "О Себе"', null=True)), + ('gender', models.CharField(max_length=1, help_text='Пол пользователя (f,m,n,?)', null=True)), ('phone', models.CharField(max_length=30, help_text='Телефон пользователя в международном формате (+00000000)', null=True)), ('email', models.CharField(max_length=1000, help_text='Почтовый ящик пользователя в формате user@example.com', null=True)), ('telegram', models.CharField(max_length=1000, help_text='Телеграм пользователя', null=True)), ('discord', models.CharField(max_length=1000, help_text='Дискорд ник пользователя', null=True)), ('city', models.CharField(max_length=1000, help_text='Город пользователя', null=True)), ('role', models.CharField(max_length=1, help_text='Роль пользователя (s - student, a - admin, m - moderator)', null=False)), - ('photo_provider', models.CharField(max_length=100, verbose_name='Сервис, из которого загружается фотография пользователя (VVSU, GRAVATAR)')), + ('photo_provider', models.CharField(max_length=100, verbose_name='Сервис, из которого загружается фотография пользователя (VVSU, GRAVATAR)', null=True)), ('openid_addr', models.CharField(max_length=1000, null=False, help_text='Адрес Open ID Connect (login@provider.com, для ВВГУ - login@vvsu.ru)')), - ('openid_id', models.CharField(max_length=5000, verbose_name='ID Пользователя в системе провайдера авторизации (скорее всего ВВГУ)')) + ('openid_id', models.CharField(max_length=5000, null=False, verbose_name='ID Пользователя в системе провайдера авторизации (скорее всего ВВГУ)')) + ] + ), + migrations.CreateModel( + name='AuthTokens', + fields=[ + ('user', models.BigIntegerField(null=False, verbose_name='ID Пользователя, которому принадлежит токен')), + ('key', models.TextField(verbose_name='Ключ API')), + ('expires', models.BigIntegerField(verbose_name='Когда ключ истечет (Unix timestamp)')), + ('ip', models.CharField(max_length=16, verbose_name='IP, с которого был создан ключ')) ] ) ] diff --git a/pairent_backend/pairent_app/models.py b/pairent_backend/pairent_app/models.py index 68fff93..017488f 100644 --- a/pairent_backend/pairent_app/models.py +++ b/pairent_backend/pairent_app/models.py @@ -129,4 +129,10 @@ class PsychTestAnswers(models.Model): class Meta: verbose_name = "Ответ на психологический тест" - verbose_name_plural = "Ответы на психологический тест" \ No newline at end of file + verbose_name_plural = "Ответы на психологический тест" + +class AuthTokens(models.Model): + user = models.BigIntegerField(null=False, verbose_name='ID Пользователя, которому принадлежит токен'); + key = models.TextField(verbose_name='Ключ API'); + expires = models.BigIntegerField(verbose_name='Когда ключ истечет (Unix timestamp)'); + ip = models.CharField(max_length=16, verbose_name='IP, с которого был создан ключ'); diff --git a/pairent_backend/pairent_app/views.py b/pairent_backend/pairent_app/views.py index b474033..7b4de4e 100644 --- a/pairent_backend/pairent_app/views.py +++ b/pairent_backend/pairent_app/views.py @@ -171,12 +171,12 @@ def VVSUAuthProxy(req: Request): return resp; -def regiserUser(oid, provider_id, name, date_of_birth): +def register(oid, provider_id, name): user = User( favorites_apartments='', comparison_apartments='', name=name, - date_of_birth=date_of_birth, + # date_of_birth=, about_me='', gender='?', phone='+00000', @@ -188,7 +188,9 @@ def regiserUser(oid, provider_id, name, date_of_birth): # photo_provider=, openid_addr=oid, openid_id=provider_id, - ) + ); + user.save(); + return user; def get_oauth_token(remote, data): return requests.post(remote + '/oauth2/token', data, @@ -234,32 +236,40 @@ class UserLogin(APIView): # 'client_secret': 'U8y@uPVee6Q^*729esHTo4Vd' # }); - auth_data = {'access_token': '5kHvrjy91LJgJLKitejBBG24c7JiX45tEstKVHRpfHc._WQDwQ2F13aytbGFjlGnjXJeUWcDD1V3om3cRW0IujM', 'expires_in': 3600, 'id_token': 'eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpoeWRyYS5vcGVuaWQuaWQtdG9rZW4iLCJ0eXAiOiJKV1QifQ.eyJhY3IiOiIwIiwiYXRfaGFzaCI6IjRMR1dRekxVaXFodUVTYjU0QWFIM0EiLCJhdWQiOlsiaXQtaHViLWNsaWVudCJdLCJhdXRoX3RpbWUiOjE2ODQyMzc4MDksImNhbGxiYWNrX3VybCI6IiIsImV4cCI6MTY4NDI0MTQ1NSwiZmFtaWx5X25hbWUiOiLQn9GD0YHRgtC-0LLQsNC70L7QsiIsImdpdmVuX25hbWUiOiLQndC40LrQuNGC0LAiLCJpYXQiOjE2ODQyMzc4NTUsImlkIjoiMDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzIiwiaXNzIjoiaHR0cHM6Ly93d3cudnZzdS5ydS9jb25uZWN0LyIsImp0aSI6IjEzMTBhNzcwLWFhZWUtNGExYS1hMTc1LWM3MzY3ZWM0ZjVhNyIsImxvZ2luIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJvcGVuaWQiOiJodHRwczovL29wZW5pZC52dnN1LnJ1L2JsZWtfXyIsInBpY3R1cmUiOiJodHRwczovL3d3dy52dnN1LnJ1L29pc2twL3Bob3RvL3B0aC5hc3A_SUQ9MDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzXHUwMDI2IiwicHJvZmlsZV91cmwiOm51bGwsInJhdCI6MTY4NDIzNzc5Nywic2lkIjoiOTYyYzg0OGYtZThkNS00ZDJjLWEwZmEtYjI5YmU3YjBlODAxIiwic3ViIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJzdXJuYW1lIjoi0J_Rg9GB0YLQvtCy0LDQu9C-0LIiLCJ0aXRsZSI6ItCh0YLRg9C00LXQvdGCIiwidnZzdV9JZEVtcGwiOm51bGwsInZ2c3VfSWRTdHVkIjoiMTk3MDgwIiwidnZzdV9JZFVzZXIiOjE5MDQ4OSwidnZzdV9sb2dpbiI6ImJsZWtfXyJ9.A4BiOxpOqnesSiTGRdcTsC-lGhSABswivpUovD9EOdYmqKW753VlLcXQxfBPcfmq8Fdf7RmVvXTXPXYqkX7AKxQT-yUUm7XtJHCb85g2YfL64cjTP2sFYD6wPIU9nzXbCrsgKqKubY3p16Dn9VyrBCXE9N6jdbuNOFbWMLPLPlp7U5fx2SzVGaBMUONlTf8KiLkcisQoN4c_rPGqdi38gzhLf7WGEiKLOldXH1q-s_kPeObFvcdbsFrrnDPnJtdqBx8SF02wqJsrZlBiB9Hl-d6sSJYLZZWumFhS-qscfwRlTEZKqC-hWF5c9R8CUYewk89JxRvCcKrHZvPMip9j9vJF1_OjkSrC5EkGaprl765FgVPEBJqXj9LjGRkTOYfYUFAAMia_HhjtinQFp6XJ-Rh3JrmIfLAQ7DEUSOldMQ1xUw9GeHo_0sIsnjaM6lVx6M_SiDTWihxNu58DiI8tmvkdw7in95OJRoJZ30EhR3SGYsK3b51qdYK1aieufJHX40bN_S1gc84pisTg58z-zC5kGsjsZNv6gRSTO4oOpZMK1FMjv7HyasSMWEu-J052X4Qxquj4pWglpiGQNt3-E0jZUUjqmZ0-7AYiyEC_3IItBqWrve-LTXRF5faIZB5v3F3urY6Qjgn93m_AoK1oujfNAPk8WOLTv419CuC2fAc', 'scope': 'openid vvsu_IdUser vvsu_IdEmpl vvsu_IdStud vvsu_login given_name family_name', 'token_type': 'bearer'} + auth_data = {'access_token': 'gcH96CSYQBeiq9te1lpJV4T9mBH4UabT4_m6fJQFQK4.K4GA7sXFtBEM26kDladZjZ8phsI3aRPmqu5oRts4Csg', 'expires_in': 3600, 'id_token': 'eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpoeWRyYS5vcGVuaWQuaWQtdG9rZW4iLCJ0eXAiOiJKV1QifQ.eyJhY3IiOiIwIiwiYXRfaGFzaCI6ImJIZS1pWmlvX2Npa3diOFc3bnBkbEEiLCJhdWQiOlsiaXQtaHViLWNsaWVudCJdLCJhdXRoX3RpbWUiOjE2ODQyNDM0NjUsImNhbGxiYWNrX3VybCI6IiIsImV4cCI6MTY4NDI0NzA3MywiZmFtaWx5X25hbWUiOiLQn9GD0YHRgtC-0LLQsNC70L7QsiIsImdpdmVuX25hbWUiOiLQndC40LrQuNGC0LAiLCJpYXQiOjE2ODQyNDM0NzMsImlkIjoiMDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzIiwiaXNzIjoiaHR0cHM6Ly93d3cudnZzdS5ydS9jb25uZWN0LyIsImp0aSI6IjU5M2FiYTQzLTU4OTQtNGZmNy1iMmU1LTdmOWZkYTZjZjFhZSIsImxvZ2luIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJvcGVuaWQiOiJodHRwczovL29wZW5pZC52dnN1LnJ1L2JsZWtfXyIsInBpY3R1cmUiOiJodHRwczovL3d3dy52dnN1LnJ1L29pc2twL3Bob3RvL3B0aC5hc3A_SUQ9MDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzXHUwMDI2IiwicHJvZmlsZV91cmwiOm51bGwsInJhdCI6MTY4NDI0MzQ1NCwic2lkIjoiMzEwZjU5MWEtZmNjYy00NzY3LTkzMmItYjM3OTQyZmFmMTA1Iiwic3ViIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJzdXJuYW1lIjoi0J_Rg9GB0YLQvtCy0LDQu9C-0LIiLCJ0aXRsZSI6ItCh0YLRg9C00LXQvdGCIiwidnZzdV9JZEVtcGwiOm51bGwsInZ2c3VfSWRTdHVkIjoiMTk3MDgwIiwidnZzdV9JZFVzZXIiOjE5MDQ4OSwidnZzdV9sb2dpbiI6ImJsZWtfXyJ9.mClShf1lzGoKarsshafM6H2_57wrINbLSUjDQrEOAICN0V6TMNmC2zevgjxBbMl3BTIWhGJ37SNViyGvdNjPeG_S32TBr0m_vJEddZbHLzO7U7J2vqYVkiFQl8hziZkvhZUboSCu71aWexvN6rtX5grxIPAZswgGP4Mszg7ueQlhybgDELVg-UG-2OVH01-ynsfoZbaPYN6_8x44FJDUiltFbdx57kD8OEh4CdqEPTl3rL2T1U04cfNY0Ij2ivo9esEyAmuuXQCmwn_YwHO3TQc0S2Bq6DeIWa4gauynxGjPl2tf4fcyz-XOVWGeMNIwXCHvIDB_aHsZromG3UV2gY3ji-RlkEq81mYzFjOwB-LArkJQ68zQZlu5cFKqtWvZOzKqCzDDRUvfiRTu3OexQse_g10EeMi7vSeocGnfETlq5utar05gFGY-DxSaFYNCKzxqqS8V78d5aRFrWcQNbE6CVpKZPbZBBEQ-ItX-wh1FEyL3Uw-MsDztwJu6p_ftwRZLF0lk3ECFlbFt4NzzutFYqwS1s5ZoSZa-ylLY8PsZdr9gj58jBYD8c1foXZ9I_KzC_bYDOyUQfjec5njxGWN3828TvySclHkXMUgQxCM16OmPq8MICk_tfhqOSezcs0JpXIEtHHn0h9HNavZuhMTIaTWErYRIIxEPgtBn8r8', 'scope': 'openid vvsu_IdUser vvsu_IdEmpl vvsu_IdStud vvsu_login given_name family_name', 'token_type': 'bearer'} + + # print(auth_data); + + if ('error' in auth_data): + return JsonResponse(auth_data); user = None; new_user = False; - print(auth_data); + # vvsu_data = get_oauth_data('https://vvsu.ru/connect', auth_data['access_token']); + vvsu_data = {'acr': '0', 'aud': ['it-hub-client'], 'auth_time': 1684243465, 'callback_url': '', 'family_name': 'Пустовалов', 'given_name': 'Никита', 'iat': 1684243466, 'id': '096C78CD-4943-4D57-BC6D-5CDE12F686E3', 'iss': 'https://www.vvsu.ru/connect/', 'login': 'https://openid.vvsu.ru/blek__', 'openid': 'https://openid.vvsu.ru/blek__', 'picture': 'https://www.vvsu.ru/oiskp/photo/pth.asp?ID=096C78CD-4943-4D57-BC6D-5CDE12F686E3&', 'profile_url': None, 'rat': 1684243454, 'sub': 'https://openid.vvsu.ru/blek__', 'surname': 'Пустовалов', 'title': 'Студент', 'vvsu_IdEmpl': None, 'vvsu_IdStud': '197080', 'vvsu_IdUser': 190489, 'vvsu_login': 'blek__'} - return JsonResponse(get_oauth_data('https://vvsu.ru/connect', auth_data['access_token'])); + if ('error' in vvsu_data): + res = JsonResponse(vvsu_data); + res.status_code = 500; + return res; req.session['auth_data'] = vvsu_data; if ('error' in vvsu_data): res = JsonResponse(vvsu_data); - res.status_code = cb.status_code; + res.status_code = 500; return res vvsu_data['vvsu_login'] += '@vvsu.ru'; try: user = User.objects.get(openid_addr=vvsu_data['vvsu_login']); except User.DoesNotExist: - registerUser(vvsu_data['vvsu_login'], cb.id, f'{cb.given_name} {cb.family_name}'); - user = User.objects.get(openid_addr=vvsu_data['vvsu_login']); + user = register(vvsu_data['vvsu_login'], vvsu_data['id'], f"{vvsu_data['given_name']} {vvsu_data['family_name']}"); new_user = True; return JsonResponse({ - 'user_data': user, + 'user_data': PublicUserSerializer(user).data, 'new_user': new_user }); From d35b50ad705b4c65820bec891849da42af763eb7 Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 01:08:04 +1000 Subject: [PATCH 2/9] add redirects on login --- pairent_backend/pairent_app/models.py | 2 +- pairent_backend/pairent_app/serializer.py | 9 ++- pairent_backend/pairent_app/views.py | 78 +++++++++++++------ pairent_backend/requirements.txt | 2 +- pairent_frontend_react/src/API/User.js | 71 ++++++++++++++++- .../src/pages/LoggedIn/index.jsx | 9 ++- 6 files changed, 142 insertions(+), 29 deletions(-) diff --git a/pairent_backend/pairent_app/models.py b/pairent_backend/pairent_app/models.py index 017488f..b03e5d8 100644 --- a/pairent_backend/pairent_app/models.py +++ b/pairent_backend/pairent_app/models.py @@ -131,7 +131,7 @@ class PsychTestAnswers(models.Model): verbose_name = "Ответ на психологический тест" verbose_name_plural = "Ответы на психологический тест" -class AuthTokens(models.Model): +class AuthToken(models.Model): user = models.BigIntegerField(null=False, verbose_name='ID Пользователя, которому принадлежит токен'); key = models.TextField(verbose_name='Ключ API'); expires = models.BigIntegerField(verbose_name='Когда ключ истечет (Unix timestamp)'); diff --git a/pairent_backend/pairent_app/serializer.py b/pairent_backend/pairent_app/serializer.py index 29416a2..221d2b8 100644 --- a/pairent_backend/pairent_app/serializer.py +++ b/pairent_backend/pairent_app/serializer.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from .models import Apartament, User +from .models import Apartament, User, AuthToken class ApartamentListSerializer(serializers.ModelSerializer): @@ -26,4 +26,9 @@ class PsychTestAddResultSerializer(serializers.ModelSerializer): class PublicUserSerializer(serializers.ModelSerializer): class Meta: model = User - exclude = ('favorites_apartments', 'comparison_apartments') \ No newline at end of file + exclude = ('favorites_apartments', 'comparison_apartments') + +class TokenSerializer(serializers.ModelSerializer): + class Meta: + model = AuthToken + fields = '__all__' \ No newline at end of file diff --git a/pairent_backend/pairent_app/views.py b/pairent_backend/pairent_app/views.py index 7b4de4e..6bd3249 100644 --- a/pairent_backend/pairent_app/views.py +++ b/pairent_backend/pairent_app/views.py @@ -10,13 +10,15 @@ from django.db.models.query import QuerySet from django.core.validators import validate_email from django.core.exceptions import ValidationError -from .models import Apartament, User, PsychTestAnswers +from .models import Apartament, User, PsychTestAnswers, AuthToken from .serializer import (ApartamentListSerializer, ApartamentDetailSerializer, PsychTestAddResultSerializer, - PublicUserSerializer) + PublicUserSerializer, + TokenSerializer) -import json, math, random, re, requests, oidc_client, base64, hashlib +import json, math, random, re, requests, oidc_client, base64, uuid, time, ipware as iplib +ipware = iplib.IpWare(); class ApartamentViewSet(viewsets.ReadOnlyModelViewSet): """Вывод списка квартир или отдельной квартиры""" @@ -185,7 +187,7 @@ def register(oid, provider_id, name): # discord=, # city=, role='s', - # photo_provider=, + photo_provider='VVSU', openid_addr=oid, openid_id=provider_id, ); @@ -206,7 +208,45 @@ def get_oauth_data(remote, key): 'User-Agent': 'curl/8.1' }).json(); +def create_auth_token(userid, ip): + + try: + token = AuthToken.objects.get(user=userid, ip=ip); + if (verify_auth_token(token.key, token.ip)): + return token; + except AuthToken.DoesNotExist: + 0 # ignore + + token = AuthToken( + user=userid, + key=str(uuid.uuid4()), + # 2 days + # vvv + expires=time.time() + 60 * 60 * 24 * 2, + ip=ip + ); + token.save(); + return token; + +def verify_auth_token(key, ip): + + try: + token = AuthToken.objects.get(key=key); + except AuthToken.DoesNotExist: + return False; + + if (token.ip != ip): + token.delete(); + return False; + + if (token.expires > time.time()): + token.delete(); + return False; + + return True; + class UserLogin(APIView): + # TODO: Remove csrf exempt when index.html is loaded through django @csrf_exempt def post(self, req: HttpRequest): @@ -227,18 +267,14 @@ class UserLogin(APIView): res.status_code = 400; return res; - # auth_data = get_oauth_token('https://vvsu.ru/connect', { - # 'grant_type': 'authorization_code', - # 'redirect_uri': 'https://pairent.vvsu.ru/sign-in/', - # 'code': data['code'], - # 'code_verifier': data['code_verifier'], - # 'client_id': 'it-hub-client', - # 'client_secret': 'U8y@uPVee6Q^*729esHTo4Vd' - # }); - - auth_data = {'access_token': 'gcH96CSYQBeiq9te1lpJV4T9mBH4UabT4_m6fJQFQK4.K4GA7sXFtBEM26kDladZjZ8phsI3aRPmqu5oRts4Csg', 'expires_in': 3600, 'id_token': 'eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpoeWRyYS5vcGVuaWQuaWQtdG9rZW4iLCJ0eXAiOiJKV1QifQ.eyJhY3IiOiIwIiwiYXRfaGFzaCI6ImJIZS1pWmlvX2Npa3diOFc3bnBkbEEiLCJhdWQiOlsiaXQtaHViLWNsaWVudCJdLCJhdXRoX3RpbWUiOjE2ODQyNDM0NjUsImNhbGxiYWNrX3VybCI6IiIsImV4cCI6MTY4NDI0NzA3MywiZmFtaWx5X25hbWUiOiLQn9GD0YHRgtC-0LLQsNC70L7QsiIsImdpdmVuX25hbWUiOiLQndC40LrQuNGC0LAiLCJpYXQiOjE2ODQyNDM0NzMsImlkIjoiMDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzIiwiaXNzIjoiaHR0cHM6Ly93d3cudnZzdS5ydS9jb25uZWN0LyIsImp0aSI6IjU5M2FiYTQzLTU4OTQtNGZmNy1iMmU1LTdmOWZkYTZjZjFhZSIsImxvZ2luIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJvcGVuaWQiOiJodHRwczovL29wZW5pZC52dnN1LnJ1L2JsZWtfXyIsInBpY3R1cmUiOiJodHRwczovL3d3dy52dnN1LnJ1L29pc2twL3Bob3RvL3B0aC5hc3A_SUQ9MDk2Qzc4Q0QtNDk0My00RDU3LUJDNkQtNUNERTEyRjY4NkUzXHUwMDI2IiwicHJvZmlsZV91cmwiOm51bGwsInJhdCI6MTY4NDI0MzQ1NCwic2lkIjoiMzEwZjU5MWEtZmNjYy00NzY3LTkzMmItYjM3OTQyZmFmMTA1Iiwic3ViIjoiaHR0cHM6Ly9vcGVuaWQudnZzdS5ydS9ibGVrX18iLCJzdXJuYW1lIjoi0J_Rg9GB0YLQvtCy0LDQu9C-0LIiLCJ0aXRsZSI6ItCh0YLRg9C00LXQvdGCIiwidnZzdV9JZEVtcGwiOm51bGwsInZ2c3VfSWRTdHVkIjoiMTk3MDgwIiwidnZzdV9JZFVzZXIiOjE5MDQ4OSwidnZzdV9sb2dpbiI6ImJsZWtfXyJ9.mClShf1lzGoKarsshafM6H2_57wrINbLSUjDQrEOAICN0V6TMNmC2zevgjxBbMl3BTIWhGJ37SNViyGvdNjPeG_S32TBr0m_vJEddZbHLzO7U7J2vqYVkiFQl8hziZkvhZUboSCu71aWexvN6rtX5grxIPAZswgGP4Mszg7ueQlhybgDELVg-UG-2OVH01-ynsfoZbaPYN6_8x44FJDUiltFbdx57kD8OEh4CdqEPTl3rL2T1U04cfNY0Ij2ivo9esEyAmuuXQCmwn_YwHO3TQc0S2Bq6DeIWa4gauynxGjPl2tf4fcyz-XOVWGeMNIwXCHvIDB_aHsZromG3UV2gY3ji-RlkEq81mYzFjOwB-LArkJQ68zQZlu5cFKqtWvZOzKqCzDDRUvfiRTu3OexQse_g10EeMi7vSeocGnfETlq5utar05gFGY-DxSaFYNCKzxqqS8V78d5aRFrWcQNbE6CVpKZPbZBBEQ-ItX-wh1FEyL3Uw-MsDztwJu6p_ftwRZLF0lk3ECFlbFt4NzzutFYqwS1s5ZoSZa-ylLY8PsZdr9gj58jBYD8c1foXZ9I_KzC_bYDOyUQfjec5njxGWN3828TvySclHkXMUgQxCM16OmPq8MICk_tfhqOSezcs0JpXIEtHHn0h9HNavZuhMTIaTWErYRIIxEPgtBn8r8', 'scope': 'openid vvsu_IdUser vvsu_IdEmpl vvsu_IdStud vvsu_login given_name family_name', 'token_type': 'bearer'} - - # print(auth_data); + auth_data = get_oauth_token('https://vvsu.ru/connect', { + 'grant_type': 'authorization_code', + 'redirect_uri': 'https://pairent.vvsu.ru/sign-in/', + 'code': data['code'], + 'code_verifier': data['code_verifier'], + 'client_id': 'it-hub-client', + 'client_secret': 'U8y@uPVee6Q^*729esHTo4Vd' + }); if ('error' in auth_data): return JsonResponse(auth_data); @@ -246,16 +282,13 @@ class UserLogin(APIView): user = None; new_user = False; - # vvsu_data = get_oauth_data('https://vvsu.ru/connect', auth_data['access_token']); - vvsu_data = {'acr': '0', 'aud': ['it-hub-client'], 'auth_time': 1684243465, 'callback_url': '', 'family_name': 'Пустовалов', 'given_name': 'Никита', 'iat': 1684243466, 'id': '096C78CD-4943-4D57-BC6D-5CDE12F686E3', 'iss': 'https://www.vvsu.ru/connect/', 'login': 'https://openid.vvsu.ru/blek__', 'openid': 'https://openid.vvsu.ru/blek__', 'picture': 'https://www.vvsu.ru/oiskp/photo/pth.asp?ID=096C78CD-4943-4D57-BC6D-5CDE12F686E3&', 'profile_url': None, 'rat': 1684243454, 'sub': 'https://openid.vvsu.ru/blek__', 'surname': 'Пустовалов', 'title': 'Студент', 'vvsu_IdEmpl': None, 'vvsu_IdStud': '197080', 'vvsu_IdUser': 190489, 'vvsu_login': 'blek__'} - + vvsu_data = get_oauth_data('https://vvsu.ru/connect', auth_data['access_token']); + if ('error' in vvsu_data): res = JsonResponse(vvsu_data); res.status_code = 500; return res; - req.session['auth_data'] = vvsu_data; - if ('error' in vvsu_data): res = JsonResponse(vvsu_data); res.status_code = 500; @@ -270,7 +303,8 @@ class UserLogin(APIView): return JsonResponse({ 'user_data': PublicUserSerializer(user).data, - 'new_user': new_user + 'new_user': new_user, + 'token': TokenSerializer(create_auth_token(user.id, ipware.get_client_ip(req.META)[0].exploded)).data }); class UserGet(APIView): diff --git a/pairent_backend/requirements.txt b/pairent_backend/requirements.txt index d495564..c8a891d 100644 --- a/pairent_backend/requirements.txt +++ b/pairent_backend/requirements.txt @@ -4,4 +4,4 @@ djangorestframework django-cors-headers Pillow requests -oidc-client \ No newline at end of file +python-ipware \ No newline at end of file diff --git a/pairent_frontend_react/src/API/User.js b/pairent_frontend_react/src/API/User.js index 4be7f14..1bfcce7 100644 --- a/pairent_frontend_react/src/API/User.js +++ b/pairent_frontend_react/src/API/User.js @@ -14,7 +14,70 @@ class UserLoginResponse { id; } -class User { +class IAPIObject { + /** + * Local storage key used to save data. + * @type {string} + */ + static storage_key = undefined; + + /** @returns {ThisType} */ + static restoreFromLocalStorage() { + if (storage_key !== undefined) { + throw Error('This doesn\'t support local storage'); + } + if (!window.localStorage.getItem(storage_key)) + return false; + return new APIToken(window.localStorage.getItem(storage_key)); + } + + /** + * Save this object to local storage + * @throws {QuotaExceededError} + * @returns {void} + */ + saveToLocalStorage() { + + // static this + sthis = Object.getPrototypeOf(this); + + if (sthis.storage_key !== undefined) { + throw Error('This doesn\'t support local storage'); + } + window.localStorage.setItem(sthis.storage_key, this); + } +} + +class APIToken extends IAPIObject { + + static storage_key = 'pairent_api_key'; + + constructor(data) { + this.user = data.user; + this.key = data.key; + this.expires = data.expires; + this.ip = data.ip; + } + + /** @type {number} */ + user; + + /** @type {string} */ + key; + + /** A Unix timestamp (when the token will expire) + * @type {number} + */ + expires; + + /** @type {string} */ + ip; +} + +class User extends IAPIObject { + + static storage_key = 'pairent_user_data'; + constructor(data) { for (const key in data) { this[key] = data[key]; @@ -44,7 +107,11 @@ class User { } const data = await axios.post(api_path('/api/auth/user/login'), response); - return new User(data.data); + + window.localStorage.setItem(APIToken.storage_key, data.data.token); + window.localStorage.setItem(User.storage_key, data.data.user_data); + + return data.data; } } diff --git a/pairent_frontend_react/src/pages/LoggedIn/index.jsx b/pairent_frontend_react/src/pages/LoggedIn/index.jsx index 476983b..eda1db9 100644 --- a/pairent_frontend_react/src/pages/LoggedIn/index.jsx +++ b/pairent_frontend_react/src/pages/LoggedIn/index.jsx @@ -4,6 +4,7 @@ import { HashLoader } from "react-spinners"; import { SigninResponse, UserManager } from 'oidc-client-ts'; import { User } from "../../API/User"; import FloatingBox from "../../components/UI/FloatingBox"; +import { useNavigate } from "react-router-dom"; import constants from "../../constants"; @@ -48,7 +49,13 @@ export default class LoggedIn extends React.Component { } } - console.log(await User.login({...this.response, code_verifier})); + const response = await User.login({...this.response, code_verifier}); + if (response.new_user) { + // TODO: Make the page + useNavigate('/register'); + } else { + useNavigate('/'); + } } render() { From 754bfa8ef0fa48c2cd1876c06b994ec913fddd23 Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 01:10:02 +1000 Subject: [PATCH 3/9] fix mismatching model name --- pairent_backend/pairent_app/migrations/0005_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pairent_backend/pairent_app/migrations/0005_user.py b/pairent_backend/pairent_app/migrations/0005_user.py index 0fa7ad4..43a57dd 100644 --- a/pairent_backend/pairent_app/migrations/0005_user.py +++ b/pairent_backend/pairent_app/migrations/0005_user.py @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ] ), migrations.CreateModel( - name='AuthTokens', + name='AuthToken', fields=[ ('user', models.BigIntegerField(null=False, verbose_name='ID Пользователя, которому принадлежит токен')), ('key', models.TextField(verbose_name='Ключ API')), From 601b13feb64544051aedcac1fc8a753ffa64f31c Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:10:17 +1000 Subject: [PATCH 4/9] finish off login --- pairent_backend/pairent_app/views.py | 3 ++ pairent_frontend_react/src/API/User.js | 49 ++++++++++++++----- .../src/pages/LoggedIn/index.jsx | 17 +++++-- .../src/pages/LoginPage/index.jsx | 22 +++++++-- 4 files changed, 71 insertions(+), 20 deletions(-) diff --git a/pairent_backend/pairent_app/views.py b/pairent_backend/pairent_app/views.py index 6bd3249..87f0ac2 100644 --- a/pairent_backend/pairent_app/views.py +++ b/pairent_backend/pairent_app/views.py @@ -251,6 +251,9 @@ class UserLogin(APIView): @csrf_exempt def post(self, req: HttpRequest): + # for debug purposes + # return HttpResponse("""{"user_data": {"id": 1, "name": "\u041d\u0438\u043a\u0438\u0442\u0430 \u041f\u0443\u0441\u0442\u043e\u0432\u0430\u043b\u043e\u0432", "date_of_birth": null, "about_me": "", "gender": "?", "phone": "+00000", "email": null, "telegram": null, "discord": null, "city": null, "role": "s", "photo_provider": "VVSU", "openid_addr": "blek__@vvsu.ru", "openid_id": "096C78CD-4943-4D57-BC6D-5CDE12F686E3"}, "new_user": false, "token": {"id": 2, "user": 1, "key": "e1c24581-523a-4f60-973f-02ba873b3edc", "expires": 1684423572, "ip": "127.0.0.1"}}"""); + if (req.session.has_key('auth_data')): # TODO: Return user object instead of error return JsonResponse({'error': 'already authenticated'}) diff --git a/pairent_frontend_react/src/API/User.js b/pairent_frontend_react/src/API/User.js index 1bfcce7..d48ee9b 100644 --- a/pairent_frontend_react/src/API/User.js +++ b/pairent_frontend_react/src/API/User.js @@ -21,30 +21,44 @@ class IAPIObject { */ static storage_key = undefined; - /** @returns {ThisType} */ - static restoreFromLocalStorage() { - if (storage_key !== undefined) { + static _checkStorageSupport() { + if (storage_key === undefined) { throw Error('This doesn\'t support local storage'); } + } + + _getStorageKey() { + return Object.getPrototypeOf(this).constructor.storage_key; + } + + _IcheckStorageSupport() { + if (this._getStorageKey() === undefined) { + throw Error('This doesn\'t support local storage'); + } + } + + /** @returns {ThisType} */ + static restoreFromLocalStorage() { + _checkStorageSupport(); if (!window.localStorage.getItem(storage_key)) return false; return new APIToken(window.localStorage.getItem(storage_key)); } + /** @returns {boolean} */ + static isCached() { + _checkStorageSupport(); + } + /** * Save this object to local storage * @throws {QuotaExceededError} * @returns {void} */ saveToLocalStorage() { + this._IcheckStorageSupport(); - // static this - sthis = Object.getPrototypeOf(this); - - if (sthis.storage_key !== undefined) { - throw Error('This doesn\'t support local storage'); - } - window.localStorage.setItem(sthis.storage_key, this); + window.localStorage.setItem(this._getStorageKey(), JSON.stringify(this)); } } @@ -53,6 +67,7 @@ class APIToken extends IAPIObject { static storage_key = 'pairent_api_key'; constructor(data) { + super(); this.user = data.user; this.key = data.key; this.expires = data.expires; @@ -76,9 +91,14 @@ class APIToken extends IAPIObject { class User extends IAPIObject { + isLoggedIn() { + return false; + } + static storage_key = 'pairent_user_data'; constructor(data) { + super(); for (const key in data) { this[key] = data[key]; } @@ -107,9 +127,14 @@ class User extends IAPIObject { } const data = await axios.post(api_path('/api/auth/user/login'), response); + if (data.status !== 200) { + return false; + } - window.localStorage.setItem(APIToken.storage_key, data.data.token); - window.localStorage.setItem(User.storage_key, data.data.user_data); + if (!data.data.error) { + new APIToken(data.data.token).saveToLocalStorage(); + new User(data.data.user_data).saveToLocalStorage(); + } return data.data; } diff --git a/pairent_frontend_react/src/pages/LoggedIn/index.jsx b/pairent_frontend_react/src/pages/LoggedIn/index.jsx index eda1db9..141161c 100644 --- a/pairent_frontend_react/src/pages/LoggedIn/index.jsx +++ b/pairent_frontend_react/src/pages/LoggedIn/index.jsx @@ -4,7 +4,6 @@ import { HashLoader } from "react-spinners"; import { SigninResponse, UserManager } from 'oidc-client-ts'; import { User } from "../../API/User"; import FloatingBox from "../../components/UI/FloatingBox"; -import { useNavigate } from "react-router-dom"; import constants from "../../constants"; @@ -33,12 +32,12 @@ export default class LoggedIn extends React.Component { constructor(props) { super(props); this.response = new SigninResponse(new URL(window.location.href).searchParams); - } async componentDidMount() { if (this.response.error) return; - + window.localStorage.removeItem('auth_fail'); + let code_verifier = '?'; // get code verifier for (const key in localStorage) { @@ -50,11 +49,19 @@ export default class LoggedIn extends React.Component { } const response = await User.login({...this.response, code_verifier}); + + if (response.error) { + // pass the data to LoginPage + window.localStorage.setItem('auth_fail', JSON.stringify(response)); + window.location.href = '/login'; + return; + } + if (response.new_user) { // TODO: Make the page - useNavigate('/register'); + window.location.href = '/register'; } else { - useNavigate('/'); + window.location.href = '/'; } } diff --git a/pairent_frontend_react/src/pages/LoginPage/index.jsx b/pairent_frontend_react/src/pages/LoginPage/index.jsx index 40b5ba4..578d891 100644 --- a/pairent_frontend_react/src/pages/LoginPage/index.jsx +++ b/pairent_frontend_react/src/pages/LoginPage/index.jsx @@ -45,9 +45,11 @@ export default class LoginPage extends React.Component { super(props); this.state = { - loading: false + loading: false, + error: JSON.parse(window.localStorage.getItem('auth_fail')) } + window.localStorage.removeItem('auth_fail'); this.openid = this.openid.bind(this); } @@ -56,12 +58,11 @@ export default class LoginPage extends React.Component { this.setState({loading: true}); OpenID.Log.setLogger(console); - OpenID.Log.setLevel(OpenID.Log.DEBUG); + OpenID.Log.setLevel(OpenID.Log.NONE); let client = new OpenID.UserManager(OIDCConfig); client.signinRedirect(); - } render() { @@ -84,6 +85,21 @@ export default class LoginPage extends React.Component { Вход осуществляется только через
Систему Единого Входа ВВГУ + { + this.state.error ? + + Произошла ошибка: { this.state.error.error } + { + this.state.error.error_description ? + <> +
+ {this.state.error.error_description} + + : null + } +
+ : null + } ); From ddaf8d4374b2f61e4690b080c82a165a30e33d2a Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:39:42 +1000 Subject: [PATCH 5/9] redirect from login page if already logged in --- pairent_frontend_react/src/pages/LoginPage/index.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pairent_frontend_react/src/pages/LoginPage/index.jsx b/pairent_frontend_react/src/pages/LoginPage/index.jsx index 578d891..f565b60 100644 --- a/pairent_frontend_react/src/pages/LoginPage/index.jsx +++ b/pairent_frontend_react/src/pages/LoginPage/index.jsx @@ -8,6 +8,8 @@ import FloatingBox from '../../components/UI/FloatingBox'; import * as OpenID from 'oidc-client-ts'; import constants from '../../constants'; +import { User } from '../../API/User'; + const { OIDCConfig } = constants; const LoginButton = styled(BlueButton)` @@ -66,6 +68,10 @@ export default class LoginPage extends React.Component { } render() { + + if (User.isCached()) + window.location.href = '/'; + return (
From 7d0616add761b6949683fb54537016a0032f644d Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:39:56 +1000 Subject: [PATCH 6/9] properly access static methods --- pairent_frontend_react/src/API/User.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pairent_frontend_react/src/API/User.js b/pairent_frontend_react/src/API/User.js index d48ee9b..1bed60c 100644 --- a/pairent_frontend_react/src/API/User.js +++ b/pairent_frontend_react/src/API/User.js @@ -22,7 +22,7 @@ class IAPIObject { static storage_key = undefined; static _checkStorageSupport() { - if (storage_key === undefined) { + if (this.storage_key === undefined) { throw Error('This doesn\'t support local storage'); } } @@ -40,14 +40,18 @@ class IAPIObject { /** @returns {ThisType} */ static restoreFromLocalStorage() { _checkStorageSupport(); - if (!window.localStorage.getItem(storage_key)) + if (!window.localStorage.getItem(this.storage_key)) return false; - return new APIToken(window.localStorage.getItem(storage_key)); + return new APIToken(window.localStorage.getItem(this.storage_key)); } /** @returns {boolean} */ static isCached() { - _checkStorageSupport(); + this._checkStorageSupport(); + if (window.localStorage.getItem(this.storage_key)) { + return true; + } + return false; } /** From c618c361b8c4b32c94ac193d4330f7b9c784e1c9 Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:49:34 +1000 Subject: [PATCH 7/9] change local storage to session storage in api --- pairent_frontend_react/src/API/User.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pairent_frontend_react/src/API/User.js b/pairent_frontend_react/src/API/User.js index 1bed60c..4fc42c4 100644 --- a/pairent_frontend_react/src/API/User.js +++ b/pairent_frontend_react/src/API/User.js @@ -40,15 +40,15 @@ class IAPIObject { /** @returns {ThisType} */ static restoreFromLocalStorage() { _checkStorageSupport(); - if (!window.localStorage.getItem(this.storage_key)) + if (!window.sessionStorage.getItem(this.storage_key)) return false; - return new APIToken(window.localStorage.getItem(this.storage_key)); + return new APIToken(window.sessionStorage.getItem(this.storage_key)); } /** @returns {boolean} */ static isCached() { this._checkStorageSupport(); - if (window.localStorage.getItem(this.storage_key)) { + if (window.sessionStorage.getItem(this.storage_key)) { return true; } return false; @@ -62,7 +62,7 @@ class IAPIObject { saveToLocalStorage() { this._IcheckStorageSupport(); - window.localStorage.setItem(this._getStorageKey(), JSON.stringify(this)); + window.sessionStorage.setItem(this._getStorageKey(), JSON.stringify(this)); } } From 5604c20903c4635f31640d23d16599a61c8755a8 Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:51:09 +1000 Subject: [PATCH 8/9] move IAPIObject to different module --- pairent_frontend_react/src/API/IAPIObject.js | 57 ++++++++++++++++++++ pairent_frontend_react/src/API/User.js | 54 +------------------ 2 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 pairent_frontend_react/src/API/IAPIObject.js diff --git a/pairent_frontend_react/src/API/IAPIObject.js b/pairent_frontend_react/src/API/IAPIObject.js new file mode 100644 index 0000000..e4a1af8 --- /dev/null +++ b/pairent_frontend_react/src/API/IAPIObject.js @@ -0,0 +1,57 @@ + +/** + * Basic API interaction & local caching interface + */ +class IAPIObject { + /** + * Local storage key used to save data. + * @type {string} + */ + static storage_key = undefined; + + static _checkStorageSupport() { + if (this.storage_key === undefined) { + throw Error('This doesn\'t support local storage'); + } + } + + _getStorageKey() { + return Object.getPrototypeOf(this).constructor.storage_key; + } + + _IcheckStorageSupport() { + if (this._getStorageKey() === undefined) { + throw Error('This doesn\'t support local storage'); + } + } + + /** @returns {ThisType} */ + static restoreFromLocalStorage() { + _checkStorageSupport(); + if (!window.sessionStorage.getItem(this.storage_key)) + return false; + return new APIToken(window.sessionStorage.getItem(this.storage_key)); + } + + /** @returns {boolean} */ + static isCached() { + this._checkStorageSupport(); + if (window.sessionStorage.getItem(this.storage_key)) { + return true; + } + return false; + } + + /** + * Save this object to local storage + * @throws {QuotaExceededError} + * @returns {void} + */ + saveToLocalStorage() { + this._IcheckStorageSupport(); + + window.sessionStorage.setItem(this._getStorageKey(), JSON.stringify(this)); + } +} + +export { IAPIObject }; \ No newline at end of file diff --git a/pairent_frontend_react/src/API/User.js b/pairent_frontend_react/src/API/User.js index 4fc42c4..99b84f7 100644 --- a/pairent_frontend_react/src/API/User.js +++ b/pairent_frontend_react/src/API/User.js @@ -1,6 +1,8 @@ import axios from 'axios'; import constants from '../constants'; +import { IAPIObject } from './IAPIObject'; + const { API_ROOT, api_path } = constants; class UserLoginResponse { @@ -14,58 +16,6 @@ class UserLoginResponse { id; } -class IAPIObject { - /** - * Local storage key used to save data. - * @type {string} - */ - static storage_key = undefined; - - static _checkStorageSupport() { - if (this.storage_key === undefined) { - throw Error('This doesn\'t support local storage'); - } - } - - _getStorageKey() { - return Object.getPrototypeOf(this).constructor.storage_key; - } - - _IcheckStorageSupport() { - if (this._getStorageKey() === undefined) { - throw Error('This doesn\'t support local storage'); - } - } - - /** @returns {ThisType} */ - static restoreFromLocalStorage() { - _checkStorageSupport(); - if (!window.sessionStorage.getItem(this.storage_key)) - return false; - return new APIToken(window.sessionStorage.getItem(this.storage_key)); - } - - /** @returns {boolean} */ - static isCached() { - this._checkStorageSupport(); - if (window.sessionStorage.getItem(this.storage_key)) { - return true; - } - return false; - } - - /** - * Save this object to local storage - * @throws {QuotaExceededError} - * @returns {void} - */ - saveToLocalStorage() { - this._IcheckStorageSupport(); - - window.sessionStorage.setItem(this._getStorageKey(), JSON.stringify(this)); - } -} - class APIToken extends IAPIObject { static storage_key = 'pairent_api_key'; From 4f5f817d835f6a53448e7d60bb7591c24c825e96 Mon Sep 17 00:00:00 2001 From: b1ek Date: Wed, 17 May 2023 02:51:21 +1000 Subject: [PATCH 9/9] move auth functions to different file --- pairent_backend/pairent_app/authlib.py | 87 +++++++++++++++++++++++++ pairent_backend/pairent_app/views.py | 88 +------------------------- 2 files changed, 89 insertions(+), 86 deletions(-) create mode 100644 pairent_backend/pairent_app/authlib.py diff --git a/pairent_backend/pairent_app/authlib.py b/pairent_backend/pairent_app/authlib.py new file mode 100644 index 0000000..3026648 --- /dev/null +++ b/pairent_backend/pairent_app/authlib.py @@ -0,0 +1,87 @@ +from django.http import HttpResponseBadRequest, HttpResponse, JsonResponse, HttpRequest + +def VVSUAuthProxy(req: HttpRequest): + proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):]; + + preq = requests.request(req.method, proxy, headers={ + 'User-Agent': 'OIDC Client / Pairent', + 'Origin': 'http://pairent.vvsu.ru', + 'Referer': 'http://pairent.vvsu.ru' + }); + + resp = HttpResponse(preq.content); + resp.headers['Content-Type'] = preq.headers['Content-Type']; + + return resp; + +def register(oid, provider_id, name): + user = User( + favorites_apartments='', + comparison_apartments='', + name=name, + # date_of_birth=, + about_me='', + gender='?', + phone='+00000', + # email=, + # telegram=, + # discord=, + # city=, + role='s', + photo_provider='VVSU', + openid_addr=oid, + openid_id=provider_id, + ); + user.save(); + return user; + +def get_oauth_token(remote, data): + return requests.post(remote + '/oauth2/token', data, + headers={ + 'Origin': 'https://pairent.vvsu.ru', + 'Referer': 'https://pairent.vvsu.ru' + }).json(); + +def get_oauth_data(remote, key): + return requests.get(remote + '/userinfo', headers={ + 'Origin': 'https://pairent.vvsu.ru', + 'Authorization': 'Bearer ' + key, + 'User-Agent': 'curl/8.1' + }).json(); + +def create_auth_token(userid, ip): + + try: + token = AuthToken.objects.get(user=userid, ip=ip); + if (verify_auth_token(token.key, token.ip)): + return token; + except AuthToken.DoesNotExist: + 0 # ignore + + token = AuthToken( + user=userid, + key=str(uuid.uuid4()), + # 2 days + # vvv + expires=time.time() + 60 * 60 * 24 * 2, + ip=ip + ); + token.save(); + return token; + +def verify_auth_token(key, ip): + + try: + token = AuthToken.objects.get(key=key); + except AuthToken.DoesNotExist: + return False; + + if (token.ip != ip): + token.delete(); + return False; + + if (token.expires > time.time()): + token.delete(); + return False; + + return True; diff --git a/pairent_backend/pairent_app/views.py b/pairent_backend/pairent_app/views.py index 87f0ac2..58aa758 100644 --- a/pairent_backend/pairent_app/views.py +++ b/pairent_backend/pairent_app/views.py @@ -17,6 +17,8 @@ from .serializer import (ApartamentListSerializer, PublicUserSerializer, TokenSerializer) +from .authlib import * + import json, math, random, re, requests, oidc_client, base64, uuid, time, ipware as iplib ipware = iplib.IpWare(); @@ -159,92 +161,6 @@ class CompatibleUsersView(viewsets.ViewSet): return Response(users); -def VVSUAuthProxy(req: Request): - proxy = 'https://vvsu.ru/connect' + req.path[len('/api/auth/vvsu'):]; - - preq = requests.request(req.method, proxy, headers={ - 'User-Agent': 'OIDC Client / Pairent', - 'Origin': 'http://pairent.vvsu.ru', - 'Referer': 'http://pairent.vvsu.ru' - }); - - resp = HttpResponse(preq.content); - resp.headers['Content-Type'] = preq.headers['Content-Type']; - - return resp; - -def register(oid, provider_id, name): - user = User( - favorites_apartments='', - comparison_apartments='', - name=name, - # date_of_birth=, - about_me='', - gender='?', - phone='+00000', - # email=, - # telegram=, - # discord=, - # city=, - role='s', - photo_provider='VVSU', - openid_addr=oid, - openid_id=provider_id, - ); - user.save(); - return user; - -def get_oauth_token(remote, data): - return requests.post(remote + '/oauth2/token', data, - headers={ - 'Origin': 'https://pairent.vvsu.ru', - 'Referer': 'https://pairent.vvsu.ru' - }).json(); - -def get_oauth_data(remote, key): - return requests.get(remote + '/userinfo', headers={ - 'Origin': 'https://pairent.vvsu.ru', - 'Authorization': 'Bearer ' + key, - 'User-Agent': 'curl/8.1' - }).json(); - -def create_auth_token(userid, ip): - - try: - token = AuthToken.objects.get(user=userid, ip=ip); - if (verify_auth_token(token.key, token.ip)): - return token; - except AuthToken.DoesNotExist: - 0 # ignore - - token = AuthToken( - user=userid, - key=str(uuid.uuid4()), - # 2 days - # vvv - expires=time.time() + 60 * 60 * 24 * 2, - ip=ip - ); - token.save(); - return token; - -def verify_auth_token(key, ip): - - try: - token = AuthToken.objects.get(key=key); - except AuthToken.DoesNotExist: - return False; - - if (token.ip != ip): - token.delete(); - return False; - - if (token.expires > time.time()): - token.delete(); - return False; - - return True; - class UserLogin(APIView): # TODO: Remove csrf exempt when index.html is loaded through django