443 lines
13 KiB
JavaScript
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() {},
|
|
};
|