spicedb-utils-lib
Version:
A utility wrapper for SpiceDB relationship management
534 lines (470 loc) • 14.9 kB
JavaScript
const axios = require('axios');
const fs = require('fs');
const util = require('util');
const SPICEDB_API = 'http://172.31.100.253:8443/v1'; // Cập nhật IP nếu khác
const AUTH_HEADER = {
Authorization: 'Bearer foobar',
'Content-Type': 'application/json',
};
async function cleanupAllRelationships(resourceTypes) {
for (const resourceType of resourceTypes) {
console.log(`\n🔍 Reading relationships for type: ${resourceType}`);
let totalDeleted = 0;
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: resourceType,
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: 'text', // NDJSON hoặc JSON object
}
);
let lines = [];
if (typeof response.data === 'string') {
// NDJSON: mỗi dòng là 1 JSON object
lines = response.data.trim().split('\n');
} else {
// JSON object: đưa vào mảng thủ công
lines = [JSON.stringify(response.data)];
}
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json.result?.relationship;
if (!rel) continue;
console.log(`🧩 ${rel.resource.objectType}:${rel.resource.objectId}#${rel.relation} @ ${rel.subject.object.objectType}:${rel.subject.object.objectId}`);
fs.appendFileSync(`backup_${resourceType}.json`, JSON.stringify(rel) + '\n');
await axios.post(
`${SPICEDB_API}/relationships/delete`,
{
relationship_filter: {
resource_type: rel.resource.objectType,
resource_id: rel.resource.objectId,
relation: rel.relation,
subject: {
object: {
object_type: rel.subject.object.objectType,
object_id: rel.subject.object.objectId,
},
},
},
},
{ headers: AUTH_HEADER }
);
console.log('🗑️ Deleted');
totalDeleted++;
}
} catch (err) {
console.error(`❌ Error for ${resourceType}:`, JSON.stringify(err.response?.data || err.message, null, 2));
}
console.log(`✅ Finished ${resourceType} — ${totalDeleted} relationships deleted`);
}
}
const resourceTypes = [
// 'user',
// 'usergroup',
// 'role',
'organization',
// 'case',
// 'document',
// 'perm_item'
];
/**
* Tạo relationship: role:<roleName>#permissions@perm_item:<permName>
*/
async function createPermission(roleName, permName) {
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/write`,
{
updates: [
{
operation: 'OPERATION_CREATE',
relationship: {
resource: {
object_type: 'role',
object_id: roleName,
},
relation: 'permissions',
subject: {
object: {
object_type: 'perm_item',
object_id: permName,
},
},
},
},
],
},
{ headers: AUTH_HEADER }
);
console.log(`✅ Created: role:${roleName}#permissions@perm_item:${permName}`);
} catch (err) {
console.error(`❌ Failed to create permission:`, err.response?.data || err.message);
}
}
async function listPermissions() {
const resourceType = 'role';
const relation = 'permissions';
console.log(`\n🔍 Reading permissions for type: ${resourceType}#${relation}`);
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: resourceType,
relation: relation,
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: 'text', // NDJSON
}
);
const lines = response.data.trim().split('\n');
const results = [];
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json.result?.relationship;
if (!rel) continue;
const role = rel.resource.objectId;
const perm = rel.subject?.object?.objectId;
if (role && perm) {
const lineStr = `role:${role}#permissions@perm_item:${perm}`;
results.push(lineStr);
console.log(`✅ ${lineStr}`);
}
}
console.log(`\n🔢 Total permissions found: ${results.length}`);
return results;
} catch (err) {
console.error(`❌ Error reading permissions:`, err.response?.data || err.message);
return [];
}
}
async function addUserToGroup(userId, groupId) {
try {
const payload = {
updates: [
{
operation: 'OPERATION_CREATE',
relationship: {
resource: {
object_type: 'usergroup',
object_id: groupId,
},
relation: 'members',
subject: {
object: {
object_type: 'user',
object_id: userId,
},
},
},
},
],
};
await axios.post(`${SPICEDB_API}/relationships/write`, payload, {
headers: AUTH_HEADER,
});
console.log(`✅ user:${userId} added to usergroup:${groupId}`);
} catch (err) {
console.error(`❌ Failed to add user:${userId} to group:${groupId}`, err.response?.data || err.message);
}
}
async function listUsersInGroup(groupId) {
const members = [];
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: 'usergroup',
relation: 'members',
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: 'text', // NDJSON format
}
);
const lines = response.data.trim().split('\n');
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json.result?.relationship;
if (!rel) continue;
const group = rel.resource?.objectId;
const userId = rel.subject?.object?.objectId;
if (group === groupId && rel.relation === "members" && userId) {
members.push(userId);
console.log(`👤 user:${userId} ∈ group:${group}`);
}
}
console.log(`📋 Total members in group ${groupId}: ${members.length}`);
return members;
} catch (err) {
console.error(`❌ Error listing users in group ${groupId}:`, err.message);
return [];
}
}
async function assignRoleToGroup(groupId, roleId) {
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/write`,
{
updates: [
{
operation: "OPERATION_CREATE",
relationship: {
resource: {
objectType: "usergroup",
objectId: groupId,
},
relation: "roles",
subject: {
object: {
objectType: "role",
objectId: roleId,
},
},
},
},
],
},
{ headers: AUTH_HEADER }
);
console.log(`✅ Assigned role:${roleId} to group:${groupId}`);
} catch (err) {
console.error(
`❌ Failed to assign role ${roleId} to group ${groupId}`,
err.response?.data || err.message
);
}
}
async function listRolesOfGroup(groupId) {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: "usergroup",
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: 'text',
}
);
const lines = response.data.trim().split('\n');
const roles = [];
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json.result?.relationship;
if (!rel) continue;
const resource = rel.resource;
const subject = rel.subject?.object;
// Lọc đúng group và đúng role
if (
resource?.objectType === "usergroup" &&
resource?.objectId === groupId &&
rel.relation === "roles" &&
subject?.objectType === "role"
) {
roles.push(subject.objectId);
console.log(`🔑 group:${groupId} ➜ role:${subject.objectId}`);
}
}
console.log(`📋 Total roles of group ${groupId}: ${roles.length}`);
}
async function assignRoleToUser(userId, roleId) {
try {
await axios.post(
`${SPICEDB_API}/relationships/write`,
{
updates: [
{
operation: "OPERATION_CREATE",
relationship: {
resource: {
objectType: "user",
objectId: userId,
},
relation: "roles",
subject: {
object: {
objectType: "role",
objectId: roleId,
},
},
},
},
],
},
{ headers: AUTH_HEADER }
);
console.log(`✅ Assigned role:${roleId} to user:${userId}`);
} catch (err) {
console.error(`❌ Failed to assign role to user:`, err.response?.data || err.message);
}
}
async function listRolesOfUser(userId) {
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: "user",
resource_id: userId,
relation: "roles",
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: "text", // nếu NDJSON thì trả text
}
);
let lines = [];
try {
lines = response.data.trim().split("\n");
} catch {
lines = [JSON.stringify(response.data)];
}
const roles = [];
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json?.result?.relationship ?? json?.relationship;
if (!rel) continue;
const roleId = rel.subject?.object?.objectId;
if (roleId) {
roles.push(roleId);
console.log(`🔗 user:${userId} → role:${roleId}`);
}
}
console.log(`📋 Total roles of user ${userId}: ${roles.length}`);
return roles;
} catch (err) {
console.error(`❌ Error listing roles for user ${userId}:`, err.response?.data || err.message);
return [];
}
}
async function assignGroupToCase(caseId, groupId, roleType) {
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/write`,
{
updates: [
{
operation: "OPERATION_CREATE",
relationship: {
resource: {
objectType: "case",
objectId: caseId,
},
relation: roleType, // viewers, commenters, editors, owners
subject: {
object: {
objectType: "usergroup",
objectId: groupId,
},
optionalRelation: "members",
},
},
},
],
},
{ headers: AUTH_HEADER }
);
console.log(`✅ Assigned group:${groupId}#members to case:${caseId}#${roleType}`);
} catch (err) {
console.error(`❌ Failed to assign group to case:`, err.response?.data || err.message);
}
}
async function listGroupsOfCase(caseId, roleType) {
try {
const response = await axios.post(
`${SPICEDB_API}/relationships/read`,
{
relationship_filter: {
resource_type: "case",
resource_id: caseId,
relation: roleType, // viewers, commenters, etc.
},
limit: 100,
},
{
headers: AUTH_HEADER,
responseType: "text",
}
);
let lines = [];
try {
lines = response.data.trim().split("\n");
} catch {
lines = [JSON.stringify(response.data)];
}
const groupIds = [];
for (const line of lines) {
if (!line.trim()) continue;
const json = JSON.parse(line);
const rel = json?.result?.relationship ?? json?.relationship;
if (!rel) continue;
const groupId = rel.subject?.object?.objectType === "usergroup"
? rel.subject?.object?.objectId
: null;
if (groupId) {
groupIds.push(groupId);
console.log(`🔗 case:${caseId}#${roleType} ← group:${groupId}#members`);
}
}
console.log(`📋 Total groups in case ${caseId}#${roleType}: ${groupIds.length}`);
return groupIds;
} catch (err) {
console.error(`❌ Error listing groups of case ${caseId}:`, err.response?.data || err.message);
return [];
}
}
async function checkPermission(resourceType, resourceId, permission, subjectType, subjectId) {
try {
const response = await axios.post(
`${SPICEDB_API}/permissions/check`,
{
resource: {
objectType: resourceType,
objectId: resourceId,
},
permission,
subject: {
object: {
objectType: subjectType,
objectId: subjectId,
},
},
},
{
headers: AUTH_HEADER,
}
);
const allowed = response?.data?.permissionship === "PERMISSIONSHIP_HAS_PERMISSION";
console.log(
allowed
? `✅ ${subjectType}:${subjectId} HAS ${permission} on ${resourceType}:${resourceId}`
: `❌ ${subjectType}:${subjectId} DOES NOT HAVE ${permission} on ${resourceType}:${resourceId}`
);
return allowed;
} catch (err) {
console.error(`❌ Error checking permission:`, err.response?.data || err.message);
return false;
}
}