auth service
This commit is contained in:
parent
790411a2d7
commit
26973a5126
38
auth-service/src/controllers/authController.js
Normal file
38
auth-service/src/controllers/authController.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import { User } from '../models/index.js';
|
||||||
|
|
||||||
|
function getJwtSecret(payload) {
|
||||||
|
const base = process.env.JWT_SECRET || 'secret';
|
||||||
|
// Если rememberMe, используем только base
|
||||||
|
if (payload && payload.rememberMe) return base;
|
||||||
|
// Иначе добавляем "часовой" компонент
|
||||||
|
const hour = new Date().toISOString().slice(0, 13); // YYYY-MM-DDTHH
|
||||||
|
return base + ':' + hour;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
async verify(req, res) {
|
||||||
|
let token = req.body.token;
|
||||||
|
if (!token && req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
|
||||||
|
token = req.headers.authorization.split(' ')[1];
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({ error: 'No token provided' });
|
||||||
|
}
|
||||||
|
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' });
|
||||||
|
res.json({ id: user.id, email: user.email, role_id: user.role_id });
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(401).json({ error: 'Invalid token', details: err.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -65,12 +65,22 @@ export default {
|
|||||||
},
|
},
|
||||||
async login(req, res) {
|
async login(req, res) {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.body;
|
const { email, password, rememberMe } = req.body;
|
||||||
const user = await User.findOne({ where: { email } });
|
const user = await User.findOne({ where: { email } });
|
||||||
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
const valid = await bcrypt.compare(password, user.password_hash);
|
const valid = await bcrypt.compare(password, user.password_hash);
|
||||||
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
|
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
const token = jwt.sign({ id: user.id, role_id: user.role_id }, process.env.JWT_SECRET, { expiresIn: '1d' });
|
let token, secret, expiresIn;
|
||||||
|
if (rememberMe) {
|
||||||
|
secret = process.env.JWT_SECRET || 'secret';
|
||||||
|
expiresIn = '30d';
|
||||||
|
} else {
|
||||||
|
const base = process.env.JWT_SECRET || 'secret';
|
||||||
|
const hour = new Date().toISOString().slice(0, 13); // YYYY-MM-DDTHH
|
||||||
|
secret = base + ':' + hour;
|
||||||
|
expiresIn = '1h';
|
||||||
|
}
|
||||||
|
token = jwt.sign({ id: user.id, role_id: user.role_id, rememberMe: !!rememberMe }, secret, { expiresIn });
|
||||||
res.json({ token, user: { id: user.id, email: user.email, name: user.name, role_id: user.role_id } });
|
res.json({ token, user: { id: user.id, email: user.email, name: user.name, role_id: user.role_id } });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import userRoutes from './user.js';
|
|||||||
import roleRoutes from './role.js';
|
import roleRoutes from './role.js';
|
||||||
import permissionRoutes from './permission.js';
|
import permissionRoutes from './permission.js';
|
||||||
import rolePermissionRoutes from './rolePermission.js';
|
import rolePermissionRoutes from './rolePermission.js';
|
||||||
|
import authController from '../controllers/authController.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -10,5 +11,6 @@ router.use('/users', userRoutes);
|
|||||||
router.use('/roles', roleRoutes);
|
router.use('/roles', roleRoutes);
|
||||||
router.use('/permissions', permissionRoutes);
|
router.use('/permissions', permissionRoutes);
|
||||||
router.use('/role-permissions', rolePermissionRoutes);
|
router.use('/role-permissions', rolePermissionRoutes);
|
||||||
|
router.post('/verify', authController.verify);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@ -3,13 +3,22 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d
|
|||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import Dashboard from './pages/Dashboard';
|
import Dashboard from './pages/Dashboard';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import { useUser } from './context/UserContext';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const { user, token, isCheckingAuth } = useUser();
|
||||||
|
|
||||||
|
if (isCheckingAuth) {
|
||||||
|
return <div style={{ textAlign: 'center', marginTop: 80 }}>Проверка авторизации...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route
|
||||||
|
path="/dashboard"
|
||||||
|
element={user && token ? <Dashboard /> : <Navigate to="/login" replace />} />
|
||||||
<Route path="*" element={<Navigate to="/login" replace />} />
|
<Route path="*" element={<Navigate to="/login" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const UserContext = createContext();
|
|||||||
export function UserProvider({ children }) {
|
export function UserProvider({ children }) {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [token, setToken] = useState(null);
|
const [token, setToken] = useState(null);
|
||||||
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Инициализация из localStorage
|
// Инициализация из localStorage
|
||||||
@ -13,7 +14,27 @@ export function UserProvider({ children }) {
|
|||||||
if (storedUser && storedToken) {
|
if (storedUser && storedToken) {
|
||||||
setUser(JSON.parse(storedUser));
|
setUser(JSON.parse(storedUser));
|
||||||
setToken(storedToken);
|
setToken(storedToken);
|
||||||
|
// Проверка токена
|
||||||
|
fetch('/api/auth/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${storedToken}` },
|
||||||
|
body: JSON.stringify({ token: storedToken })
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok) throw new Error('Invalid token');
|
||||||
|
return res.json();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
setIsCheckingAuth(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
logout();
|
||||||
|
window.location.href = '/login';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setIsCheckingAuth(false);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = (user, token) => {
|
const login = (user, token) => {
|
||||||
@ -31,7 +52,7 @@ export function UserProvider({ children }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UserContext.Provider value={{ user, token, login, logout }}>
|
<UserContext.Provider value={{ user, token, login, logout, isCheckingAuth }}>
|
||||||
{children}
|
{children}
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -25,12 +25,18 @@ function SmtpServersPage() {
|
|||||||
}, [page]);
|
}, [page]);
|
||||||
|
|
||||||
const fetchServers = async (page) => {
|
const fetchServers = async (page) => {
|
||||||
|
if (!token) {
|
||||||
|
setError('Нет авторизации');
|
||||||
|
setServers([]);
|
||||||
|
setTotal(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
const offset = (page - 1) * PAGE_SIZE;
|
const offset = (page - 1) * PAGE_SIZE;
|
||||||
const res = await fetch(`/api/mail/smtp-servers?limit=${PAGE_SIZE}&offset=${offset}`, {
|
const res = await fetch(`/api/mail/smtp-servers?limit=${PAGE_SIZE}&offset=${offset}`, {
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -51,12 +57,16 @@ function SmtpServersPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id) => {
|
const handleDelete = async (id) => {
|
||||||
|
if (!token) {
|
||||||
|
alert('Нет авторизации');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!window.confirm('Удалить SMTP-сервер?')) return;
|
if (!window.confirm('Удалить SMTP-сервер?')) return;
|
||||||
setDeleteLoading(id);
|
setDeleteLoading(id);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/mail/smtp-servers/${id}`, {
|
const res = await fetch(`/api/mail/smtp-servers/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@ -77,6 +87,10 @@ function SmtpServersPage() {
|
|||||||
|
|
||||||
const handleEditSave = async (e) => {
|
const handleEditSave = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!token) {
|
||||||
|
alert('Нет авторизации');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setEditLoading(true);
|
setEditLoading(true);
|
||||||
try {
|
try {
|
||||||
const { group_id, ...serverData } = editServer;
|
const { group_id, ...serverData } = editServer;
|
||||||
@ -84,7 +98,7 @@ function SmtpServersPage() {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {})
|
Authorization: `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ ...serverData, user_id: user?.id })
|
body: JSON.stringify({ ...serverData, user_id: user?.id })
|
||||||
});
|
});
|
||||||
@ -110,6 +124,10 @@ function SmtpServersPage() {
|
|||||||
|
|
||||||
const handleCreateSave = async (e) => {
|
const handleCreateSave = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!token) {
|
||||||
|
alert('Нет авторизации');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setCreateLoading(true);
|
setCreateLoading(true);
|
||||||
try {
|
try {
|
||||||
const { group_id, ...serverData } = createServer;
|
const { group_id, ...serverData } = createServer;
|
||||||
@ -117,7 +135,7 @@ function SmtpServersPage() {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...(token ? { Authorization: `Bearer ${token}` } : {})
|
Authorization: `Bearer ${token}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ ...serverData, user_id: user?.id })
|
body: JSON.stringify({ ...serverData, user_id: user?.id })
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { sequelize } from './models/index.js';
|
|||||||
import routes from './routes/index.js';
|
import routes from './routes/index.js';
|
||||||
import { processScheduledCampaigns } from './service/queueFillerJob.js';
|
import { processScheduledCampaigns } from './service/queueFillerJob.js';
|
||||||
import { startMailSender } from './service/mailSender.js';
|
import { startMailSender } from './service/mailSender.js';
|
||||||
|
import authMiddleware from './middleware/auth.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@ -13,7 +14,7 @@ app.get('/', (req, res) => {
|
|||||||
res.send('Mail Service is running');
|
res.send('Mail Service is running');
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use('/api/mail', routes);
|
app.use('/api/mail', authMiddleware, routes);
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
23
mail-service/src/middleware/auth.js
Normal file
23
mail-service/src/middleware/auth.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
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 {
|
||||||
|
// Проверяем токен через auth-service
|
||||||
|
const resp = await fetch('http://auth-service:3000/api/auth/verify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
|
||||||
|
body: JSON.stringify({ token }),
|
||||||
|
});
|
||||||
|
if (!resp.ok) {
|
||||||
|
return res.status(401).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
const user = await resp.json();
|
||||||
|
req.user = user;
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(401).json({ error: 'Auth service error', details: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user