auth middleware and role edit ui

This commit is contained in:
romantarkin 2025-07-31 14:46:19 +05:00
parent 950b892bec
commit a283f57af3
8 changed files with 99 additions and 22 deletions

View File

@ -0,0 +1,39 @@
import jwt from 'jsonwebtoken';
import { User } from '../models/index.js';
function getJwtSecret(payload) {
const base = process.env.JWT_SECRET || 'secret';
if (payload && payload.rememberMe) {
return base;
}
const hour = new Date().toISOString().slice(0, 13); // YYYY-MM-DDTHH
return base + ':' + hour;
}
export default async function authMiddleware(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
// Сначала декодируем без проверки, чтобы узнать rememberMe
let payload = null;
try {
payload = jwt.decode(token);
} catch {}
const secret = getJwtSecret(payload);
payload = jwt.verify(token, secret);
// Найти пользователя по id
const user = await User.findByPk(payload.id);
if (!user) return res.status(401).json({ error: 'User not found' });
req.user = { id: user.id, email: user.email, role_id: user.role_id };
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token', details: err.message });
}
}

View File

@ -1,12 +1,13 @@
import { Router } from 'express'; import { Router } from 'express';
import roleController from '../controllers/roleController.js'; import roleController from '../controllers/roleController.js';
import authMiddleware from '../middleware/auth.js';
const router = Router(); const router = Router();
router.post('/', roleController.create); router.post('/', authMiddleware, roleController.create);
router.get('/', roleController.getAll); router.get('/', authMiddleware, roleController.getAll);
router.get('/:id', roleController.getById); router.get('/:id', authMiddleware, roleController.getById);
router.put('/:id', roleController.update); router.put('/:id', authMiddleware, roleController.update);
router.delete('/:id', roleController.delete); router.delete('/:id', authMiddleware, roleController.delete);
export default router; export default router;

View File

@ -1,13 +1,14 @@
import { Router } from 'express'; import { Router } from 'express';
import userController from '../controllers/userController.js'; import userController from '../controllers/userController.js';
import authMiddleware from '../middleware/auth.js';
const router = Router(); const router = Router();
router.post('/', userController.create); router.post('/', authMiddleware, userController.create);
router.get('/', userController.getAll); router.get('/', authMiddleware, userController.getAll);
router.get('/:id', userController.getById); router.get('/:id', authMiddleware, userController.getById);
router.put('/:id', userController.update); router.put('/:id', authMiddleware, userController.update);
router.delete('/:id', userController.delete); router.delete('/:id', authMiddleware, userController.delete);
router.post('/login', userController.login); router.post('/login', userController.login);
export default router; export default router;

View File

