fix merge conflict
This commit is contained in:
commit
31cd4f98a8
|
@ -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;
|
|
@ -17,22 +17,31 @@ class Migration(migrations.Migration):
|
||||||
name='User',
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('favorites_apartments', models.CharField(max_length=100, help_text="Избранные квартиры (CSV)")),
|
('favorites_apartments', models.CharField(max_length=100, help_text="Избранные квартиры (CSV)", null=True)),
|
||||||
('comparison_apartments', models.CharField(max_length=100, help_text="Квартиры для сравнения (CSV)")),
|
('comparison_apartments', models.CharField(max_length=100, help_text="Квартиры для сравнения (CSV)", null=True)),
|
||||||
('name', models.CharField(max_length=256, help_text='ФИО Пользователя')),
|
('name', models.CharField(max_length=500, help_text='ФИО Пользователя', null=True)),
|
||||||
('date_of_birth', models.DateField(help_text='Дата рождения пользователя')),
|
('date_of_birth', models.DateField(help_text='Дата рождения пользователя', null=True)),
|
||||||
('about_me', models.CharField(max_length=1000, help_text='Поле "О Себе"')),
|
('about_me', models.CharField(max_length=1000, help_text='Поле "О Себе"', null=True)),
|
||||||
('gender', models.CharField(max_length=1, help_text='Пол пользователя (f,m,n,?)')),
|
('gender', models.CharField(max_length=1, help_text='Пол пользователя (f,m,n,?)', null=True)),
|
||||||
('phone', models.CharField(max_length=30, help_text='Телефон пользователя в международном формате (+00000000)', 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)),
|
('email', models.CharField(max_length=1000, help_text='Почтовый ящик пользователя в формате user@example.com', null=True)),
|
||||||
('telegram', models.CharField(max_length=1000, help_text='Телеграм пользователя', null=True)),
|
('telegram', models.CharField(max_length=1000, help_text='Телеграм пользователя', null=True)),
|
||||||
('discord', 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)),
|
('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)),
|
('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_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='AuthToken',
|
||||||
|
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, с которого был создан ключ'))
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -129,4 +129,10 @@ class PsychTestAnswers(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Ответ на психологический тест"
|
verbose_name = "Ответ на психологический тест"
|
||||||
verbose_name_plural = "Ответы на психологический тест"
|
verbose_name_plural = "Ответы на психологический тест"
|
||||||
|
|
||||||
|
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)');
|
||||||
|
ip = models.CharField(max_length=16, verbose_name='IP, с которого был создан ключ');
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
from .models import Apartament, User, PsychTestAnswers
|
from .models import Apartament, User, PsychTestAnswers
|
||||||
|
=======
|
||||||
|
from .models import Apartament, User, AuthToken
|
||||||
|
>>>>>>> 4f5f817d835f6a53448e7d60bb7591c24c825e96
|
||||||
|
|
||||||
|
|
||||||
class ApartamentListSerializer(serializers.ModelSerializer):
|
class ApartamentListSerializer(serializers.ModelSerializer):
|
||||||
|
@ -36,4 +40,8 @@ class PsychTestReultsSerializer(serializers.ModelSerializer):
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
class TokenSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AuthToken
|
||||||
|
fields = '__all__'
|
||||||
|
|
|
@ -10,15 +10,19 @@ from django.db.models.query import QuerySet
|
||||||
from django.core.validators import validate_email
|
from django.core.validators import validate_email
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from .models import Apartament, User, PsychTestAnswers
|
from .models import Apartament, User, PsychTestAnswers, AuthToken
|
||||||
from .serializer import (ApartamentListSerializer,
|
from .serializer import (ApartamentListSerializer,
|
||||||
ApartamentDetailSerializer,
|
ApartamentDetailSerializer,
|
||||||
PsychTestAddResultSerializer,
|
PsychTestAddResultSerializer,
|
||||||
PublicUserSerializer,
|
PublicUserSerializer,
|
||||||
PsychTestReultsSerializer,
|
PsychTestReultsSerializer,
|
||||||
UserSerializer)
|
UserSerializer,
|
||||||
|
TokenSerializer)
|
||||||
|
|
||||||
import json, math, random, re, requests, oidc_client, base64, hashlib
|
from .authlib import *
|
||||||
|
|
||||||
|
import json, math, random, re, requests, oidc_client, base64, uuid, time, ipware as iplib
|
||||||
|
ipware = iplib.IpWare();
|
||||||
|
|
||||||
class ApartamentViewSet(viewsets.ReadOnlyModelViewSet):
|
class ApartamentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Вывод списка квартир или отдельной квартиры"""
|
"""Вывод списка квартир или отдельной квартиры"""
|
||||||
|
@ -163,58 +167,15 @@ class CompatibleUsersView(viewsets.ViewSet):
|
||||||
|
|
||||||
return Response(users);
|
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 regiserUser(oid, provider_id, name, date_of_birth):
|
|
||||||
user = User(
|
|
||||||
favorites_apartments='',
|
|
||||||
comparison_apartments='',
|
|
||||||
name=name,
|
|
||||||
date_of_birth=date_of_birth,
|
|
||||||
about_me='',
|
|
||||||
gender='?',
|
|
||||||
phone='+00000',
|
|
||||||
# email=,
|
|
||||||
# telegram=,
|
|
||||||
# discord=,
|
|
||||||
# city=,
|
|
||||||
role='s',
|
|
||||||
# photo_provider=,
|
|
||||||
openid_addr=oid,
|
|
||||||
openid_id=provider_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
class UserLogin(APIView):
|
class UserLogin(APIView):
|
||||||
|
|
||||||
# TODO: Remove csrf exempt when index.html is loaded through django
|
# TODO: Remove csrf exempt when index.html is loaded through django
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def post(self, req: HttpRequest):
|
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')):
|
if (req.session.has_key('auth_data')):
|
||||||
# TODO: Return user object instead of error
|
# TODO: Return user object instead of error
|
||||||
return JsonResponse({'error': 'already authenticated'})
|
return JsonResponse({'error': 'already authenticated'})
|
||||||
|
@ -231,42 +192,44 @@ class UserLogin(APIView):
|
||||||
res.status_code = 400;
|
res.status_code = 400;
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
# auth_data = get_oauth_token('https://vvsu.ru/connect', {
|
auth_data = get_oauth_token('https://vvsu.ru/connect', {
|
||||||
# 'grant_type': 'authorization_code',
|
'grant_type': 'authorization_code',
|
||||||
# 'redirect_uri': 'https://pairent.vvsu.ru/sign-in/',
|
'redirect_uri': 'https://pairent.vvsu.ru/sign-in/',
|
||||||
# 'code': data['code'],
|
'code': data['code'],
|
||||||
# 'code_verifier': data['code_verifier'],
|
'code_verifier': data['code_verifier'],
|
||||||
# 'client_id': 'it-hub-client',
|
'client_id': 'it-hub-client',
|
||||||
# 'client_secret': 'U8y@uPVee6Q^*729esHTo4Vd'
|
'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'}
|
if ('error' in auth_data):
|
||||||
|
return JsonResponse(auth_data);
|
||||||
|
|
||||||
user = None;
|
user = None;
|
||||||
new_user = False;
|
new_user = False;
|
||||||
|
|
||||||
print(auth_data);
|
vvsu_data = get_oauth_data('https://vvsu.ru/connect', auth_data['access_token']);
|
||||||
|
|
||||||
return JsonResponse(get_oauth_data('https://vvsu.ru/connect', auth_data['access_token']));
|
if ('error' in vvsu_data):
|
||||||
|
res = JsonResponse(vvsu_data);
|
||||||
req.session['auth_data'] = vvsu_data;
|
res.status_code = 500;
|
||||||
|
return res;
|
||||||
|
|
||||||
if ('error' in vvsu_data):
|
if ('error' in vvsu_data):
|
||||||
res = JsonResponse(vvsu_data);
|
res = JsonResponse(vvsu_data);
|
||||||
res.status_code = cb.status_code;
|
res.status_code = 500;
|
||||||
return res
|
return res
|
||||||
|
|
||||||
vvsu_data['vvsu_login'] += '@vvsu.ru';
|
vvsu_data['vvsu_login'] += '@vvsu.ru';
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(openid_addr=vvsu_data['vvsu_login']);
|
user = User.objects.get(openid_addr=vvsu_data['vvsu_login']);
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
registerUser(vvsu_data['vvsu_login'], cb.id, f'{cb.given_name} {cb.family_name}');
|
user = register(vvsu_data['vvsu_login'], vvsu_data['id'], f"{vvsu_data['given_name']} {vvsu_data['family_name']}");
|
||||||
user = User.objects.get(openid_addr=vvsu_data['vvsu_login']);
|
|
||||||
new_user = True;
|
new_user = True;
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'user_data': user,
|
'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):
|
class UserGet(APIView):
|
||||||
|
|
|
@ -4,4 +4,4 @@ djangorestframework
|
||||||
django-cors-headers
|
django-cors-headers
|
||||||
Pillow
|
Pillow
|
||||||
requests
|
requests
|
||||||
oidc-client
|
python-ipware
|
|
@ -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 };
|
|
@ -1,6 +1,8 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import constants from '../constants';
|
import constants from '../constants';
|
||||||
|
|
||||||
|
import { IAPIObject } from './IAPIObject';
|
||||||
|
|
||||||
const { API_ROOT, api_path } = constants;
|
const { API_ROOT, api_path } = constants;
|
||||||
|
|
||||||
class UserLoginResponse {
|
class UserLoginResponse {
|
||||||
|
@ -14,8 +16,43 @@ class UserLoginResponse {
|
||||||
id;
|
id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class User {
|
class APIToken extends IAPIObject {
|
||||||
|
|
||||||
|
static storage_key = 'pairent_api_key';
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
super();
|
||||||
|
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 {
|
||||||
|
|
||||||
|
isLoggedIn() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static storage_key = 'pairent_user_data';
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
super();
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
this[key] = data[key];
|
this[key] = data[key];
|
||||||
}
|
}
|
||||||
|
@ -44,7 +81,16 @@ class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await axios.post(api_path('/api/auth/user/login'), response);
|
const data = await axios.post(api_path('/api/auth/user/login'), response);
|
||||||
return new User(data.data);
|
if (data.status !== 200) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.data.error) {
|
||||||
|
new APIToken(data.data.token).saveToLocalStorage();
|
||||||
|
new User(data.data.user_data).saveToLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,12 +32,12 @@ export default class LoggedIn extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.response = new SigninResponse(new URL(window.location.href).searchParams);
|
this.response = new SigninResponse(new URL(window.location.href).searchParams);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
if (this.response.error) return;
|
if (this.response.error) return;
|
||||||
|
window.localStorage.removeItem('auth_fail');
|
||||||
|
|
||||||
let code_verifier = '?';
|
let code_verifier = '?';
|
||||||
// get code verifier
|
// get code verifier
|
||||||
for (const key in localStorage) {
|
for (const key in localStorage) {
|
||||||
|
@ -48,7 +48,21 @@ 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.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
|
||||||
|
window.location.href = '/register';
|
||||||
|
} else {
|
||||||
|
window.location.href = '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -8,6 +8,8 @@ import FloatingBox from '../../components/UI/FloatingBox';
|
||||||
import * as OpenID from 'oidc-client-ts';
|
import * as OpenID from 'oidc-client-ts';
|
||||||
import constants from '../../constants';
|
import constants from '../../constants';
|
||||||
|
|
||||||
|
import { User } from '../../API/User';
|
||||||
|
|
||||||
const { OIDCConfig } = constants;
|
const { OIDCConfig } = constants;
|
||||||
|
|
||||||
const LoginButton = styled(BlueButton)`
|
const LoginButton = styled(BlueButton)`
|
||||||
|
@ -45,9 +47,11 @@ export default class LoginPage extends React.Component {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
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);
|
this.openid = this.openid.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,15 +60,18 @@ export default class LoginPage extends React.Component {
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
|
|
||||||
OpenID.Log.setLogger(console);
|
OpenID.Log.setLogger(console);
|
||||||
OpenID.Log.setLevel(OpenID.Log.DEBUG);
|
OpenID.Log.setLevel(OpenID.Log.NONE);
|
||||||
|
|
||||||
let client = new OpenID.UserManager(OIDCConfig);
|
let client = new OpenID.UserManager(OIDCConfig);
|
||||||
|
|
||||||
client.signinRedirect();
|
client.signinRedirect();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
|
if (User.isCached())
|
||||||
|
window.location.href = '/';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{height: '65vh'}}>
|
<div style={{height: '65vh'}}>
|
||||||
<FloatingBox>
|
<FloatingBox>
|
||||||
|
@ -84,6 +91,21 @@ export default class LoginPage extends React.Component {
|
||||||
Вход осуществляется только через<br/>
|
Вход осуществляется только через<br/>
|
||||||
Систему Единого Входа ВВГУ
|
Систему Единого Входа ВВГУ
|
||||||
</SmallText>
|
</SmallText>
|
||||||
|
{
|
||||||
|
this.state.error ?
|
||||||
|
<SmallText style={{color: 'darkred', fontWeight: '600'}}>
|
||||||
|
Произошла ошибка: { this.state.error.error }
|
||||||
|
{
|
||||||
|
this.state.error.error_description ?
|
||||||
|
<>
|
||||||
|
<br/>
|
||||||
|
{this.state.error.error_description}
|
||||||
|
</>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</SmallText>
|
||||||
|
: null
|
||||||
|
}
|
||||||
</FloatingBox>
|
</FloatingBox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue