sc-idp/lib/groups.js
2026-06-17 17:37:45 -05:00

83 lines
2.5 KiB
JavaScript

// 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:<name>) UNION their custom group
// memberships (group:<name>), 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
};