diff --git a/.changeset/angry-tables-repeat.md b/.changeset/angry-tables-repeat.md new file mode 100644 index 000000000..38404091d --- /dev/null +++ b/.changeset/angry-tables-repeat.md @@ -0,0 +1,6 @@ +--- +"@logto/schemas": minor +"@logto/cli": minor +--- + +create a pre-configured role with Management API access when seeding the database diff --git a/packages/cli/src/commands/database/seed/roles.ts b/packages/cli/src/commands/database/seed/roles.ts new file mode 100644 index 000000000..c4253e9a0 --- /dev/null +++ b/packages/cli/src/commands/database/seed/roles.ts @@ -0,0 +1,44 @@ +import { + PredefinedScope, + getManagementApiResourceIndicator, + createPreConfiguredManagementApiAccessRole, +} from '@logto/schemas'; +import { generateStandardId } from '@logto/shared'; +import { sql, type CommonQueryMethods } from '@silverhand/slonik'; + +import { insertInto } from '../../../database.js'; + +/** + * Create a pre-configured role with Management API access + * + * Caution: + * This function should only be called after the tenant's Management API resource and the related all scope have been created. + */ +export const seedPreConfiguredManagementApiAccessRole = async ( + pool: CommonQueryMethods, + tenantId: string +) => { + const role = createPreConfiguredManagementApiAccessRole(tenantId); + + await pool.query(insertInto(role, 'roles')); + + // Assign Logto Management API permission `all` to the Logto Management API M2M role + await pool.query(sql` + insert into roles_scopes (id, role_id, scope_id, tenant_id) + values ( + ${generateStandardId()}, + ${role.id}, + ( + select scopes.id + from scopes + join resources on + scopes.tenant_id = resources.tenant_id and + scopes.resource_id = resources.id + where resources.indicator = ${getManagementApiResourceIndicator(tenantId)} + and scopes.name = ${PredefinedScope.All} + and scopes.tenant_id = ${tenantId} + ), + ${tenantId} + ) + `); +}; diff --git a/packages/cli/src/commands/database/seed/tables.ts b/packages/cli/src/commands/database/seed/tables.ts index de4dc07f8..343e86103 100644 --- a/packages/cli/src/commands/database/seed/tables.ts +++ b/packages/cli/src/commands/database/seed/tables.ts @@ -40,6 +40,7 @@ import { consoleLog, getPathInModule } from '../../../utils.js'; import { appendAdminConsoleRedirectUris, seedTenantCloudServiceApplication } from './cloud.js'; import { seedOidcConfigs } from './oidc-config.js'; +import { seedPreConfiguredManagementApiAccessRole } from './roles.js'; import { seedTenantOrganizations } from './tenant-organizations.js'; import { assignScopesToRole, @@ -150,6 +151,14 @@ export const seedTables = async ( await seedOidcConfigs(connection, defaultTenantId); await seedAdminData(connection, defaultManagementApi); + /** + * Create a pre-configured role for the Logto Management API access + * in the default tenant (the default tenant is the only tenant for the OSS version, and the initial tenant for cloud). + * + * Called after the default tenant's Management API resource and the related all scope have been created. + */ + await seedPreConfiguredManagementApiAccessRole(connection, defaultTenantId); + await createTenant(connection, adminTenantId); await seedOidcConfigs(connection, adminTenantId); await seedAdminData(connection, createAdminDataInAdminTenant(defaultTenantId)); diff --git a/packages/schemas/alterations/next-1716291265-create-pre-configured-m-api-role.ts b/packages/schemas/alterations/next-1716291265-create-pre-configured-m-api-role.ts new file mode 100644 index 000000000..22c598bfb --- /dev/null +++ b/packages/schemas/alterations/next-1716291265-create-pre-configured-m-api-role.ts @@ -0,0 +1,93 @@ +import { ConsoleLog, generateStandardId } from '@logto/shared'; +import { yes } from '@silverhand/essentials'; +import { sql } from '@silverhand/slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +const isCi = yes(process.env.CI); + +const defaultTenantId = 'default'; +const defaultTenantManagementApiIndicator = `https://${defaultTenantId}.logto.app/api`; +const roleName = 'Logto Management API access'; +const roleDescription = 'This default role grants access to the Logto management API.'; +enum RoleType { + MachineToMachine = 'MachineToMachine', +} + +enum PredefinedScope { + All = 'all', +} + +const consoleLog = new ConsoleLog(); + +/** + * This script is to create a pre-configured Management API M2M role for new users. + * This script is **only for CI**, since we won't create this role for existing users, so this script is not applicable for existing db data. + */ +const alteration: AlterationScript = { + up: async (pool) => { + if (!isCi) { + consoleLog.info( + "Skipping the alteration script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to existing db data." + ); + return; + } + + /** + * Only affect the `default` tenant, since this is the only tenant in the OSS version and the initial tenant in the cloud version. + * So we only need to care about this role for the `default` tenant. + */ + + const roleId = generateStandardId(); + + await pool.query(sql` + insert into roles (id, tenant_id, name, description, type) + values ( + ${roleId}, + ${defaultTenantId}, + ${roleName}, + ${roleDescription}, + ${RoleType.MachineToMachine} + ); + `); + + // Assign Logto Management API permission `all` to the Logto Management API M2M role + await pool.query(sql` + insert into roles_scopes (id, role_id, scope_id, tenant_id) + values ( + ${generateStandardId()}, + ${roleId}, + ( + select scopes.id + from scopes + join resources on + scopes.tenant_id = resources.tenant_id and + scopes.resource_id = resources.id + where resources.indicator = ${defaultTenantManagementApiIndicator} + and scopes.name = ${PredefinedScope.All} + and scopes.tenant_id = ${defaultTenantId} + ), + ${defaultTenantId} + ) + `); + }, + down: async (pool) => { + if (!isCi) { + consoleLog.info( + "Skipping the down script `next-1716291265-create-pre-configured-m-api-role.ts` since it's should not be applied to production db." + ); + return; + } + + // Delete the created role + await pool.query(sql` + delete from roles + where tenant_id = ${defaultTenantId} + and name = ${roleName} + and description = ${roleDescription} + and type = ${RoleType.MachineToMachine} + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/seeds/management-api.ts b/packages/schemas/src/seeds/management-api.ts index 4271bea5c..b1e4d962f 100644 --- a/packages/schemas/src/seeds/management-api.ts +++ b/packages/schemas/src/seeds/management-api.ts @@ -176,3 +176,14 @@ export const createMeApiInAdminTenant = () => { }, } satisfies AdminData); }; + +/** + * Create a pre-configured M2M role for Management API access. + */ +export const createPreConfiguredManagementApiAccessRole = (tenantId: string): CreateRole => ({ + tenantId, + id: generateStandardId(), + description: 'This default role grants access to the Logto management API.', + name: 'Logto Management API access', + type: RoleType.MachineToMachine, +});