// Group membership model: the single source of truth for the OIDC `groups` claim // (and, in a later phase, LDAP groups). A user's effective groups = their // Saltcorn role exposed as a group (role:) UNION their custom group // memberships (group:), so a role and a custom group with the same name // never collide. const db = require("@saltcorn/data/db"); const { TABLE_GROUPS, TABLE_GROUP_MEMBERS } = require("./constants"); const ROLE_PREFIX = "role:"; const GROUP_PREFIX = "group:"; const listGroups = async () => { return await db.select(TABLE_GROUPS, {}, { orderBy: "name" }); }; const createGroup = async (name, description) => { return await db.insert(TABLE_GROUPS, { name: name, description: description || null, created_at: new Date().toISOString() }); }; const deleteGroup = async (id) => { await db.deleteWhere(TABLE_GROUP_MEMBERS, { group_id: id }); await db.deleteWhere(TABLE_GROUPS, { id: id }); }; const membersOf = async (groupId) => { return await db.select(TABLE_GROUP_MEMBERS, { group_id: groupId }); }; // Memberships are keyed by the user's stable EMAIL (not the raw users.id), so a // restore that reassigns user ids does not orphan/misattribute memberships. // Callers pass the canonical email (resolved from the User row). const addMember = async (groupId, email) => { const existing = await db.selectMaybeOne(TABLE_GROUP_MEMBERS, { group_id: groupId, user_email: email }); if (existing) { return; } await db.insert(TABLE_GROUP_MEMBERS, { group_id: groupId, user_email: email }, { noid: true }); }; const removeMember = async (groupId, email) => { await db.deleteWhere(TABLE_GROUP_MEMBERS, { group_id: groupId, user_email: email }); }; const effectiveGroups = async (user) => { const out = []; const role = await db.selectMaybeOne("_sc_roles", { id: user.role_id }); if (role && role.role) { out.push(ROLE_PREFIX + role.role); } // user object already carries the canonical email; resolve groups by it. const members = await db.select(TABLE_GROUP_MEMBERS, { user_email: user.email }); for (const member of members) { const group = await db.selectMaybeOne(TABLE_GROUPS, { id: member.group_id }); if (group && group.name) { out.push(GROUP_PREFIX + group.name); } } return out; }; module.exports = { listGroups, createGroup, deleteGroup, membersOf, addMember, removeMember, effectiveGroups };