2026-04-18 18:25:10 +05:00
|
|
|
|
import type { APIRoute } from 'astro';
|
|
|
|
|
|
|
2026-05-04 03:10:06 +05:00
|
|
|
|
const PB_POCKETBASE_URL = import.meta.env.PB_POCKETBASE_URL || 'http://localhost:8090';
|
2026-05-05 21:18:47 +05:00
|
|
|
|
const PB_ADMIN_EMAIL = import.meta.env.PB_ADMIN_EMAIL || 'redibedi2019@gmail.com';
|
|
|
|
|
|
const PB_ADMIN_PASSWORD = import.meta.env.PB_ADMIN_PASSWORD || 'Stalin4444';
|
2026-04-18 18:25:10 +05:00
|
|
|
|
|
|
|
|
|
|
const PASSWORD_MIN_LENGTH = 8;
|
|
|
|
|
|
const PASSWORD_MAX_LENGTH = 12;
|
2026-05-05 20:58:35 +05:00
|
|
|
|
const PASSWORD_REGEX = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d_!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/;
|
2026-04-18 18:25:10 +05:00
|
|
|
|
|
2026-05-05 21:12:46 +05:00
|
|
|
|
async function getAdminToken(): Promise<string> {
|
|
|
|
|
|
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<string | null> {
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-18 18:25:10 +05:00
|
|
|
|
function validatePassword(password: string): { valid: boolean; error?: string } {
|
|
|
|
|
|
if (!password || password.length < PASSWORD_MIN_LENGTH) {
|
|
|
|
|
|
return { valid: false, error: 'Пароль должен быть не менее 8 символов' };
|
|
|
|
|
|
}
|
|
|
|
|
|
if (password.length > PASSWORD_MAX_LENGTH) {
|
|
|
|
|
|
return { valid: false, error: 'Пароль не должен превышать 12 символов' };
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!PASSWORD_REGEX.test(password)) {
|
|
|
|
|
|
return { valid: false, error: 'Пароль должен содержать хотя бы одну букву и одну цифру' };
|
|
|
|
|
|
}
|
|
|
|
|
|
return { valid: true };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const POST: APIRoute = async ({ request }) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let body;
|
|
|
|
|
|
try {
|
|
|
|
|
|
body = await request.json();
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Неверный формат данных' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { token, password, passwordConfirm } = body;
|
|
|
|
|
|
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Токен сброса пароля обязателен' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!password || !passwordConfirm) {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Пароль и подтверждение обязательны' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (password !== passwordConfirm) {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: 'Пароли не совпадают' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const passwordValidation = validatePassword(password);
|
|
|
|
|
|
if (!passwordValidation.valid) {
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({ error: passwordValidation.error }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 21:12:46 +05:00
|
|
|
|
// Ищем пользователя по токену через Admin API
|
|
|
|
|
|
const userId = await getUserIdByResetToken(token);
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
2026-04-18 18:25:10 +05:00
|
|
|
|
return new Response(
|
2026-05-05 21:12:46 +05:00
|
|
|
|
JSON.stringify({ error: 'Неверный или истёкший токен сброса пароля' }),
|
|
|
|
|
|
{ status: 400, headers: { 'Content-Type': 'application/json' } }
|
2026-04-18 18:25:10 +05:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-05 21:12:46 +05:00
|
|
|
|
// Обновляем пароль через 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);
|
2026-04-18 18:25:10 +05:00
|
|
|
|
return new Response(
|
2026-05-05 21:12:46 +05:00
|
|
|
|
JSON.stringify({ error: 'Не удалось обновить пароль' }),
|
2026-04-18 18:25:10 +05:00
|
|
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({
|
2026-05-05 21:12:46 +05:00
|
|
|
|
success: true,
|
|
|
|
|
|
message: 'Пароль успешно изменён'
|
2026-04-18 18:25:10 +05:00
|
|
|
|
}),
|
2026-05-05 21:12:46 +05:00
|
|
|
|
{ status: 200, headers: { 'Content-Type': 'application/json' } }
|
2026-04-18 18:25:10 +05:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[CONFIRM_RESET] Error:', error);
|
|
|
|
|
|
return new Response(
|
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
|
error: 'Внутренняя ошибка сервера'
|
|
|
|
|
|
}),
|
|
|
|
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|