39538-vm/backend/src/db/seeders/20260410120000-role-model-alignment.js
2026-04-10 01:31:18 +00:00

443 lines
13 KiB
JavaScript

'use strict';
const bcrypt = require('bcrypt');
const config = require('../../config');
const CRUD_OPERATIONS = ['CREATE', 'READ', 'UPDATE', 'DELETE'];
const ENTITY_NAMES = [
'users',
'roles',
'permissions',
'organizations',
'user_invitations',
'connected_systems',
'control_frameworks',
'control_requirements',
'identity_workflows',
'evidence_models',
'artifacts',
'sampled_subjects',
'proof_packets',
'remediation_items',
'access_reviews',
'access_review_items',
'exceptions',
'audit_logs',
'notifications',
'organization_settings',
'artifact_type_catalog',
'workflow_templates',
];
const ROLE_RENAMES = {
'Super Administrator': 'super_admin',
Administrator: 'org_admin',
org_governance_lead: 'compliance_manager',
platform_ops_manager: 'security_manager',
evidence_operations_lead: 'iam_operator',
audit_viewer: 'auditor',
platform_owner: 'read_only_client',
};
const SAMPLE_USER_EMAILS = {
org_admin: 'admin@flatlogic.com',
compliance_manager: 'john@doe.com',
read_only_client: 'client@hello.com',
security_manager: 'security.manager@flatlogic.com',
iam_operator: 'iam.operator@flatlogic.com',
auditor: 'auditor@flatlogic.com',
};
function getCrudPermissions(entityNames) {
return entityNames.flatMap((entityName) =>
CRUD_OPERATIONS.map((operation) => `${operation}_${entityName.toUpperCase()}`),
);
}
function getReadPermissions(entityNames) {
return entityNames.map((entityName) => `READ_${entityName.toUpperCase()}`);
}
module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
const [roleRows] = await queryInterface.sequelize.query(
`SELECT id, name FROM "roles" WHERE "deletedAt" IS NULL`,
{ transaction },
);
const roleIdsByName = roleRows.reduce((accumulator, role) => {
accumulator[role.name] = role.id;
return accumulator;
}, {});
for (const [legacyName, targetName] of Object.entries(ROLE_RENAMES)) {
const roleId = roleIdsByName[legacyName] || roleIdsByName[targetName];
if (!roleId) {
throw new Error(`Missing role row for '${legacyName}' -> '${targetName}' alignment.`);
}
await queryInterface.bulkUpdate(
'roles',
{
name: targetName,
globalAccess: targetName === 'super_admin',
updatedAt: new Date(),
},
{ id: roleId },
{ transaction },
);
roleIdsByName[targetName] = roleId;
}
const managedRoleNames = [
'super_admin',
'org_admin',
'compliance_manager',
'security_manager',
'iam_operator',
'auditor',
'read_only_client',
];
const managedRoleIds = managedRoleNames.map((roleName) => roleIdsByName[roleName]).filter(Boolean);
const [permissionRows] = await queryInterface.sequelize.query(
`SELECT id, name FROM "permissions" WHERE "deletedAt" IS NULL`,
{ transaction },
);
const permissionIdsByName = permissionRows.reduce((accumulator, permission) => {
accumulator[permission.name] = permission.id;
return accumulator;
}, {});
const permissionMatrix = {
super_admin: [
...getCrudPermissions(ENTITY_NAMES),
'READ_API_DOCS',
'CREATE_SEARCH',
],
org_admin: [
'READ_USERS',
'CREATE_USERS',
'UPDATE_USERS',
'DELETE_USERS',
'READ_ROLES',
'READ_PERMISSIONS',
'READ_ORGANIZATIONS',
'UPDATE_ORGANIZATIONS',
'READ_USER_INVITATIONS',
'CREATE_USER_INVITATIONS',
'UPDATE_USER_INVITATIONS',
'DELETE_USER_INVITATIONS',
...getCrudPermissions([
'connected_systems',
'control_frameworks',
'control_requirements',
'identity_workflows',
'evidence_models',
'artifacts',
'sampled_subjects',
'proof_packets',
'remediation_items',
'access_reviews',
'access_review_items',
'exceptions',
'notifications',
'organization_settings',
'artifact_type_catalog',
'workflow_templates',
]),
'READ_AUDIT_LOGS',
'READ_API_DOCS',
'CREATE_SEARCH',
],
compliance_manager: [
...getReadPermissions([
'users',
'roles',
'organizations',
'user_invitations',
'connected_systems',
'sampled_subjects',
'exceptions',
'audit_logs',
'notifications',
'organization_settings',
'artifact_type_catalog',
'workflow_templates',
]),
...getCrudPermissions([
'control_frameworks',
'control_requirements',
'identity_workflows',
'evidence_models',
'proof_packets',
'remediation_items',
'access_reviews',
'access_review_items',
]),
'READ_ARTIFACTS',
'UPDATE_ARTIFACTS',
'CREATE_SEARCH',
],
security_manager: [
...getReadPermissions([
'users',
'roles',
'organizations',
'control_frameworks',
'control_requirements',
'evidence_models',
'proof_packets',
'sampled_subjects',
'notifications',
'organization_settings',
'artifact_type_catalog',
'workflow_templates',
]),
...getCrudPermissions([
'connected_systems',
'remediation_items',
'exceptions',
]),
'READ_IDENTITY_WORKFLOWS',
'UPDATE_IDENTITY_WORKFLOWS',
'READ_ARTIFACTS',
'UPDATE_ARTIFACTS',
'READ_ACCESS_REVIEWS',
'READ_ACCESS_REVIEW_ITEMS',
'UPDATE_ACCESS_REVIEW_ITEMS',
'READ_AUDIT_LOGS',
'CREATE_SEARCH',
],
iam_operator: [
...getReadPermissions([
'users',
'roles',
'organizations',
'connected_systems',
'control_frameworks',
'control_requirements',
'evidence_models',
'notifications',
'artifact_type_catalog',
'workflow_templates',
]),
'READ_IDENTITY_WORKFLOWS',
'UPDATE_IDENTITY_WORKFLOWS',
'CREATE_ARTIFACTS',
'READ_ARTIFACTS',
'UPDATE_ARTIFACTS',
'CREATE_SAMPLED_SUBJECTS',
'READ_SAMPLED_SUBJECTS',
'UPDATE_SAMPLED_SUBJECTS',
'CREATE_PROOF_PACKETS',
'READ_PROOF_PACKETS',
'UPDATE_PROOF_PACKETS',
'READ_ACCESS_REVIEWS',
'READ_ACCESS_REVIEW_ITEMS',
'UPDATE_ACCESS_REVIEW_ITEMS',
'READ_EXCEPTIONS',
'UPDATE_EXCEPTIONS',
'CREATE_SEARCH',
],
auditor: [
...getReadPermissions([
'users',
'roles',
'organizations',
'control_frameworks',
'control_requirements',
'identity_workflows',
'evidence_models',
'artifacts',
'proof_packets',
'remediation_items',
'access_reviews',
'access_review_items',
'exceptions',
'audit_logs',
'artifact_type_catalog',
'workflow_templates',
]),
'CREATE_SEARCH',
],
read_only_client: [
...getReadPermissions([
'organizations',
'organization_settings',
'proof_packets',
'remediation_items',
'access_reviews',
'access_review_items',
]),
'CREATE_SEARCH',
],
};
await queryInterface.bulkDelete(
'rolesPermissionsPermissions',
{
roles_permissionsId: {
[Sequelize.Op.in]: managedRoleIds,
},
},
{ transaction },
);
const rolePermissionRows = [];
const createdAt = new Date();
const updatedAt = new Date();
for (const [roleName, permissionNames] of Object.entries(permissionMatrix)) {
const roleId = roleIdsByName[roleName];
const uniquePermissionNames = [...new Set(permissionNames)];
for (const permissionName of uniquePermissionNames) {
const permissionId = permissionIdsByName[permissionName];
if (!permissionId) {
throw new Error(`Missing permission '${permissionName}' while aligning '${roleName}'.`);
}
rolePermissionRows.push({
createdAt,
updatedAt,
roles_permissionsId: roleId,
permissionId,
});
}
}
await queryInterface.bulkInsert('rolesPermissionsPermissions', rolePermissionRows, { transaction });
const [organizationRows] = await queryInterface.sequelize.query(
`SELECT id, name FROM "organizations" WHERE "deletedAt" IS NULL ORDER BY "createdAt" ASC`,
{ transaction },
);
const primaryOrganizationId = organizationRows[0]?.id || null;
if (!primaryOrganizationId) {
throw new Error('At least one organization is required to assign organization-scoped sample users.');
}
const adminHash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds);
const userHash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds);
const [existingUsers] = await queryInterface.sequelize.query(
`SELECT id, email FROM "users" WHERE "deletedAt" IS NULL`,
{ transaction },
);
const existingUsersByEmail = existingUsers.reduce((accumulator, user) => {
accumulator[user.email] = user;
return accumulator;
}, {});
const ensureUser = async ({ email, firstName, lastName, password, roleName }) => {
const roleId = roleIdsByName[roleName];
const existingUser = existingUsersByEmail[email];
if (existingUser) {
await queryInterface.bulkUpdate(
'users',
{
firstName,
lastName,
emailVerified: true,
provider: config.providers.LOCAL,
app_roleId: roleId,
organizationsId: roleName === 'super_admin' ? null : primaryOrganizationId,
updatedAt: new Date(),
},
{ id: existingUser.id },
{ transaction },
);
return;
}
await queryInterface.bulkInsert(
'users',
[
{
id: Sequelize.Utils.toDefaultValue(Sequelize.UUIDV4()),
firstName,
lastName,
email,
emailVerified: true,
provider: config.providers.LOCAL,
password,
app_roleId: roleId,
organizationsId: roleName === 'super_admin' ? null : primaryOrganizationId,
createdAt: new Date(),
updatedAt: new Date(),
},
],
{ transaction },
);
};
await ensureUser({
email: 'super_admin@flatlogic.com',
firstName: 'Super',
lastName: 'Admin',
password: adminHash,
roleName: 'super_admin',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.org_admin,
firstName: 'Organization',
lastName: 'Admin',
password: adminHash,
roleName: 'org_admin',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.compliance_manager,
firstName: 'Compliance',
lastName: 'Manager',
password: userHash,
roleName: 'compliance_manager',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.security_manager,
firstName: 'Security',
lastName: 'Manager',
password: userHash,
roleName: 'security_manager',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.iam_operator,
firstName: 'IAM',
lastName: 'Operator',
password: userHash,
roleName: 'iam_operator',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.auditor,
firstName: 'Audit',
lastName: 'Reviewer',
password: userHash,
roleName: 'auditor',
});
await ensureUser({
email: SAMPLE_USER_EMAILS.read_only_client,
firstName: 'Client',
lastName: 'Viewer',
password: userHash,
roleName: 'read_only_client',
});
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
async down() {},
};