// Per-user data endpoints: // GET /CollabCentral/:app/user/groups/list — this user's favorites // GET /CollabCentral/:app/user/groups/find — org-wide groups // (cached) // POST /CollabCentral/:app/user/groups/add — save a favorite // POST /CollabCentral/:app/user/groups/remove — drop a favorite // // All four require the caller to be authorized on :app. Favorites live // in the mutable `authorized` doc (config/authorized.json) so they // survive restarts and are picklable in the compose page's Favorite // Groups selector. // // registerUserRoutes(app, { isAuthorized, authorized, helpers, // getGroupsFromCache, saveConfig, logger }) export function registerUserRoutes(app, deps) { var isAuthorized = deps.isAuthorized; var authorized = deps.authorized; var helpers = deps.helpers; var getGroupsFromCache = deps.getGroupsFromCache; var saveConfig = deps.saveConfig; var logger = deps.logger || function () {}; app.get('/CollabCentral/:app/user/:scope/:action', function (req, res) { if (!isAuthorized(req.params.app, req.cookies.id)) { return res.status(401).send('Unauthorized.'); } if (req.params.scope !== 'groups') { return res.status(404).send('Not found.'); } if (req.params.action === 'list') { logger('apiEndpoint(' + req.params.app + ')', 'GET /user/groups/list'); var listEntry = helpers.getAuthorizedEntry(authorized, req.params.app, req.cookies.id); return res.status(200).send((listEntry && listEntry.groups) || []); } if (req.params.action === 'find') { logger('apiEndpoint(' + req.params.app + ')', 'GET /user/groups/find'); return getGroupsFromCache() .then(function (groups) { res.status(200).send(groups); }) .catch(function (err) { logger('apiEndpoint(' + req.params.app + ')', 'groups/find failed: ' + (err && err.message || err)); res.status(502).send('Failed to load groups from Webex.'); }); } return res.status(404).send('Not found.'); }); // Add / remove a favorite group for the calling user. Validation: // - the caller is authorized for :app // - :id looks like a Webex SCIM group id we know about (present in // the cached org-wide list) — stops bad actors from stuffing // arbitrary strings into the store. // Because index.js is single-process and node is single-threaded, // concurrent requests here can't corrupt authorized.json — every // read/mutate/write executes atomically within one handler. app.post('/CollabCentral/:app/user/groups/add', async function (req, res) { if (!isAuthorized(req.params.app, req.cookies.id)) return res.status(401).send('Unauthorized.'); var appName = req.params.app; var personId = req.cookies.id; var groupId = req.body && req.body.id; if (!groupId) return res.status(400).send('Missing group id.'); var group; try { var allGroups = await getGroupsFromCache(); group = allGroups.find(function (g) { return g.id === groupId; }); } catch (err) { logger('apiEndpoint(' + appName + ')', 'groups/add cache lookup failed: ' + (err && err.message || err)); return res.status(502).send('Group list unavailable.'); } if (!group) return res.status(404).send('Unknown group id.'); var entry = helpers.getAuthorizedEntry(authorized, appName, personId); // isAuthorized guarantees the entry exists but be defensive; a // race between an admin removal and this request could otherwise // crash. if (!entry) return res.status(401).send('Unauthorized.'); var favorites = Array.isArray(entry.groups) ? entry.groups : []; if (favorites.find(function (g) { return g.id === groupId; })) { return res.status(200).send(favorites); } favorites.push({ name: group.displayName, id: group.id }); entry.groups = favorites; try { saveConfig(authorized, './config/authorized.json'); } catch (err) { logger('apiEndpoint(' + appName + ')', 'groups/add save failed: ' + (err && err.message || err)); return res.status(500).send('Failed to save favorite.'); } logger('apiEndpoint(' + appName + ')', 'groups/add ' + personId.slice(-8) + ' + "' + group.displayName + '"'); res.status(200).send(favorites); }); app.post('/CollabCentral/:app/user/groups/remove', function (req, res) { if (!isAuthorized(req.params.app, req.cookies.id)) return res.status(401).send('Unauthorized.'); var appName = req.params.app; var personId = req.cookies.id; var groupId = req.body && req.body.id; if (!groupId) return res.status(400).send('Missing group id.'); var entry = helpers.getAuthorizedEntry(authorized, appName, personId); if (!entry) return res.status(401).send('Unauthorized.'); var favorites = Array.isArray(entry.groups) ? entry.groups : []; var before = favorites.length; var removed = favorites.find(function (g) { return g.id === groupId; }); favorites = favorites.filter(function (g) { return g.id !== groupId; }); if (favorites.length === before) return res.status(200).send(favorites); entry.groups = favorites; try { saveConfig(authorized, './config/authorized.json'); } catch (err) { logger('apiEndpoint(' + appName + ')', 'groups/remove save failed: ' + (err && err.message || err)); return res.status(500).send('Failed to remove favorite.'); } logger('apiEndpoint(' + appName + ')', 'groups/remove ' + personId.slice(-8) + ' - "' + (removed && removed.name || groupId) + '"'); res.status(200).send(favorites); }); }