2025-12-08 23:42:32 +00:00
const express = require ( 'express' ) ;
const router = express . Router ( ) ;
const crypto = require ( 'crypto' ) ;
const bcrypt = require ( 'bcryptjs' ) ;
2025-12-09 01:03:25 +00:00
const axios = require ( 'axios' ) ;
2025-12-08 23:42:32 +00:00
const User = require ( '../models/User' ) ;
const EmailVerificationCode = require ( '../models/EmailVerificationCode' ) ;
const { sendVerificationCode } = require ( '../utils/email' ) ;
const { signAuthTokens , setAuthCookies , clearAuthCookies , verifyAccessToken } = require ( '../utils/tokens' ) ;
const { logSecurityEvent } = require ( '../middleware/logger' ) ;
const { authenticateModeration } = require ( '../middleware/auth' ) ;
const { isEmail } = require ( 'validator' ) ;
2025-12-09 01:03:25 +00:00
const config = require ( '../config' ) ;
// Кэш для username бота модерации
let cachedModerationBotUsername = null ;
2025-12-08 23:42:32 +00:00
// Rate limiting для авторизации
const rateLimit = require ( 'express-rate-limit' ) ;
const authLimiter = rateLimit ( {
windowMs : 15 * 60 * 1000 , // 15 минут
max : 5 , // 5 попыток
message : 'Слишком много попыток авторизации. Попробуйте позже.'
} ) ;
const codeLimiter = rateLimit ( {
windowMs : 60 * 1000 , // 1 минута
max : 1 , // 1 запрос в минуту
message : 'Подождите минуту перед следующим запросом кода.'
} ) ;
// Отправка кода подтверждения на email
router . post ( '/send-code' , codeLimiter , async ( req , res ) => {
try {
const { email } = req . body ;
if ( ! email || ! isEmail ( email ) ) {
return res . status ( 400 ) . json ( { error : 'Неверный email адрес' } ) ;
}
2025-12-14 13:56:06 +00:00
const emailLower = email . toLowerCase ( ) . trim ( ) ;
// Проверить, есть ли уже пользователь с этим email и ролью moderator/admin
2025-12-08 23:42:32 +00:00
const existingUser = await User . findOne ( {
2025-12-14 13:56:06 +00:00
email : emailLower ,
2025-12-08 23:42:32 +00:00
role : { $in : [ 'moderator' , 'admin' ] }
} ) ;
2025-12-14 14:28:59 +00:00
console . log ( ` [ModerationAuth] Проверка пользователя для email ${ emailLower } : ` , {
found : ! ! existingUser ,
hasPassword : existingUser ? . passwordHash ? true : false ,
role : existingUser ? . role
} ) ;
// Если пользователь существует - разрешить отправку кода (независимо от наличия пароля)
if ( existingUser ) {
console . log ( ` [ModerationAuth] Пользователь найден, отправка кода разрешена ` ) ;
} else {
2025-12-14 13:56:06 +00:00
// Пользователя нет - проверить, может быть email установлен, но роль не установлена
const userByEmail = await User . findOne ( { email : emailLower } ) ;
2025-12-14 14:28:59 +00:00
if ( userByEmail ) {
console . log ( ` [ModerationAuth] Пользователь найден, но роль не moderator/admin: ` , userByEmail . role ) ;
2025-12-15 00:37:34 +00:00
return res . status ( 403 ) . json ( {
error : 'Регистрация недоступна. Обратитесь к администратору для получения доступа.'
} ) ;
2025-12-14 13:56:06 +00:00
}
// Если пользователя нет вообще - разрешить отправку кода
// (администратор должен создать пользователя заранее, но на случай если забыл - разрешаем)
2025-12-14 14:28:59 +00:00
console . log ( ` [ModerationAuth] Пользователь не найден, но отправка кода разрешена для ${ emailLower } ` ) ;
2025-12-08 23:42:32 +00:00
}
// Генерировать 6-значный код
const code = crypto . randomInt ( 100000 , 999999 ) . toString ( ) ;
// Удалить старые коды для этого email
await EmailVerificationCode . deleteMany ( {
email : email . toLowerCase ( ) ,
purpose : 'registration'
} ) ;
// Сохранить новый код (действителен 15 минут)
const verificationCode = new EmailVerificationCode ( {
email : email . toLowerCase ( ) ,
code ,
purpose : 'registration' ,
expiresAt : new Date ( Date . now ( ) + 15 * 60 * 1000 ) // 15 минут
} ) ;
await verificationCode . save ( ) ;
// Отправить код на email
try {
await sendVerificationCode ( email , code ) ;
res . json ( {
success : true ,
message : 'Код подтверждения отправлен на email'
} ) ;
} catch ( emailError ) {
console . error ( 'Ошибка отправки email:' , emailError ) ;
await EmailVerificationCode . deleteOne ( { _id : verificationCode . _id } ) ;
2025-12-14 23:45:41 +00:00
let errorMessage = 'Н е удалось отправить код на email.' ;
if ( emailError . code === 'EAUTH' || emailError . message ? . includes ( 'Authentication credentials invalid' ) ) {
errorMessage = 'Ошибка аутентификации SMTP. Проверьте YANDEX_SMTP_USER и YANDEX_SMTP_PASSWORD в .env. Для Yandex используйте пароль приложения, а не основной пароль.' ;
} else if ( emailError . code === 'ECONNECTION' ) {
errorMessage = 'Н е удалось подключиться к SMTP серверу. Проверьте YANDEX_SMTP_HOST и YANDEX_SMTP_PORT.' ;
} else if ( emailError . message ) {
errorMessage = ` Ошибка отправки email: ${ emailError . message } ` ;
}
2025-12-08 23:42:32 +00:00
return res . status ( 500 ) . json ( {
2025-12-14 23:45:41 +00:00
error : errorMessage
2025-12-08 23:42:32 +00:00
} ) ;
}
} catch ( error ) {
console . error ( 'Ошибка отправки кода:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
// Регистрация с кодом подтверждения
router . post ( '/register' , authLimiter , async ( req , res ) => {
try {
const { email , code , password , username } = req . body ;
if ( ! email || ! code || ! password || ! username ) {
return res . status ( 400 ) . json ( { error : 'В с е поля обязательны' } ) ;
}
if ( ! isEmail ( email ) ) {
return res . status ( 400 ) . json ( { error : 'Неверный email адрес' } ) ;
}
if ( password . length < 6 ) {
return res . status ( 400 ) . json ( { error : 'Пароль должен содержать минимум 6 символов' } ) ;
}
// Найти код подтверждения
const verificationCode = await EmailVerificationCode . findOne ( {
email : email . toLowerCase ( ) ,
code ,
purpose : 'registration' ,
verified : false
} ) ;
if ( ! verificationCode ) {
return res . status ( 400 ) . json ( { error : 'Неверный или истекший код' } ) ;
}
// Проверить срок действия
if ( new Date ( ) > verificationCode . expiresAt ) {
await EmailVerificationCode . deleteOne ( { _id : verificationCode . _id } ) ;
return res . status ( 400 ) . json ( { error : 'Код истек. Запросите новый.' } ) ;
}
// Найти пользователя (должен быть создан администратором)
const user = await User . findOne ( {
email : email . toLowerCase ( ) ,
role : { $in : [ 'moderator' , 'admin' ] }
} ) ;
if ( ! user ) {
return res . status ( 403 ) . json ( {
error : 'Регистрация недоступна. Обратитесь к администратору.'
} ) ;
}
// Если у пользователя уже есть пароль - ошибка
if ( user . passwordHash ) {
return res . status ( 400 ) . json ( {
error : 'Аккаунт уже зарегистрирован. Используйте вход по паролю.'
} ) ;
}
// Захешировать пароль
const passwordHash = await bcrypt . hash ( password , 10 ) ;
// Обновить пользователя
user . passwordHash = passwordHash ;
user . emailVerified = true ;
user . username = username || user . username ;
await user . save ( ) ;
// Пометить код как использованный
verificationCode . verified = true ;
await verificationCode . save ( ) ;
// Генерировать токены
const tokens = signAuthTokens ( user ) ;
// Установить cookies
setAuthCookies ( res , tokens ) ;
res . json ( {
success : true ,
user : {
id : user . _id ,
username : user . username ,
role : user . role
} ,
accessToken : tokens . accessToken
} ) ;
} catch ( error ) {
console . error ( 'Ошибка регистрации:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
// Авторизация по email и паролю
router . post ( '/login' , authLimiter , async ( req , res ) => {
try {
const { email , password } = req . body ;
if ( ! email || ! password ) {
return res . status ( 400 ) . json ( { error : 'Email и пароль обязательны' } ) ;
}
if ( ! isEmail ( email ) ) {
return res . status ( 400 ) . json ( { error : 'Неверный email адрес' } ) ;
}
// Найти пользователя с паролем (только модераторы и админы)
const user = await User . findOne ( {
email : email . toLowerCase ( ) ,
passwordHash : { $exists : true , $ne : null } ,
role : { $in : [ 'moderator' , 'admin' ] }
} ) . select ( '+passwordHash' ) ;
if ( ! user ) {
logSecurityEvent ( 'MODERATION_LOGIN_FAILED' , req , { email : email . toLowerCase ( ) } ) ;
return res . status ( 401 ) . json ( { error : 'Неверный email или пароль' } ) ;
}
if ( user . banned ) {
return res . status ( 403 ) . json ( { error : 'Аккаунт заблокирован' } ) ;
}
// Проверить пароль
const isPasswordValid = await bcrypt . compare ( password , user . passwordHash ) ;
if ( ! isPasswordValid ) {
logSecurityEvent ( 'MODERATION_LOGIN_FAILED' , req , { email : email . toLowerCase ( ) , userId : user . _id } ) ;
return res . status ( 401 ) . json ( { error : 'Неверный email или пароль' } ) ;
}
// Обновить время последней активности
user . lastActiveAt = new Date ( ) ;
await user . save ( ) ;
// Генерировать токены
const tokens = signAuthTokens ( user ) ;
// Установить cookies
setAuthCookies ( res , tokens ) ;
logSecurityEvent ( 'MODERATION_LOGIN_SUCCESS' , req , { userId : user . _id , email : user . email } ) ;
// Email не возвращаем в ответе для безопасности
res . json ( {
success : true ,
user : {
id : user . _id ,
username : user . username ,
role : user . role ,
telegramId : user . telegramId
} ,
accessToken : tokens . accessToken
} ) ;
} catch ( error ) {
console . error ( 'Ошибка авторизации:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
// Авторизация через Telegram Login Widget (для обычного браузера)
router . post ( '/telegram-widget' , authLimiter , async ( req , res ) => {
try {
const { id , first _name , last _name , username , photo _url , auth _date , hash } = req . body ;
if ( ! id || ! hash || ! auth _date ) {
return res . status ( 400 ) . json ( { error : 'Неполные данные от Telegram Login Widget' } ) ;
}
// Проверить подпись (базовая проверка)
// В production нужно проверить hash через Bot API
// Для модерации используем упрощенную проверку - ищем пользователя по telegramId
const user = await User . findOne ( { telegramId : id . toString ( ) } ) ;
if ( ! user ) {
return res . status ( 404 ) . json ( { error : 'Пользователь не найден. Сначала зарегистрируйтесь через бота.' } ) ;
}
if ( ! [ 'moderator' , 'admin' ] . includes ( user . role ) ) {
return res . status ( 403 ) . json ( { error : 'Доступ запрещен. У вас нет прав модератора.' } ) ;
}
if ( user . banned ) {
return res . status ( 403 ) . json ( { error : 'Аккаунт заблокирован' } ) ;
}
// Обновить данные пользователя из виджета
if ( username && ! user . username ) {
user . username = username ;
}
if ( first _name && ! user . firstName ) {
user . firstName = first _name ;
}
if ( last _name && ! user . lastName ) {
user . lastName = last _name ;
}
if ( photo _url && ! user . photoUrl ) {
user . photoUrl = photo _url ;
}
user . lastActiveAt = new Date ( ) ;
await user . save ( ) ;
// Генерировать JWT токены
const tokens = signAuthTokens ( user ) ;
setAuthCookies ( res , tokens ) ;
logSecurityEvent ( 'MODERATION_TELEGRAM_WIDGET_LOGIN_SUCCESS' , req , { userId : user . _id } ) ;
res . json ( {
success : true ,
user : {
id : user . _id ,
username : user . username ,
role : user . role ,
telegramId : user . telegramId
} ,
accessToken : tokens . accessToken
} ) ;
} catch ( error ) {
console . error ( 'Ошибка авторизации через Telegram Widget:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
// Авторизация через Telegram (для модерации)
router . post ( '/telegram' , authLimiter , authenticateModeration , async ( req , res ) => {
try {
const user = req . user ;
2025-12-09 00:51:07 +00:00
const { isModerationAdmin } = require ( '../services/moderationAdmin' ) ;
const { normalizeUsername } = require ( '../services/moderationAdmin' ) ;
const config = require ( '../config' ) ;
2025-12-08 23:42:32 +00:00
2025-12-09 00:51:07 +00:00
if ( ! user ) {
2025-12-08 23:42:32 +00:00
return res . status ( 403 ) . json ( { error : 'Доступ запрещен' } ) ;
}
if ( user . banned ) {
return res . status ( 403 ) . json ( { error : 'Аккаунт заблокирован' } ) ;
}
2025-12-09 00:51:07 +00:00
// Проверить доступ: роль admin/moderator ИЛИ является модератором через ModerationAdmin
const username = normalizeUsername ( user . username ) ;
const telegramId = user . telegramId ;
const OWNER _USERNAMES = new Set ( config . moderationOwnerUsernames || [ ] ) ;
const isOwner = OWNER _USERNAMES . has ( username ) ;
const isAdminByRole = [ 'moderator' , 'admin' ] . includes ( user . role ) ;
const isAdminByDB = await isModerationAdmin ( { telegramId , username } ) ;
if ( ! isOwner && ! isAdminByRole && ! isAdminByDB ) {
return res . status ( 403 ) . json ( {
error : 'Доступ запрещен. У вас нет прав модератора. Обратитесь к администратору.'
} ) ;
}
2025-12-08 23:42:32 +00:00
// Обновить время последней активности
user . lastActiveAt = new Date ( ) ;
await user . save ( ) ;
// Генерировать токены
const tokens = signAuthTokens ( user ) ;
// Установить cookies
setAuthCookies ( res , tokens ) ;
logSecurityEvent ( 'MODERATION_TELEGRAM_LOGIN_SUCCESS' , req , { userId : user . _id } ) ;
// Email не возвращаем в ответе для безопасности
res . json ( {
success : true ,
user : {
id : user . _id ,
username : user . username ,
role : user . role ,
telegramId : user . telegramId
} ,
accessToken : tokens . accessToken
} ) ;
} catch ( error ) {
console . error ( 'Ошибка авторизации через Telegram:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
// Выход
router . post ( '/logout' , ( req , res ) => {
clearAuthCookies ( res ) ;
res . json ( { success : true } ) ;
} ) ;
2025-12-09 01:03:25 +00:00
// Получить конфигурацию для фронтенда (включая bot username)
router . get ( '/config' , async ( req , res ) => {
try {
// Если username указан в env - используем е г о
if ( config . moderationBotUsername ) {
return res . json ( {
botUsername : config . moderationBotUsername
} ) ;
}
// Если есть кэш - используем е г о
if ( cachedModerationBotUsername ) {
return res . json ( {
botUsername : cachedModerationBotUsername
} ) ;
}
// Получить username через Bot API используя MODERATION_BOT_TOKEN
if ( config . moderationBotToken ) {
try {
const botInfo = await axios . get ( ` https://api.telegram.org/bot ${ config . moderationBotToken } /getMe ` ) ;
const username = botInfo . data . result ? . username ;
if ( username ) {
cachedModerationBotUsername = username ;
return res . json ( {
botUsername : username
} ) ;
}
} catch ( error ) {
console . error ( 'Ошибка получения username бота модерации через Bot API:' , error . message ) ;
}
}
// Fallback
res . json ( {
botUsername : 'moderation_bot'
} ) ;
} catch ( error ) {
console . error ( 'Ошибка получения конфигурации:' , error ) ;
res . json ( {
botUsername : cachedModerationBotUsername || 'moderation_bot'
} ) ;
}
} ) ;
2025-12-08 23:42:32 +00:00
// Проверка текущей сессии
router . get ( '/me' , async ( req , res ) => {
try {
// Получить токен из заголовка или cookie
let token = null ;
const authHeader = req . headers . authorization ;
if ( authHeader && authHeader . startsWith ( 'Bearer ' ) ) {
token = authHeader . slice ( 7 ) ;
} else if ( req . cookies && req . cookies [ require ( '../utils/tokens' ) . ACCESS _COOKIE ] ) {
token = req . cookies [ require ( '../utils/tokens' ) . ACCESS _COOKIE ] ;
}
if ( ! token ) {
return res . status ( 401 ) . json ( { error : 'Н е авторизован' } ) ;
}
// Проверить токен
let payload ;
try {
payload = verifyAccessToken ( token ) ;
} catch ( error ) {
return res . status ( 401 ) . json ( { error : 'Неверный токен' } ) ;
}
// Найти пользователя
const user = await User . findById ( payload . userId ) ;
if ( ! user ) {
return res . status ( 401 ) . json ( { error : 'Пользователь не найден' } ) ;
}
if ( user . banned ) {
return res . status ( 403 ) . json ( { error : 'Аккаунт заблокирован' } ) ;
}
// Проверить роль (только модераторы и админы)
if ( ! [ 'moderator' , 'admin' ] . includes ( user . role ) ) {
return res . status ( 403 ) . json ( { error : 'Доступ запрещен' } ) ;
}
res . json ( {
success : true ,
user : {
id : user . _id ,
username : user . username ,
role : user . role ,
telegramId : user . telegramId
}
} ) ;
} catch ( error ) {
console . error ( 'Ошибка проверки сессии:' , error ) ;
res . status ( 500 ) . json ( { error : 'Ошибка сервера' } ) ;
}
} ) ;
module . exports = router ;