astro_avtourist/frontend/src/pages/api/auth/confirm-password-reset.ts

162 lines
No EOL
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 || 'redibedi2019@gmail.com';
const PB_ADMIN_PASSWORD = import.meta.env.PB_ADMIN_PASSWORD || 'Stalin4444';
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<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;
}
}
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' } }
);
}
// Ищем пользователя по токену через Admin API
const userId = await getUserIdByResetToken(token);
if (!userId) {
return new Response(
JSON.stringify({ error: 'Неверный или истёкший токен сброса пароля' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// Обновляем пароль через 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: 'Не удалось обновить пароль' }),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
return new Response(
JSON.stringify({
success: true,
message: 'Пароль успешно изменён'
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
} catch (error) {
console.error('[CONFIRM_RESET] Error:', error);
return new Response(
JSON.stringify({
error: 'Внутренняя ошибка сервера'
}),
{ status: 500, headers: { 'Content-Type': 'application/json' } }
);
}
};