diff --git a/packages/cli/src/commands/database/seed/tables.ts b/packages/cli/src/commands/database/seed/tables.ts index 343e86103..2dcf0005c 100644 --- a/packages/cli/src/commands/database/seed/tables.ts +++ b/packages/cli/src/commands/database/seed/tables.ts @@ -181,7 +181,8 @@ export const seedTables = async ( ({ name }) => name === CloudScope.SendSms || name === CloudScope.SendEmail || - name === CloudScope.FetchCustomJwt + name === CloudScope.FetchCustomJwt || + name === CloudScope.ReportSubscriptionUpdates ) .map(({ id }) => id) ); diff --git a/packages/core/src/libraries/cloud-connection.ts b/packages/core/src/libraries/cloud-connection.ts index f1530e3ae..d32d8f71f 100644 --- a/packages/core/src/libraries/cloud-connection.ts +++ b/packages/core/src/libraries/cloud-connection.ts @@ -29,8 +29,13 @@ const accessTokenResponseGuard = z.object({ * The scope here can be empty and still work, because the cloud API requests made using this client do not rely on scope verification. * The `CloudScope.SendEmail` is added for now because it needs to call the cloud email service API. * The `CloudScope.FetchCustomJwt` is added for now because it needs to call the cloud custom JWT service API. + * The `CloudScope.ReportSubscriptionUpdates` is added since we need to report subscription updates to the cloud. */ -const scopes: string[] = [CloudScope.SendEmail, CloudScope.FetchCustomJwt]; +const scopes: string[] = [ + CloudScope.SendEmail, + CloudScope.FetchCustomJwt, + CloudScope.ReportSubscriptionUpdates, +]; const accessTokenExpirationMargin = 60; /** The library for connecting to Logto Cloud service. */ diff --git a/packages/schemas/alterations/next-1722914357-add-report-sub-updates-cloud-scope.ts b/packages/schemas/alterations/next-1722914357-add-report-sub-updates-cloud-scope.ts new file mode 100644 index 000000000..06a7caf81 --- /dev/null +++ b/packages/schemas/alterations/next-1722914357-add-report-sub-updates-cloud-scope.ts @@ -0,0 +1,92 @@ +import { generateStandardId } from '@logto/shared/universal'; +import { sql } from '@silverhand/slonik'; + +import type { AlterationScript } from '../lib/types/alteration.js'; + +type Resource = { + tenantId: string; + id: string; + name: string; + indicator: string; + isDefault: boolean; +}; + +type Scope = { + tenantId: string; + id: string; + resourceId: string; + name: string; + description: string; +}; + +type Role = { + tenantId: string; + id: string; + name: string; + description: string; +}; + +const cloudApiIndicator = 'https://cloud.logto.io/api'; + +const cloudConnectionAppRoleName = 'tenantApplication'; + +const adminTenantId = 'admin'; + +const reportSubscriptionUpdatesScopeName = 'report:subscription:updates'; +const reportSubscriptionUpdatesScopeDescription = + 'Allow reporting changes on Stripe subscription to Logto Cloud.'; + +const alteration: AlterationScript = { + up: async (pool) => { + // Get the Cloud API resource + const cloudApiResource = await pool.one(sql` + select * from resources + where tenant_id = ${adminTenantId} + and indicator = ${cloudApiIndicator} + `); + + // Get cloud connection application role + const tenantApplicationRole = await pool.one(sql` + select * from roles + where tenant_id = ${adminTenantId} + and name = ${cloudConnectionAppRoleName} and type = 'MachineToMachine' + `); + + // Create the `report:subscription:updates` scope + const reportSubscriptionUpdatesCloudScope = await pool.one(sql` + insert into scopes (id, tenant_id, resource_id, name, description) + values (${generateStandardId()}, ${adminTenantId}, ${ + cloudApiResource.id + }, ${reportSubscriptionUpdatesScopeName}, ${reportSubscriptionUpdatesScopeDescription}) + returning *; + `); + + // Assign the `report:subscription:updates` scope to cloud connection application role + await pool.query(sql` + insert into roles_scopes (id, tenant_id, role_id, scope_id) + values (${generateStandardId()}, ${adminTenantId}, ${tenantApplicationRole.id}, ${ + reportSubscriptionUpdatesCloudScope.id + }); + `); + }, + down: async (pool) => { + // Get the Cloud API resource + const cloudApiResource = await pool.one(sql` + select * from resources + where tenant_id = ${adminTenantId} + and indicator = ${cloudApiIndicator} + `); + + // Remove the `report:subscription:updates` scope + await pool.query(sql` + delete from scopes + where + tenant_id = ${adminTenantId} and + name = ${reportSubscriptionUpdatesScopeName} and + description = ${reportSubscriptionUpdatesScopeDescription} and + resource_id = ${cloudApiResource.id} + `); + }, +}; + +export default alteration; diff --git a/packages/schemas/src/seeds/cloud-api.ts b/packages/schemas/src/seeds/cloud-api.ts index f98474c54..64afa8357 100644 --- a/packages/schemas/src/seeds/cloud-api.ts +++ b/packages/schemas/src/seeds/cloud-api.ts @@ -22,6 +22,10 @@ export enum CloudScope { * scripts and fetch the parsed token payload. */ FetchCustomJwt = 'fetch:custom:jwt', + /** + * The entity can report changes on Stripe subscription to Logto Cloud. + */ + ReportSubscriptionUpdates = 'report:subscription:updates', /** The user can see and manage affiliates, including create, update, and delete. */ ManageAffiliate = 'manage:affiliate', /** The user can create new affiliates and logs. */ @@ -70,6 +74,10 @@ export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]> CloudScope.FetchCustomJwt, 'Allow accessing external resource to execute JWT payload customizer script and fetch the parsed token payload.' ), + buildScope( + CloudScope.ReportSubscriptionUpdates, + 'Allow reporting changes on Stripe subscription to Logto Cloud.' + ), buildScope(CloudScope.CreateAffiliate, 'Allow creating new affiliates and logs.'), buildScope( CloudScope.ManageAffiliate,