hdf
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user