@ -18,7 +18,6 @@ const SideMenu = ({ active, onSelect }) => {
<div className={styles.section}>Администрирование</div> <div className={styles.section}>Администрирование</div>
<ul> <ul>
<li className={active === 'users' ? styles.active : ''} onClick={() => onSelect('users')}>Управление пользователями</li> <li className={active === 'users' ? styles.active : ''} onClick={() => onSelect('users')}>Управление пользователями</li>
<li className={active === 'roles' ? styles.active : ''} onClick={() => onSelect('roles')}>Управление ролями</li>
</ul> </ul>
</nav> </nav>
</aside> </aside>

View File

@ -15,10 +15,19 @@ export default function CreateUserModal({ isOpen, onClose, user, roles, loading,
</label> </label>
<label className={styles.label}>Роль <label className={styles.label}>Роль
<select value={user.role_id} onChange={e => onChange({ ...user, role_id: Number(e.target.value) })} required className={styles.input}> <select value={user.role_id} onChange={e => onChange({ ...user, role_id: Number(e.target.value) })} required className={styles.input}>
{roles.map(role => ( {roles.length === 0 ? (
<option value="">Загрузка ролей...</option>
) : (
roles.map(role => (
<option key={role.id} value={role.id}>{role.name}</option> <option key={role.id} value={role.id}>{role.name}</option>
))} ))
)}
</select> </select>
{process.env.NODE_ENV === 'development' && (
<div style={{ fontSize: '10px', color: '#666', marginTop: '4px' }}>
Debug: {roles.length} roles, user.role_id: {user.role_id}
</div>
)}
</label> </label>
<label className={styles.label}>Пароль <label className={styles.label}>Пароль
<input type="password" value={user.password} onChange={e => onChange({ ...user, password: e.target.value })} required className={styles.input} /> <input type="password" value={user.password} onChange={e => onChange({ ...user, password: e.target.value })} required className={styles.input} />

View File

@ -15,10 +15,19 @@ export default function EditUserModal({ isOpen, onClose, user, roles, loading, o
</label> </label>
<label className={styles.label}>Роль <label className={styles.label}>Роль
<select value={user.role_id} onChange={e => onChange({ ...user, role_id: Number(e.target.value) })} required className={styles.input}> <select value={user.role_id} onChange={e => onChange({ ...user, role_id: Number(e.target.value) })} required className={styles.input}>
{roles.map(role => ( {roles.length === 0 ? (
<option value="">Загрузка ролей...</option>
) : (
roles.map(role => (
<option key={role.id} value={role.id}>{role.name}</option> <option key={role.id} value={role.id}>{role.name}</option>
))} ))
)}
</select> </select>
{process.env.NODE_ENV === 'development' && (
<div style={{ fontSize: '10px', color: '#666', marginTop: '4px' }}>
Debug: {roles.length} roles, user.role_id: {user.role_id}
</div>
)}
</label> </label>
<div className={styles.actions}> <div className={styles.actions}>
<button type="submit" disabled={loading} className={styles.saveBtn}>{loading ? 'Сохранение...' : 'Сохранить'}</button> <button type="submit" disabled={loading} className={styles.saveBtn}>{loading ? 'Сохранение...' : 'Сохранить'}</button>

View File

@ -3,7 +3,6 @@ import SideMenu from '../components/SideMenu';
import Header from '../components/Header'; import Header from '../components/Header';
import { useUser } from '../context/UserContext'; import { useUser } from '../context/UserContext';
import UsersPage from './UsersPage'; import UsersPage from './UsersPage';
import RolesPage from './RolesPage';
import SmtpServersPage from './SmtpServersPage'; import SmtpServersPage from './SmtpServersPage';
import EmailTemplatesPage from './EmailTemplatesPage'; import EmailTemplatesPage from './EmailTemplatesPage';
import UnsubscribedPage from './UnsubscribedPage'; import UnsubscribedPage from './UnsubscribedPage';
@ -23,7 +22,6 @@ const Dashboard = () => {
function renderPage() { function renderPage() {
switch (active) { switch (active) {
case 'users': return <UsersPage />; case 'users': return <UsersPage />;
case 'roles': return <RolesPage />;
case 'smtp': return <SmtpServersPage />; case 'smtp': return <SmtpServersPage />;
case 'template': return <EmailTemplatesPage />; case 'template': return <EmailTemplatesPage />;
case 'unsubscribed': return <UnsubscribedPage />; case 'unsubscribed': return <UnsubscribedPage />;

View File

@ -62,12 +62,18 @@ function UsersPage() {
headers: token ? { Authorization: `Bearer ${token}` } : {} headers: token ? { Authorization: `Bearer ${token}` } : {}
}); });
const data = await res.json(); const data = await res.json();
if (res.ok && Array.isArray(data)) { console.log('Roles API response:', data); // Отладочная информация
setRoles(data); if (res.ok) {
// API всегда возвращает объект с rows и count
const rolesData = data.rows || [];
console.log('Setting roles:', rolesData); // Отладочная информация
setRoles(rolesData);
} else { } else {
console.error('Roles API error:', data); // Отладочная информация
setRoles([]); setRoles([]);
} }
} catch { } catch (error) {
console.error('Roles fetch error:', error); // Отладочная информация
setRoles([]); setRoles([]);
} }
}; };
@ -128,6 +134,11 @@ function UsersPage() {
}; };
const handleCreate = () => { const handleCreate = () => {
// Убеждаемся, что у нас есть роли перед созданием пользователя
if (roles.length === 0) {
alert('Загрузка ролей... Пожалуйста, подождите.');
return;
}
setCreateUser({ email: '', name: '', role_id: roles[0]?.id || 1, password: '' }); setCreateUser({ email: '', name: '', role_id: roles[0]?.id || 1, password: '' });
}; };
@ -232,6 +243,16 @@ function UsersPage() {
onSave={handleCreateSave} onSave={handleCreateSave}
/> />
)} )}
{/* Отладочная информация */}
{process.env.NODE_ENV === 'development' && (
<div style={{ marginTop: 20, padding: 10, background: '#f0f0f0', fontSize: '12px' }}>
<strong>Debug Info:</strong><br/>
Roles count: {roles.length}<br/>
Roles: {JSON.stringify(roles.slice(0, 3))}<br/>
Edit user: {editUser ? JSON.stringify(editUser) : 'null'}<br/>
Create user: {createUser ? JSON.stringify(createUser) : 'null'}
</div>
)}
</div> </div>
); );
} }