'use strict' // ───────────────────────────────────────────────────────────────────────────── // Préférences d'AFFICHAGE par utilisateur (filtres, tri, densité, vues…), par // ESPACE DE NOMS (namespace : 'inbox', 'planif', 'tickets', …). Stockage serveur // keyé par courriel Authentik → suit l'usager entre appareils ET lisible par un // admin (impersonation LECTURE SEULE, phase 3). Réutilise le patron notif-prefs. // // GET /prefs → { prefs: { : {…}, … }, user, impersonating } // GET /prefs?as= → (ADMIN seulement) les prefs d'un AUTRE usager (lecture seule) // PUT /prefs { namespace, value } → remplace ce namespace par `value` (état complet) // ───────────────────────────────────────────────────────────────────────────── const { json, parseBody, readJsonFile, writeJsonFile } = require('./helpers') const FILE = '/app/data/user_prefs.json' const meOf = (req) => String(req.headers['x-authentik-email'] || '').toLowerCase() // Garde admin pour l'impersonation (phase 3). x-authentik-groups est transmis par le proxy si configuré ; sinon → jamais admin // (repli SÛR : pas de groupe = pas d'impersonation, on ne voit que ses propres prefs). function isAdmin (req) { const g = String(req.headers['x-authentik-groups'] || req.headers['x-authentik-group'] || '') return /(^|[,|;])\s*(ops-?admin|admin(istrateurs?)?|superviseurs?)\s*([,|;]|$)/i.test(g) } async function handle (req, res, method, path) { if (path !== '/prefs') return json(res, 404, { error: 'not found' }) const me = meOf(req) const all = readJsonFile(FILE, {}) if (method === 'GET') { const as = String(new URL(req.url, 'http://localhost').searchParams.get('as') || '').toLowerCase() const target = (as && as !== me && isAdmin(req)) ? as : me // impersonation lecture seule réservée admin return json(res, 200, { prefs: all[target] || {}, user: target, impersonating: !!(as && target === as && as !== me) }) } if (method === 'PUT') { if (!me) return json(res, 400, { error: 'utilisateur inconnu (x-authentik-email absent)' }) const b = await parseBody(req) const ns = String((b && b.namespace) || '').trim() if (!ns) return json(res, 400, { error: 'namespace requis' }) const value = (b && b.value && typeof b.value === 'object' && !Array.isArray(b.value)) ? b.value : {} all[me] = all[me] || {} all[me][ns] = value // le client envoie l'ÉTAT COMPLET du namespace → remplacement (pas de fusion partielle) writeJsonFile(FILE, all) return json(res, 200, { ok: true, namespace: ns, value }) } return json(res, 405, { error: 'method not allowed' }) } module.exports = { handle }