From f14125d198cd76c0a5ff757a63285e1f3ee93f86 Mon Sep 17 00:00:00 2001 From: Web-serfer Date: Tue, 5 May 2026 21:12:46 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/api/auth/confirm-password-reset.ts | 95 +++++++++++++++---- .../pages/api/auth/request-password-reset.ts | 49 ++++++++++ 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/api/auth/confirm-password-reset.ts b/frontend/src/pages/api/auth/confirm-password-reset.ts index a6f112f..90dcdf5 100644 --- a/frontend/src/pages/api/auth/confirm-password-reset.ts +++ b/frontend/src/pages/api/auth/confirm-password-reset.ts @@ -1,11 +1,55 @@ import type { APIRoute } from 'astro'; const PB_POCKETBASE_URL = import.meta.env.PB_POCKETBASE_URL || 'http://localhost:8090'; +const PB_ADMIN_EMAIL = import.meta.env.PB_ADMIN_EMAIL; +const PB_ADMIN_PASSWORD = import.meta.env.PB_ADMIN_PASSWORD; const PASSWORD_MIN_LENGTH = 8; const PASSWORD_MAX_LENGTH = 12; const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d_!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/; +async function getAdminToken(): Promise { + const response = await fetch(`${PB_POCKETBASE_URL}/api/admins/auth-with-password`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + identity: PB_ADMIN_EMAIL, + password: PB_ADMIN_PASSWORD + }) + }); + + if (!response.ok) { + throw new Error('Не удалось авторизоваться как админ'); + } + + const data = await response.json(); + return data.token; +} + +async function getUserIdByResetToken(token: string): Promise { + try { + const adminToken = await getAdminToken(); + + const response = await fetch(`${PB_POCKETBASE_URL}/api/collections/users/records?filter=reset_token="${token}"&limit=1`, { + headers: { + 'Authorization': `Bearer ${adminToken}` + } + }); + + if (!response.ok) return null; + + const data = await response.json(); + if (data.items && data.items.length > 0) { + return data.items[0].id; + } + + return null; + } catch (e) { + console.error('[CONFIRM_RESET] Error getting user by token:', e); + return null; + } +} + function validatePassword(password: string): { valid: boolean; error?: string } { if (!password || password.length < PASSWORD_MIN_LENGTH) { return { valid: false, error: 'Пароль должен быть не менее 8 символов' }; @@ -62,39 +106,48 @@ export const POST: APIRoute = async ({ request }) => { ); } - const confirmUrl = `${PB_POCKETBASE_URL}/api/collections/users/confirm-password-reset`; - - const response = await fetch(confirmUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ token, password, passwordConfirm }), - }); - - if (response.status === 204) { + // Ищем пользователя по токену через Admin API + const userId = await getUserIdByResetToken(token); + + if (!userId) { return new Response( - JSON.stringify({ - success: true, - message: 'Пароль успешно изменён' - }), - { status: 200, headers: { 'Content-Type': 'application/json' } } + JSON.stringify({ error: 'Неверный или истёкший токен сброса пароля' }), + { status: 400, headers: { 'Content-Type': 'application/json' } } ); } - let data; - try { - data = await response.json(); - } catch { + // Обновляем пароль через Admin API (обходит валидацию PB) + const adminToken = await getAdminToken(); + + const updateResponse = await fetch(`${PB_POCKETBASE_URL}/api/collections/users/records/${userId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${adminToken}` + }, + body: JSON.stringify({ + password: password, + passwordConfirm: passwordConfirm, + reset_token: '', // Очищаем токен + reset_token_expires: '' + }) + }); + + if (!updateResponse.ok) { + const updateData = await updateResponse.json(); + console.log('[CONFIRM_RESET] Update error:', updateData); return new Response( - JSON.stringify({ error: 'Ошибка обработки ответа' }), + JSON.stringify({ error: 'Не удалось обновить пароль' }), { status: 500, headers: { 'Content-Type': 'application/json' } } ); } return new Response( JSON.stringify({ - error: data.message || 'Неверный или истёкший токен сброса пароля' + success: true, + message: 'Пароль успешно изменён' }), - { status: response.status, headers: { 'Content-Type': 'application/json' } } + { status: 200, headers: { 'Content-Type': 'application/json' } } ); } catch (error) { diff --git a/frontend/src/pages/api/auth/request-password-reset.ts b/frontend/src/pages/api/auth/request-password-reset.ts index 908e4a6..fbfb08f 100644 --- a/frontend/src/pages/api/auth/request-password-reset.ts +++ b/frontend/src/pages/api/auth/request-password-reset.ts @@ -162,6 +162,55 @@ export const POST: APIRoute = async ({ request }) => { // Создаём свой токен сброса const resetToken = Buffer.from(`${user.id}:${Date.now()}`).toString('base64').replace(/=/g, ''); + + // Сохраняем токен в поле пользователя + try { + await pbAdmin.collection('users').update(user.id, { + reset_token: resetToken, + reset_token_expires: new Date(Date.now() + 60 * 60 * 1000).toISOString() + }); + } catch (updateError) { + console.log('Fields reset_token may not exist, trying to add:', updateError); + // Поля могут не существовать - попробуем создать их + try { + const adminToken = await pbAdmin.authStore.token; + + // Проверяем существуют ли поля + const collectionResponse = await fetch(`${PB_POCKETBASE_URL}/api/collections/users`, { + headers: { 'Authorization': `Bearer ${adminToken}` } + }); + const collectionData = await collectionResponse.json(); + + const fieldExists = collectionData.fields?.some((f: any) => f.name === 'reset_token'); + + if (!fieldExists) { + // Добавляем поля в коллекцию + const newFields = [ + ...(collectionData.fields || []), + { name: 'reset_token', type: 'text' }, + { name: 'reset_token_expires', type: 'text' } + ]; + + await fetch(`${PB_POCKETBASE_URL}/api/collections/users`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${adminToken}` + }, + body: JSON.stringify({ fields: newFields }) + }); + } + + // Пробуем снова обновить + await pbAdmin.collection('users').update(user.id, { + reset_token: resetToken, + reset_token_expires: new Date(Date.now() + 60 * 60 * 1000).toISOString() + }); + } catch (e) { + console.error('Failed to save reset token:', e); + } + } + const resetLink = `${getSiteUrl()}/auth/reset-password?token=${resetToken}&userId=${user.id}`; // Отправляем письмо через SMTP.BZ