This commit is contained in:
2026-09-12 01:44:03 +02:00
parent 3dda406954
commit 155395237b
16 changed files with 1045 additions and 31 deletions
+78
View File
@@ -1755,5 +1755,83 @@ export default (router, tpl) => {
}
});
// ── Admin Bar: User Impersonation ────────────────────────────────────────────
// GET /api/v2/admin/users/search?q= — autocomplete for the admin bar "View as" input
router.get(/^\/api\/v2\/admin\/users\/search\/?$/, lib.adminAuth, async (req, res) => {
try {
const q = (req.url.qs?.q || '').trim();
if (!q || q.length < 1) {
if (res.json) return res.json([]);
return res.writeHead(200, { 'Content-Type': 'application/json' }).end('[]');
}
const escaped = lib.escapeLike(q);
const users = await db`
SELECT id, login as user
FROM "user"
WHERE login ILIKE ${'%' + escaped + '%'}
AND activated = true
AND banned = false
ORDER BY login ASC
LIMIT 10
`;
const result = users.map(u => ({ id: u.id, user: u.user }));
if (res.json) return res.json(result);
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify(result));
} catch (e) {
if (res.json) return res.json({ success: false, msg: e.message });
return res.writeHead(500, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: e.message }));
}
});
// POST /api/v2/admin/impersonate — start impersonating a user
router.post(/^\/api\/v2\/admin\/impersonate\/?$/, lib.adminAuth, async (req, res) => {
try {
const { username } = req.post;
if (!username) throw new Error('Username required');
const target = await db`
SELECT id, login as user
FROM "user"
WHERE login = ${username.toLowerCase().trim()}
AND activated = true
LIMIT 1
`;
if (target.length === 0) throw new Error('User not found');
if (target[0].id === req.session.id) throw new Error('Cannot impersonate yourself');
// Build signed payload: base64(JSON) + "." + HMAC
const crypto = (await import('crypto')).default || await import('crypto');
const secret = cfg.main.secret || cfg.main.url.full || 'f0ckm-impersonate-secret';
const payload = Buffer.from(JSON.stringify({
uid: target[0].id,
orig: lib.sha256(req.cookies.session),
ts: Date.now()
})).toString('base64url');
const sig = crypto.createHmac('sha256', secret).update(payload).digest('hex');
const cookieVal = `${payload}.${sig}`;
const cookieOpts = lib.getCookieOptions('Fri, 31 Dec 9999 23:59:59 GMT');
res.writeHead(200, {
'Content-Type': 'application/json',
'Set-Cookie': `impersonate=${cookieVal}; ${cookieOpts}`
}).end(JSON.stringify({ success: true, username: target[0].user }));
} catch (e) {
if (res.json) return res.json({ success: false, msg: e.message });
return res.writeHead(400, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: e.message }));
}
});
// POST /api/v2/admin/stop-impersonate — exit impersonation
router.post(/^\/api\/v2\/admin\/stop-impersonate\/?$/, async (req, res) => {
// No auth guard needed — just clear the cookie
const cookieOpts = lib.getCookieOptions('Thu, 01 Jan 1970 00:00:00 GMT');
res.writeHead(200, {
'Content-Type': 'application/json',
'Set-Cookie': `impersonate=; ${cookieOpts}`
}).end(JSON.stringify({ success: true }));
});
return router;
}