feat(prod): deploy web dashboard, migrate mongodb configuration, resolve server components render error and fix logo static asset paths
This commit is contained in:
1 parent
4882108068
commit
b2ea883601
119 files changed
+7123
-2011
No files matched your search
+114
-9
@@ -5,9 +5,11 @@ const jwt = require('jsonwebtoken');
|
||||
const User = require('../../models/User');
|
||||
const { makeToken, setCookieToken, requireAuth, JWT_SECRET } = require('./helpers');
|
||||
|
||||
const { TenantConfig, CustomAgentLocation } = require('../../models/Schemas');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// ─── Auto-seed SUPER_ADMIN dan SOC_ANALYST jika belum ada ───────────────────────
|
||||
// ─── Auto-seed SUPER_ADMIN, SOC_ANALYST, dan TENANT_ADMIN jika belum ada ─────────
|
||||
(async () => {
|
||||
try {
|
||||
const count = await User.countDocuments({ role: 'SUPER_ADMIN' });
|
||||
@@ -25,19 +27,122 @@ const router = express.Router();
|
||||
console.log('[Auth] ⚠ GANTI PASSWORD INI SEGERA DI PRODUCTION!');
|
||||
}
|
||||
|
||||
const analystCount = await User.countDocuments({ role: 'SOC_ANALYST' });
|
||||
if (analystCount === 0) {
|
||||
const hash = bcrypt.hashSync('analyst', 10);
|
||||
const siabCount = await User.countDocuments({ username: 'siab' });
|
||||
if (siabCount === 0) {
|
||||
const hash = bcrypt.hashSync('siab', 10);
|
||||
await User.create({
|
||||
username: 'analyst',
|
||||
username: 'siab',
|
||||
password_hash: hash,
|
||||
account_name: 'BackOne SOC Analyst',
|
||||
role: 'SOC_ANALYST',
|
||||
site_uuid: process.env.NETIFY_SITE_UUID || null,
|
||||
account_name: 'SIAB Administrator',
|
||||
role: 'TENANT_ADMIN',
|
||||
site_uuid: '6681452d_9cae_4ff4_8ae8_0d504774265e',
|
||||
agent_uuid: null,
|
||||
});
|
||||
console.log('[Auth] ✓ Default SOC_ANALYST created: analyst / analyst');
|
||||
console.log('[Auth] ✓ Default SIAB Tenant created: siab / siab');
|
||||
}
|
||||
|
||||
const nexusCount = await User.countDocuments({ username: 'nexus' });
|
||||
if (nexusCount === 0) {
|
||||
const hash = bcrypt.hashSync('nexus', 10);
|
||||
await User.create({
|
||||
username: 'nexus',
|
||||
password_hash: hash,
|
||||
account_name: 'Nexus Administrator',
|
||||
role: 'TENANT_ADMIN',
|
||||
site_uuid: 'd7902405_0dc2_458b_8584_ed4d24b64f24',
|
||||
agent_uuid: null,
|
||||
});
|
||||
console.log('[Auth] ✓ Default Nexus Tenant created: nexus / nexus');
|
||||
}
|
||||
|
||||
// Repair/Migration: Ensure legacy users have appropriate created_by values
|
||||
try {
|
||||
const missingCreatedBy = await User.find({ $or: [{ created_by: { $exists: false } }, { created_by: null }] });
|
||||
if (missingCreatedBy.length > 0) {
|
||||
console.log(`[Auth] Migrating ${missingCreatedBy.length} legacy users to set created_by...`);
|
||||
for (const u of missingCreatedBy) {
|
||||
if (u.username === 'admin') {
|
||||
u.created_by = 'admin';
|
||||
} else if (u.site_uuid === '6681452d_9cae_4ff4_8ae8_0d504774265e') {
|
||||
u.created_by = 'siab';
|
||||
} else if (u.site_uuid === 'd7902405_0dc2_458b_8584_ed4d24b64f24') {
|
||||
u.created_by = 'nexus';
|
||||
} else {
|
||||
u.created_by = 'admin';
|
||||
}
|
||||
await u.save();
|
||||
}
|
||||
console.log(`[Auth] Migration complete.`);
|
||||
}
|
||||
} catch (migrateErr) {
|
||||
console.error('[Auth] Migration failed:', migrateErr.message);
|
||||
}
|
||||
|
||||
const defaultConfigs = [
|
||||
{
|
||||
site_uuid: 'default',
|
||||
brand_name: 'BackOne',
|
||||
brand_logo: '/backone-logo.png',
|
||||
footer_copyright: 'PT. Data Bisnis Solusi',
|
||||
primary_color: '#E11D48',
|
||||
},
|
||||
{
|
||||
site_uuid: '6681452d_9cae_4ff4_8ae8_0d504774265e',
|
||||
brand_name: 'SIAB',
|
||||
brand_logo: '/siab-logo.png',
|
||||
footer_copyright: 'PT. SIAB Indonesia',
|
||||
primary_color: '#3B82F6',
|
||||
},
|
||||
{
|
||||
site_uuid: 'd7902405_0dc2_458b_8584_ed4d24b64f24',
|
||||
brand_name: 'Nexus',
|
||||
brand_logo: '/nexus-logo.png',
|
||||
footer_copyright: 'PT. Nexus Solusi',
|
||||
primary_color: '#8B5CF6',
|
||||
}
|
||||
];
|
||||
|
||||
for (const config of defaultConfigs) {
|
||||
const existing = await TenantConfig.findOne({ site_uuid: config.site_uuid });
|
||||
if (!existing) {
|
||||
await TenantConfig.create(config);
|
||||
console.log(`[Auth] ✓ Seeded TenantConfig for: ${config.brand_name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Seed default agent locations
|
||||
const defaultLocations = [
|
||||
{
|
||||
agent_uuid: 'F6-2V-DT-8A',
|
||||
site_uuid: '6681452d_9cae_4ff4_8ae8_0d504774265e',
|
||||
latitude: -6.2263304,
|
||||
longitude: 106.4247322,
|
||||
label: 'CPI Balaraja Agent Office'
|
||||
},
|
||||
{
|
||||
agent_uuid: '2F-TF-1D-GK',
|
||||
site_uuid: '6681452d_9cae_4ff4_8ae8_0d504774265e',
|
||||
latitude: -6.3763318,
|
||||
longitude: 106.8983017,
|
||||
label: 'JRP Cibubur Agent Office'
|
||||
},
|
||||
{
|
||||
agent_uuid: '8A-V3-PB-85',
|
||||
site_uuid: '6681452d_9cae_4ff4_8ae8_0d504774265e',
|
||||
latitude: -6.2253265,
|
||||
longitude: 106.8061484,
|
||||
label: 'IFG LT.18 Agent HQ'
|
||||
}
|
||||
];
|
||||
|
||||
for (const loc of defaultLocations) {
|
||||
const existing = await CustomAgentLocation.findOne({ agent_uuid: loc.agent_uuid });
|
||||
if (!existing) {
|
||||
await CustomAgentLocation.create(loc);
|
||||
console.log(`[Auth] ✓ Seeded CustomAgentLocation for: ${loc.agent_uuid}`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.warn('[Auth] Seed skipped (MongoDB not ready yet):', err.message);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-backone-key';
|
||||
const { requireAuth, requireAdmin, JWT_SECRET } = require('../../middleware/auth');
|
||||
|
||||
function makeToken(user) {
|
||||
return jwt.sign(
|
||||
@@ -31,52 +31,6 @@ function setCookieToken(res, token) {
|
||||
});
|
||||
}
|
||||
|
||||
function requireAuth(req, res, next) {
|
||||
const token = req.cookies?.token;
|
||||
if (!token) return res.status(401).json({ error: 'Not authenticated' });
|
||||
try {
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// ── VIEW-AS MODE ──────────────────────────────────────────────────────────
|
||||
const viewAsHeader = req.headers['x-view-as-agent'];
|
||||
if (viewAsHeader && req.user.role === 'SUPER_ADMIN') {
|
||||
try {
|
||||
const viewDecoded = jwt.verify(viewAsHeader, JWT_SECRET);
|
||||
if (viewDecoded.type === 'view-as' && viewDecoded.adminId === req.user.id && viewDecoded.viewAs) {
|
||||
req.user = {
|
||||
...req.user,
|
||||
role: 'AGENT_VIEWER',
|
||||
agent_uuid: viewDecoded.viewAs,
|
||||
agent_label: viewDecoded.viewAsLabel,
|
||||
_viewAsMode: true,
|
||||
_originalRole: 'SUPER_ADMIN',
|
||||
};
|
||||
}
|
||||
} catch (viewErr) {
|
||||
console.warn('[ViewAs] Invalid view-as token, ignoring:', viewErr.message);
|
||||
}
|
||||
}
|
||||
next();
|
||||
} catch {
|
||||
res.status(401).json({ error: 'Token tidak valid' });
|
||||
}
|
||||
}
|
||||
|
||||
function requireAdmin(req, res, next) {
|
||||
const token = req.cookies?.token;
|
||||
if (!token) return res.status(401).json({ error: 'Not authenticated' });
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
if (decoded.role !== 'SUPER_ADMIN' && decoded.role !== 'TENANT_ADMIN' && decoded.role !== 'SOC_ANALYST') {
|
||||
return res.status(403).json({ error: 'Role Anda tidak memiliki izin untuk melakukan aksi ini (Hanya Administrator / Analyst)' });
|
||||
}
|
||||
req.adminUser = decoded;
|
||||
next();
|
||||
} catch {
|
||||
res.status(401).json({ error: 'Token tidak valid' });
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadsDir() {
|
||||
if (fs.existsSync('/home/adminbackend/web/demoplace.my.id/public_html')) {
|
||||
return '/home/adminbackend/web/demoplace.my.id/public_html/api/uploads';
|
||||
|
||||
@@ -17,7 +17,17 @@ function blockAnalyst(req, res, next) {
|
||||
// GET /api/auth/admin/users — daftar semua users (admin & analyst)
|
||||
router.get('/admin/users', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const users = await User.find({}, '-password_hash').sort({ created_at: 1 });
|
||||
let query = {};
|
||||
if (req.adminUser.role === 'TENANT_ADMIN') {
|
||||
query = {
|
||||
$or: [
|
||||
{ role: 'AGENT_VIEWER', site_uuid: req.adminUser.site_uuid },
|
||||
{ created_by: req.adminUser.username }
|
||||
]
|
||||
};
|
||||
}
|
||||
query.username = { $ne: req.adminUser.username };
|
||||
const users = await User.find(query, '-password_hash').sort({ created_at: 1 });
|
||||
const data = users.map(u => ({
|
||||
id: u._id.toString(),
|
||||
username: u.username,
|
||||
@@ -42,7 +52,21 @@ router.post('/admin/create-agent-user', requireAdmin, blockAnalyst, async (req,
|
||||
return res.status(400).json({ ok: false, error: 'Username dan password wajib diisi' });
|
||||
}
|
||||
const passwordHash = bcrypt.hashSync(password, 10);
|
||||
const siteUuid = process.env.BACKONE_SITE_UUID || process.env.NETIFY_SITE_UUID || null;
|
||||
|
||||
let siteUuid = null;
|
||||
if (agent_uuid) {
|
||||
const { Summary } = require('../../models/Schemas');
|
||||
const summaryDoc = await Summary.findOne({ agent_uuid: agent_uuid.trim() });
|
||||
if (summaryDoc) {
|
||||
siteUuid = summaryDoc.site_uuid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!siteUuid) {
|
||||
siteUuid = req.adminUser.role === 'SUPER_ADMIN'
|
||||
? (req.body.site_uuid || process.env.BACKONE_SITE_UUID || process.env.NETIFY_SITE_UUID || null)
|
||||
: req.adminUser.site_uuid;
|
||||
}
|
||||
|
||||
const newUser = await User.create({
|
||||
username: username.trim(),
|
||||
@@ -51,6 +75,7 @@ router.post('/admin/create-agent-user', requireAdmin, blockAnalyst, async (req,
|
||||
agent_uuid: agent_uuid?.trim() || null,
|
||||
role: 'AGENT_VIEWER',
|
||||
site_uuid: siteUuid,
|
||||
created_by: req.adminUser.username,
|
||||
});
|
||||
|
||||
res.json({ ok: true, message: 'Akun Network Agent berhasil dibuat', userId: newUser._id.toString() });
|
||||
@@ -69,6 +94,10 @@ router.post('/admin/update-agent-user', requireAdmin, blockAnalyst, upload.singl
|
||||
const target = await User.findById(user_id).select('+password_hash');
|
||||
if (!target) return res.status(404).json({ ok: false, error: 'User tidak ditemukan' });
|
||||
if (target.role === 'SUPER_ADMIN') return res.status(403).json({ ok: false, error: 'Tidak bisa mengubah akun SUPER_ADMIN dari sini' });
|
||||
|
||||
if (req.adminUser.role !== 'SUPER_ADMIN' && target.site_uuid !== req.adminUser.site_uuid) {
|
||||
return res.status(403).json({ ok: false, error: 'Unauthorized: This account does not belong to your tenant.' });
|
||||
}
|
||||
|
||||
if (username?.trim()) {
|
||||
const existing = await User.findOne({ username: username.trim(), _id: { $ne: user_id } });
|
||||
@@ -77,7 +106,16 @@ router.post('/admin/update-agent-user', requireAdmin, blockAnalyst, upload.singl
|
||||
}
|
||||
if (password) target.password_hash = bcrypt.hashSync(password, 10);
|
||||
if (account_name != null) target.account_name = account_name?.trim() || null;
|
||||
if (agent_uuid != null) target.agent_uuid = agent_uuid?.trim() || null;
|
||||
if (agent_uuid != null) {
|
||||
target.agent_uuid = agent_uuid?.trim() || null;
|
||||
if (agent_uuid.trim()) {
|
||||
const { Summary } = require('../../models/Schemas');
|
||||
const summaryDoc = await Summary.findOne({ agent_uuid: agent_uuid.trim() });
|
||||
if (summaryDoc) {
|
||||
target.site_uuid = summaryDoc.site_uuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (req.file) target.profile_picture = req.file.filename;
|
||||
|
||||
await target.save();
|
||||
@@ -94,6 +132,10 @@ router.delete('/admin/delete-agent-user/:id', requireAdmin, blockAnalyst, async
|
||||
const target = await User.findById(req.params.id);
|
||||
if (!target) return res.status(404).json({ ok: false, error: 'User tidak ditemukan' });
|
||||
if (target.role === 'SUPER_ADMIN') return res.status(403).json({ ok: false, error: 'Tidak bisa menghapus SUPER_ADMIN' });
|
||||
|
||||
if (req.adminUser.role !== 'SUPER_ADMIN' && target.site_uuid !== req.adminUser.site_uuid) {
|
||||
return res.status(403).json({ ok: false, error: 'Unauthorized: This account does not belong to your tenant.' });
|
||||
}
|
||||
await User.findByIdAndDelete(req.params.id);
|
||||
res.json({ ok: true, message: 'Akun berhasil dihapus' });
|
||||
} catch (err) {
|
||||
@@ -107,6 +149,10 @@ router.post('/admin/upload-agent-picture/:id', requireAdmin, blockAnalyst, uploa
|
||||
if (!req.file) return res.status(400).json({ ok: false, error: 'File gambar wajib diupload' });
|
||||
const target = await User.findById(req.params.id);
|
||||
if (!target) return res.status(404).json({ ok: false, error: 'User tidak ditemukan' });
|
||||
|
||||
if (req.adminUser.role !== 'SUPER_ADMIN' && target.site_uuid !== req.adminUser.site_uuid) {
|
||||
return res.status(403).json({ ok: false, error: 'Unauthorized: This account does not belong to your tenant.' });
|
||||
}
|
||||
target.profile_picture = req.file.filename;
|
||||
await target.save();
|
||||
res.json({ ok: true, message: 'Foto profil berhasil diperbarui', filename: req.file.filename });
|
||||
@@ -135,7 +181,13 @@ router.post('/admin/create-external-user', requireAdmin, blockAnalyst, upload.si
|
||||
}
|
||||
|
||||
const passwordHash = bcrypt.hashSync(password, 10);
|
||||
const siteUuid = process.env.BACKONE_SITE_UUID || process.env.NETIFY_SITE_UUID || null;
|
||||
const siteUuid = req.adminUser.role === 'SUPER_ADMIN'
|
||||
? (req.body.site_uuid || process.env.BACKONE_SITE_UUID || process.env.NETIFY_SITE_UUID || null)
|
||||
: req.adminUser.site_uuid;
|
||||
|
||||
const createdBy = req.adminUser.role === 'SUPER_ADMIN'
|
||||
? (req.body.created_by || req.adminUser.username)
|
||||
: req.adminUser.username;
|
||||
|
||||
const newUser = await User.create({
|
||||
username: username.trim(),
|
||||
@@ -143,6 +195,7 @@ router.post('/admin/create-external-user', requireAdmin, blockAnalyst, upload.si
|
||||
account_name: account_name?.trim() || null,
|
||||
role: role,
|
||||
site_uuid: siteUuid,
|
||||
created_by: createdBy,
|
||||
profile_picture: req.file ? req.file.filename : null
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user