206 lines
6.9 KiB
JavaScript
206 lines
6.9 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
require('dotenv').config();
|
|
|
|
const db = require('./db');
|
|
const { generateLicense } = require('./licenseManager');
|
|
const jwt = require('jsonwebtoken');
|
|
const bcrypt = require('bcryptjs');
|
|
const cookieParser = require('cookie-parser');
|
|
|
|
const app = express();
|
|
const PORT = process.env.LICENSE_SERVER_PORT || 3006;
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'fallback_secret';
|
|
|
|
app.use(cors({
|
|
origin: true,
|
|
credentials: true
|
|
}));
|
|
app.use(express.json());
|
|
app.use(cookieParser());
|
|
|
|
// Auth Middleware
|
|
const authenticateToken = (req, res, next) => {
|
|
const token = req.cookies.token;
|
|
if (!token) return res.status(401).json({ error: 'Unauthorized: No token provided' });
|
|
|
|
jwt.verify(token, JWT_SECRET, (err, user) => {
|
|
if (err) return res.status(403).json({ error: 'Forbidden: Invalid token' });
|
|
req.user = user;
|
|
next();
|
|
});
|
|
};
|
|
|
|
// Load Private Key for Generation
|
|
const privateKeyPath = path.join(__dirname, '../config/private_key.pem');
|
|
let privateKey = null;
|
|
try {
|
|
if (fs.existsSync(privateKeyPath)) {
|
|
privateKey = fs.readFileSync(privateKeyPath, 'utf8');
|
|
} else {
|
|
console.error('WARNING: private_key.pem not found in config. Generation will fail.');
|
|
}
|
|
} catch (e) {
|
|
console.error('Error loading private key:', e);
|
|
}
|
|
|
|
// --- Auth Routes ---
|
|
app.post('/api/auth/login', async (req, res) => {
|
|
const { loginId, password } = req.body;
|
|
try {
|
|
const [users] = await db.query('SELECT * FROM users WHERE login_id = ?', [loginId]);
|
|
if (users.length === 0) return res.status(401).json({ error: 'Invalid ID or Password' });
|
|
|
|
const user = users[0];
|
|
const validPassword = await bcrypt.compare(password, user.password);
|
|
if (!validPassword) return res.status(401).json({ error: 'Invalid ID or Password' });
|
|
|
|
const token = jwt.sign({ id: user.id, loginId: user.login_id, name: user.name }, JWT_SECRET, { expiresIn: '24h' });
|
|
|
|
res.cookie('token', token, {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
|
});
|
|
|
|
res.json({ success: true, user: { id: user.id, loginId: user.login_id, name: user.name } });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Login failed' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/auth/me', authenticateToken, (req, res) => {
|
|
res.json(req.user);
|
|
});
|
|
|
|
app.post('/api/auth/logout', (req, res) => {
|
|
res.clearCookie('token');
|
|
res.json({ success: true });
|
|
});
|
|
|
|
app.post('/api/auth/change-password', authenticateToken, async (req, res) => {
|
|
const { currentPassword, newPassword } = req.body;
|
|
const userId = req.user.id;
|
|
|
|
try {
|
|
const [users] = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
|
|
if (users.length === 0) return res.status(404).json({ error: 'User not found' });
|
|
|
|
const user = users[0];
|
|
const validPassword = await bcrypt.compare(currentPassword, user.password);
|
|
if (!validPassword) return res.status(400).json({ error: '현재 비밀번호가 일치하지 않습니다.' });
|
|
|
|
const hashedPw = await bcrypt.hash(newPassword, 10);
|
|
await db.query('UPDATE users SET password = ? WHERE id = ?', [hashedPw, userId]);
|
|
|
|
res.json({ success: true, message: '비밀번호가 성공적으로 변경되었습니다.' });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: '비밀번호 변경 실패' });
|
|
}
|
|
});
|
|
|
|
// --- License Routes ---
|
|
// 1. Get All Issued Licenses
|
|
app.get('/api/licenses', authenticateToken, async (req, res) => {
|
|
try {
|
|
const [rows] = await db.query('SELECT * FROM issued_licenses ORDER BY created_at DESC');
|
|
res.json(rows);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Database error' });
|
|
}
|
|
});
|
|
|
|
// 1.1 Delete License
|
|
app.delete('/api/licenses/:id', authenticateToken, async (req, res) => {
|
|
const { id } = req.params;
|
|
try {
|
|
await db.query('DELETE FROM issued_licenses WHERE id = ?', [id]);
|
|
res.json({ success: true, message: 'License deleted' });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Failed to delete license' });
|
|
}
|
|
});
|
|
|
|
// 1.2 Activate License by Key (called by SmartIMS)
|
|
app.post('/api/licenses/activate', async (req, res) => {
|
|
const { licenseKey } = req.body;
|
|
if (!licenseKey) return res.status(400).json({ error: 'License key is required' });
|
|
|
|
try {
|
|
const [result] = await db.query('UPDATE issued_licenses SET status = "ACTIVATED" WHERE license_key = ?', [licenseKey]);
|
|
if (result.affectedRows === 0) {
|
|
return res.status(404).json({ error: 'License key not found' });
|
|
}
|
|
res.json({ success: true, message: 'License status updated to ACTIVATED' });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Database error' });
|
|
}
|
|
});
|
|
|
|
// 2. Generate New License
|
|
app.post('/api/licenses/generate', authenticateToken, async (req, res) => {
|
|
const { moduleCode, type, subscriberId, expiryDate } = req.body;
|
|
|
|
if (!moduleCode || !type || !subscriberId) {
|
|
return res.status(400).json({ error: 'Missing required fields' });
|
|
}
|
|
|
|
if (!privateKey) {
|
|
return res.status(500).json({ error: 'Server key configuration error' });
|
|
}
|
|
|
|
try {
|
|
// Generate the key
|
|
// For DEV, we don't pass expiryDate to make it permanent
|
|
const finalExpiryDate = type === 'dev' ? null : expiryDate;
|
|
|
|
const licenseKey = generateLicense(
|
|
moduleCode,
|
|
type,
|
|
subscriberId,
|
|
null, // activationDays
|
|
finalExpiryDate,
|
|
privateKey
|
|
);
|
|
|
|
// Store in DB
|
|
const sql = `
|
|
INSERT INTO issued_licenses (module_code, license_key, license_type, subscriber_id, status, created_at)
|
|
VALUES (?, ?, ?, ?, 'WAITING', NOW())
|
|
`;
|
|
await db.query(sql, [moduleCode, licenseKey, type, subscriberId]);
|
|
|
|
res.json({ success: true, licenseKey });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: err.message || 'Failed to generate license' });
|
|
}
|
|
});
|
|
|
|
// Serve Static Files (Production)
|
|
const clientDistPath = path.join(__dirname, '../client/dist');
|
|
if (fs.existsSync(clientDistPath)) {
|
|
app.use(express.static(clientDistPath));
|
|
|
|
// SPA Fallback: Any route not handled by API should return index.html
|
|
app.get(/.*/, (req, res) => {
|
|
if (!req.path.startsWith('/api')) {
|
|
res.sendFile(path.join(clientDistPath, 'index.html'));
|
|
}
|
|
});
|
|
console.log(`📁 Serving client from ${clientDistPath}`);
|
|
} else {
|
|
console.log('⚠️ Client dist folder not found. API mode only.');
|
|
}
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 License Manager Server running on port ${PORT}`);
|
|
});
|