feat
This commit is contained in:
parent
d6512dac5a
commit
c1efb9d36f
@ -8,12 +8,14 @@
|
||||
|
||||
{% block content %}
|
||||
{{ header.section(user) }}
|
||||
<form on class="c_sbrxk">
|
||||
<form class="c_sbrxk">
|
||||
<img class="login-illustration" src="/static/login-art.png" alt="" aria-hidden="true">
|
||||
<div class="c_dtjoh">
|
||||
<div class="c_aihm6 floating-group">
|
||||
<input type="email" id="email" name="email" class="c_xhd5k" required>
|
||||
<label for="email" class="c_z3ybu floating-label">Email</label>
|
||||
<input type="text" id="email" name="email" class="c_xhd5k" autocomplete="username" required>
|
||||
<label for="email" class="c_z3ybu floating-label">Логин</label>
|
||||
</div>
|
||||
<span class="login-hint">Можно указать телефон или email от аккаунта</span>
|
||||
<div class="c_aihm6 floating-group file-group poster-group password-group" style="margin-bottom: 0;">
|
||||
<input type="password" id="password" name="password" class="c_xhd5k" required>
|
||||
<label for="password" class="c_z3ybu floating-label">Пароль</label>
|
||||
@ -34,7 +36,8 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a href="/restore{% if next %}?next={{next}}{% endif %}" onclick="sendToNextPage(event)" style="margin-bottom: 1rem;">Забыли пароль ?</a>
|
||||
<span class="login-hint">Минимум 8 символов</span>
|
||||
<a href="/restore{% if next %}?next={{next}}{% endif %}" onclick="sendToNextPage(event)" class="login-forgot" style="margin-bottom: 1rem;">Забыли пароль ?</a>
|
||||
|
||||
<button class="c_aq9et" id="createBtn" type="button">
|
||||
<span class="c_som9s">Войти</span>
|
||||
@ -42,6 +45,18 @@
|
||||
<div style="display: flex; gap:15px">
|
||||
<span>Нет аккаунта?</span><a href="/registration{% if next %}?next={{next}}{% endif %}">Регистрация</a>
|
||||
</div>
|
||||
|
||||
<div class="login-social">
|
||||
<div class="login-social__or"><span>или войдите через</span></div>
|
||||
<div class="login-social__row">
|
||||
<a class="login-social__btn login-social__btn--vk" href="/api/v1/social/vk/register{% if next %}?next={{next}}{% endif %}">
|
||||
<svg class="login-social__logo" width="22" height="22" viewBox="0 0 24 24" fill="#0077FF" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
<path d="M13.162 18.994c-6.13 0-9.625-4.203-9.77-11.196h3.07c.101 5.132 2.366 7.307 4.16 7.755V7.798h2.89v4.427c1.77-.19 3.63-2.208 4.256-4.427h2.89c-.48 2.734-2.498 4.752-3.933 5.581 1.435.673 3.733 2.432 4.61 5.614h-3.182c-.682-2.125-2.384-3.767-4.64-3.99v3.99h-.34z"/>
|
||||
</svg>
|
||||
<span>VK</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -120,13 +135,18 @@ function sendToNextPage(e) {
|
||||
window.location.href = next;
|
||||
|
||||
} else {
|
||||
// non-2xx — mark all input groups as error
|
||||
const allFields = document.querySelectorAll('.floating-group');
|
||||
allFields.forEach(g => g.classList.add('error'));
|
||||
|
||||
// Optional: you can show server message if present
|
||||
// const errorText = await res.text();
|
||||
// console.error('Server error:', errorText);
|
||||
let msg;
|
||||
if (res.status === 403) {
|
||||
// Аккаунт заблокирован — поля ввода ни при чём, показываем причину
|
||||
msg = 'Ваш аккаунт заблокирован администратором.';
|
||||
try { const d = await res.json(); if (d && typeof d.detail === 'string') msg = d.detail; } catch (e) {}
|
||||
allFields.forEach(g => g.classList.remove('error'));
|
||||
} else {
|
||||
msg = 'Неверный логин или пароль';
|
||||
allFields.forEach(g => g.classList.add('error'));
|
||||
}
|
||||
if (typeof showToast === 'function') showToast({ message: msg, status: 'error', duration: 9000 });
|
||||
}
|
||||
} catch (err) {
|
||||
// Network or other unexpected error — mark fields as error
|
||||
@ -179,6 +199,17 @@ function sendToNextPage(e) {
|
||||
pwd.focus();
|
||||
});
|
||||
|
||||
// Вход через Макс — бэкенд ещё не готов: уведомление, без перехода
|
||||
var maxLoginBtn = document.querySelector('.login-social__btn--max');
|
||||
if (maxLoginBtn) {
|
||||
maxLoginBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
if (typeof showToast === 'function') {
|
||||
showToast({ message: 'Вход через Макс — в процессе реализации', status: 'info', duration: 2800 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{{ footer.section() }}
|
||||
<script>
|
||||
@ -441,6 +472,43 @@ input[type="number"]:focus::-webkit-inner-spin-button {display: none;}
|
||||
}
|
||||
.c_sbrxk{z-index: 1000;}
|
||||
#preview_poster{width: 335px;}
|
||||
|
||||
/* «Забыли пароль?» — второстепенная ссылка */
|
||||
.login-forgot { font-size: 13px; color: #888; align-self: flex-start; }
|
||||
.login-forgot:hover { color: #5F1798; text-decoration: underline; }
|
||||
|
||||
/* Подписи-подсказки под полями */
|
||||
.login-hint { font-size: 12px; color: #888; align-self: flex-start; line-height: 1.3; margin-top: -6px; }
|
||||
/* убираем большой нижний отступ у группы, за которой идёт подсказка */
|
||||
.floating-group:has(+ .login-hint) { margin-bottom: 0; }
|
||||
|
||||
/* Микроанимация кнопки «Войти» */
|
||||
.c_aq9et { cursor: pointer; box-shadow: 0 4px 14px -4px rgba(95,23,152,.5); transition: transform .18s, box-shadow .18s, background .18s; }
|
||||
.c_aq9et:hover { background: #6d1ba9; transform: translateY(-2px); box-shadow: 0 10px 24px -6px rgba(95,23,152,.6); }
|
||||
.c_aq9et:active { transform: translateY(0); box-shadow: 0 4px 12px -4px rgba(95,23,152,.5); }
|
||||
@media (prefers-reduced-motion: reduce) { .c_aq9et:hover, .c_aq9et:active { transform: none; } }
|
||||
|
||||
/* Картинка слева от формы (только десктоп) */
|
||||
.login-illustration { display: none; }
|
||||
@media (min-width: 901px) {
|
||||
.login-illustration { display: block; width: 340px; max-width: 38vw; height: auto; align-self: center; }
|
||||
.c_sbrxk { gap: 80px; }
|
||||
}
|
||||
|
||||
/* Вход через VK / Макс */
|
||||
.login-social { display: flex; flex-direction: column; gap: 10px; align-self: stretch; margin-top: 4px; }
|
||||
.login-social__or { display: flex; align-items: center; gap: 10px; color: #999; font-size: 13px; }
|
||||
.login-social__or::before, .login-social__or::after { content: ''; flex: 1; height: 1px; background: #e5e5e5; }
|
||||
.login-social__row { display: flex; gap: 10px; }
|
||||
.login-social__btn { flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 48px; border-radius: 8px; background: #fff; border: 2px solid #ddd; color: #111; text-decoration: none; font-size: 15px; font-weight: 600; transition: border-color .18s, box-shadow .18s, transform .18s; }
|
||||
.login-social__btn:hover { transform: translateY(-1px); box-shadow: 0 6px 16px -6px rgba(0,0,0,.28); }
|
||||
.login-social__btn:active { transform: translateY(0); }
|
||||
.login-social__btn--vk:hover { border-color: #0077FF; }
|
||||
.login-social__btn--max:hover { border-color: #7a3ff2; }
|
||||
.login-social__logo { flex: 0 0 auto; display: block; width: 22px; height: 22px; }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.login-social__btn:hover, .login-social__btn:active { transform: none; }
|
||||
}
|
||||
</style>
|
||||
{{ footer.style() }}
|
||||
{% endblock %}
|
||||
|
||||
110
token_manager.py
110
token_manager.py
@ -1,5 +1,6 @@
|
||||
import os
|
||||
import json
|
||||
import re # регистронезависимый поиск email при VK-регистрации
|
||||
import hashlib
|
||||
import base64
|
||||
from datetime import datetime, timedelta, UTC
|
||||
@ -9,7 +10,8 @@ from bson import ObjectId
|
||||
from config import *
|
||||
from db import mongo_client, redis_client, gridfs_map
|
||||
import asyncio
|
||||
from utils import fetch_bytes
|
||||
import secrets
|
||||
from utils import fetch_bytes, get_password_hash
|
||||
import io
|
||||
import uuid
|
||||
from utils import encrypt, decrypt
|
||||
@ -17,7 +19,7 @@ from utils import encrypt, decrypt
|
||||
# ---------------------- Константы OAuth ----------------------
|
||||
OAUTH_URL = 'https://id.vk.com/oauth2/auth'
|
||||
AUTHORIZE_URL = 'https://id.vk.ru/authorize'
|
||||
SCOPES = 'offline,wall,photos,groups'
|
||||
SCOPES = 'offline,wall,photos,groups,email' # email — для регистрации через VK
|
||||
REDIRECT_URL = f'{FULL_DOMAIN}/api/v1/social/vk/token'
|
||||
|
||||
|
||||
@ -29,14 +31,21 @@ class TokenManager:
|
||||
def __init__(self):
|
||||
self.tokens_coll = mongo_client["vk"]["tokens"]
|
||||
|
||||
async def get_auth_url(self, user_id: str) -> str:
|
||||
async def get_auth_url(self, user_id: str = None, purpose: str = "link", next: str = None) -> str:
|
||||
"""purpose: "link" (привязка соцсети) или "register" (вход/регистрация через VK);
|
||||
next — куда вернуть пользователя после успешного входа."""
|
||||
state = os.urandom(32).hex()
|
||||
code_verifier = os.urandom(50).hex()
|
||||
code_challenge = base64.urlsafe_b64encode(
|
||||
hashlib.sha256(code_verifier.encode('utf-8')).digest()
|
||||
).decode('utf-8').rstrip('=')
|
||||
|
||||
oauth_data = {"user_id": user_id, "code_verifier": code_verifier}
|
||||
oauth_data = {
|
||||
"user_id": user_id,
|
||||
"code_verifier": code_verifier,
|
||||
"purpose": purpose,
|
||||
"next": next,
|
||||
}
|
||||
await redis_client.setex(f"oauth_state:{state}", 60 * 15, json.dumps(oauth_data))
|
||||
|
||||
url = (
|
||||
@ -73,7 +82,10 @@ class TokenManager:
|
||||
return None
|
||||
|
||||
oauth_data = json.loads(oauth_data_raw)
|
||||
user_id = oauth_data["user_id"]
|
||||
user_id = oauth_data.get("user_id")
|
||||
purpose = oauth_data.get("purpose", "link")
|
||||
next_url = oauth_data.get("next")
|
||||
created = False
|
||||
code_verifier = oauth_data["code_verifier"]
|
||||
|
||||
# Выполняем запрос к VK для обмена кода на токены
|
||||
@ -103,6 +115,13 @@ class TokenManager:
|
||||
})
|
||||
vk_profile = reqp.json()['response'][0]
|
||||
|
||||
# Режим регистрации: находим или создаём пользователя ДО сохранения соцсетей,
|
||||
# чтобы дальше socials/tokens сохранились уже с реальным user_id.
|
||||
if purpose == "register" and not user_id:
|
||||
user_id, created = await self._resolve_user_for_register(
|
||||
vk_profile, token_data.get("email")
|
||||
)
|
||||
|
||||
reqg = await client.post(f"{VK_API_URI}groups.get", data={
|
||||
'access_token':access_token,
|
||||
'extended':1,
|
||||
@ -111,8 +130,11 @@ class TokenManager:
|
||||
})
|
||||
vk_groups = reqg.json()['response']['items']
|
||||
urls = [vk_profile['photo_max_orig']] + [i['photo_200'] for i in vk_groups]
|
||||
results = await asyncio.gather(*(fetch_bytes(client, u) for u in urls))
|
||||
results = [await self.save_vk(i) for i in results]
|
||||
image_bytes = await asyncio.gather(*(fetch_bytes(client, u) for u in urls))
|
||||
# В режиме регистрации ставим аватар VK в профиль, если фото не задано вручную
|
||||
if purpose == "register" and user_id and image_bytes:
|
||||
await self._maybe_set_vk_avatar(user_id, created, image_bytes[0])
|
||||
results = [await self.save_vk(i) for i in image_bytes]
|
||||
socials = [{
|
||||
'name': f"{vk_profile['last_name']} {vk_profile['first_name']}",
|
||||
'screen_name': vk_profile['screen_name'],
|
||||
@ -142,7 +164,12 @@ class TokenManager:
|
||||
# Удаляем использованный state, чтобы его нельзя было переиспользовать.
|
||||
await redis_client.delete(key)
|
||||
|
||||
return token_data
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"registered": created,
|
||||
"purpose": purpose,
|
||||
"next": next_url,
|
||||
}
|
||||
|
||||
# Обработка ошибок HTTP (напр., неверный код, истёк скоп)
|
||||
except httpx.HTTPStatusError as e:
|
||||
@ -157,6 +184,73 @@ class TokenManager:
|
||||
return None
|
||||
|
||||
|
||||
async def _resolve_user_for_register(self, vk_profile: dict, email: str | None):
|
||||
"""Находит пользователя по vk_id или email, иначе создаёт нового.
|
||||
|
||||
Возвращает кортеж (user_id: str, created: bool).
|
||||
При совпадении по email — привязывает vk_id к найденному аккаунту.
|
||||
"""
|
||||
users = mongo_client["auth"]["users"]
|
||||
vk_id = vk_profile.get("id")
|
||||
|
||||
# 1) Уже привязанный VK-аккаунт
|
||||
existing = await users.find_one({"vk_id": vk_id}, {"_id": 1})
|
||||
if existing:
|
||||
return str(existing["_id"]), False
|
||||
|
||||
# 2) Существующий аккаунт с таким email — логиним и привязываем VK
|
||||
if email:
|
||||
existing = await users.find_one(
|
||||
{"email": {"$regex": f"^{re.escape(email)}$", "$options": "i"}},
|
||||
{"_id": 1},
|
||||
)
|
||||
if existing:
|
||||
await users.update_one({"_id": existing["_id"]}, {"$set": {"vk_id": vk_id}})
|
||||
return str(existing["_id"]), False
|
||||
|
||||
# 3) Создаём нового пользователя (роль по умолчанию — художник)
|
||||
first = vk_profile.get("first_name", "") or ""
|
||||
last = vk_profile.get("last_name", "") or ""
|
||||
doc = {
|
||||
"username": f"{first} {last}".strip(),
|
||||
"female_name_lastname": f"{last} {first}".strip(),
|
||||
"email": email,
|
||||
"phone": None,
|
||||
"img": "/static/painter.svg",
|
||||
"roles": ["user"],
|
||||
"extra_scopes": [],
|
||||
"vk_id": vk_id,
|
||||
"client_id": "vk",
|
||||
"living_place": None,
|
||||
"offer_accepted": False,
|
||||
# Неиспользуемый случайный пароль: вход по email/паролю невозможен,
|
||||
# но login-эндпоинт не падает на None.startswith(...).
|
||||
"hashed_password": get_password_hash(secrets.token_urlsafe(32)),
|
||||
}
|
||||
res = await users.insert_one(doc)
|
||||
return str(res.inserted_id), True
|
||||
|
||||
async def _maybe_set_vk_avatar(self, user_id: str, created: bool, avatar_bytes: bytes):
|
||||
"""Ставит аватар из VK в профиль, если пользователь не задал своё фото.
|
||||
|
||||
«Не задан вручную» = img пустой или дефолтная заглушка (/static/...).
|
||||
Для только что созданного пользователя ставим всегда.
|
||||
"""
|
||||
if not avatar_bytes:
|
||||
return
|
||||
users = mongo_client["auth"]["users"]
|
||||
if not created:
|
||||
u = await users.find_one({"_id": ObjectId(user_id)}, {"img": 1})
|
||||
img = (u or {}).get("img")
|
||||
if img and not str(img).startswith("/static/"):
|
||||
return # у пользователя уже своё фото — не трогаем
|
||||
try:
|
||||
grid = gridfs_map["users"]
|
||||
file_id = await grid.upload_from_stream(f"vk_{user_id}.jpg", io.BytesIO(avatar_bytes))
|
||||
await users.update_one({"_id": ObjectId(user_id)}, {"$set": {"img": str(file_id)}})
|
||||
except Exception as e:
|
||||
logging.error(f"[TokenManager] VK avatar save failed: {e}")
|
||||
|
||||
# ====================== ШИФРОВАНИЕ ======================
|
||||
async def _encrypt_token_fields(self, token_data: dict) -> dict:
|
||||
"""Шифрует access_token и refresh_token."""
|
||||
|
||||
42
vk.ipynb
42
vk.ipynb
@ -46,8 +46,13 @@
|
||||
"\n",
|
||||
"try:\n",
|
||||
" load_env('env')\n",
|
||||
"except:\n",
|
||||
" load_env('env_example')"
|
||||
"except Exception:\n",
|
||||
" # env_example тоже может отсутствовать (в образ эти файлы не копируются) —\n",
|
||||
" # тогда переменные берутся из окружения контейнера/k8s-секрета\n",
|
||||
" try:\n",
|
||||
" load_env('env_example')\n",
|
||||
" except Exception:\n",
|
||||
" pass"
|
||||
],
|
||||
"display_name": ".venv"
|
||||
},
|
||||
@ -78,7 +83,7 @@
|
||||
"from celery import Celery\n",
|
||||
"from bson import ObjectId\n",
|
||||
"from fastapi import status\n",
|
||||
"from fastapi import APIRouter,Depends\n",
|
||||
"from fastapi import APIRouter, Depends, Request, Response\n",
|
||||
"from fastapi.responses import RedirectResponse\n",
|
||||
"from images import insert_logo\n"
|
||||
],
|
||||
@ -130,10 +135,29 @@
|
||||
" return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@router.get(\"/register\")\n",
|
||||
"async def vk_register_start(next: str = '/profile'):\n",
|
||||
" \"\"\"Старт регистрации/входа через VK (без предварительной авторизации).\"\"\"\n",
|
||||
" url = await token_manager.get_auth_url(purpose='register', next=next)\n",
|
||||
" return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@router.get(\"/token\")\n",
|
||||
"async def oauth_callback(code: str, state: str, device_id: str = None):\n",
|
||||
" token_data = await token_manager.get_token(code=code, state=state, device_id=device_id)\n",
|
||||
" return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)\n"
|
||||
"async def oauth_callback(request: Request, response: Response, code: str, state: str, device_id: str = None):\n",
|
||||
" result = await token_manager.get_token(code=code, state=state, device_id=device_id)\n",
|
||||
" if not result or not result.get('user_id'):\n",
|
||||
" return RedirectResponse(url='/login', status_code=status.HTTP_303_SEE_OTHER)\n",
|
||||
"\n",
|
||||
" # Регистрация/вход через VK — выдаём нашу сессию и редиректим на next\n",
|
||||
" if result.get('purpose') == 'register':\n",
|
||||
" await issue_session(result['user_id'], request, response, client_id='vk')\n",
|
||||
" next_url = result.get('next') or '/profile'\n",
|
||||
" if not is_safe_redirect(next_url):\n",
|
||||
" next_url = '/profile'\n",
|
||||
" return RedirectResponse(url=next_url, status_code=status.HTTP_303_SEE_OTHER, headers=response.headers)\n",
|
||||
"\n",
|
||||
" # Привязка соцсети к уже залогиненному пользователю\n",
|
||||
" return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)"
|
||||
],
|
||||
"display_name": ".venv"
|
||||
},
|
||||
@ -175,7 +199,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# async def send_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True): TODO\n",
|
||||
"@router.post(\"/send\")\n",
|
||||
"async def send_vk(data:dict, request: Request):\n",
|
||||
" auth = request.headers.get(\"Authorization\")\n",
|
||||
@ -250,7 +273,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# async def edit_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True): TODO\n",
|
||||
"@router.post(\"/edit\")\n",
|
||||
"async def edit_vk(data:dict, request: Request):\n",
|
||||
" auth = request.headers.get(\"Authorization\")\n",
|
||||
@ -265,6 +287,7 @@
|
||||
" doc = data['doc']\n",
|
||||
" post_id = data['post_id']\n",
|
||||
" sign_avatar = data['sign_avatar']\n",
|
||||
"# async def edit_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True):\n",
|
||||
" db_name = item.get('db')\n",
|
||||
" if db_name == 'competitions':\n",
|
||||
" social = await social_collection.find_one({'service': True, 'type':'vk'})\n",
|
||||
@ -327,7 +350,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# TODO сделать вызов функции из @celery_app_vk.task( а не по url в router\n",
|
||||
"@router.post(\"/delete\")\n",
|
||||
"async def wall_delete(data: dict, request: Request):\n",
|
||||
" # ---- auth ----\n",
|
||||
@ -1614,7 +1636,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv (3.14.6.final.0)",
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
|
||||
41
vk.py
41
vk.py
@ -31,8 +31,13 @@ def load_env(file_path):
|
||||
|
||||
try:
|
||||
load_env('env')
|
||||
except:
|
||||
load_env('env_example')
|
||||
except Exception:
|
||||
# env_example тоже может отсутствовать (в образ эти файлы не копируются) —
|
||||
# тогда переменные берутся из окружения контейнера/k8s-секрета
|
||||
try:
|
||||
load_env('env_example')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
## Class
|
||||
|
||||
@ -48,7 +53,7 @@ from io import BytesIO
|
||||
from celery import Celery
|
||||
from bson import ObjectId
|
||||
from fastapi import status
|
||||
from fastapi import APIRouter,Depends
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
from fastapi.responses import RedirectResponse
|
||||
from images import insert_logo
|
||||
|
||||
@ -70,12 +75,30 @@ async def auth_redirect(
|
||||
return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@router.get("/token")
|
||||
async def oauth_callback(code: str, state: str, device_id: str = None):
|
||||
token_data = await token_manager.get_token(code=code, state=state, device_id=device_id)
|
||||
return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)
|
||||
@router.get("/register")
|
||||
async def vk_register_start(next: str = '/profile'):
|
||||
"""Старт регистрации/входа через VK (без предварительной авторизации)."""
|
||||
url = await token_manager.get_auth_url(purpose='register', next=next)
|
||||
return RedirectResponse(url=url, status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
|
||||
@router.get("/token")
|
||||
async def oauth_callback(request: Request, response: Response, code: str, state: str, device_id: str = None):
|
||||
result = await token_manager.get_token(code=code, state=state, device_id=device_id)
|
||||
if not result or not result.get('user_id'):
|
||||
return RedirectResponse(url='/login', status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
# Регистрация/вход через VK — выдаём нашу сессию и редиректим на next
|
||||
# if result.get('purpose') == 'register':
|
||||
# await issue_session(result['user_id'], request, response, client_id='vk')
|
||||
# next_url = result.get('next') or '/profile'
|
||||
# if not is_safe_redirect(next_url):
|
||||
# next_url = '/profile'
|
||||
# return RedirectResponse(url=next_url, status_code=status.HTTP_303_SEE_OTHER, headers=response.headers)
|
||||
|
||||
# Привязка соцсети к уже залогиненному пользователю
|
||||
return RedirectResponse(url='/profile/social', status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
async def get_images(db_name: str, file_ids: list, sign_avatar: bool = True):
|
||||
images = {}
|
||||
photo_bytes_list= []
|
||||
@ -99,7 +122,6 @@ async def get_images(db_name: str, file_ids: list, sign_avatar: bool = True):
|
||||
return images
|
||||
|
||||
|
||||
# async def send_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True): TODO
|
||||
@router.post("/send")
|
||||
async def send_vk(data:dict, request: Request):
|
||||
auth = request.headers.get("Authorization")
|
||||
@ -166,7 +188,6 @@ async def send_vk(data:dict, request: Request):
|
||||
|
||||
|
||||
|
||||
# async def edit_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True): TODO
|
||||
@router.post("/edit")
|
||||
async def edit_vk(data:dict, request: Request):
|
||||
auth = request.headers.get("Authorization")
|
||||
@ -181,6 +202,7 @@ async def edit_vk(data:dict, request: Request):
|
||||
doc = data['doc']
|
||||
post_id = data['post_id']
|
||||
sign_avatar = data['sign_avatar']
|
||||
# async def edit_vk(item: dict, text: str, file_ids: list, doc: dict, sign_avatar: bool = True):
|
||||
db_name = item.get('db')
|
||||
if db_name == 'competitions':
|
||||
social = await social_collection.find_one({'service': True, 'type':'vk'})
|
||||
@ -235,7 +257,6 @@ async def edit_vk(data:dict, request: Request):
|
||||
|
||||
|
||||
|
||||
# TODO сделать вызов функции из @celery_app_vk.task( а не по url в router
|
||||
@router.post("/delete")
|
||||
async def wall_delete(data: dict, request: Request):
|
||||
# ---- auth ----
|
||||
|
||||
Loading…
Reference in New Issue
Block a user