mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(cli,cloud,schemas): seed m2m app for each tenant (#3364)
This commit is contained in:
parent
922e3a3a38
commit
1f618a3a10
11 changed files with 322 additions and 2 deletions
|
@ -176,6 +176,7 @@ const queryDatabaseData = async (database) => {
|
||||||
'id',
|
'id',
|
||||||
'resource_id',
|
'resource_id',
|
||||||
'role_id',
|
'role_id',
|
||||||
|
'application_id',
|
||||||
'scope_id',
|
'scope_id',
|
||||||
'created_at',
|
'created_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
|
|
|
@ -12,6 +12,10 @@ import {
|
||||||
createAdminTenantSignInExperience,
|
createAdminTenantSignInExperience,
|
||||||
createDefaultAdminConsoleApplication,
|
createDefaultAdminConsoleApplication,
|
||||||
createCloudApi,
|
createCloudApi,
|
||||||
|
createTenantApplicationRole,
|
||||||
|
createTenantMachineToMachineApplication,
|
||||||
|
createAdminTenantApplicationRole,
|
||||||
|
CloudScope,
|
||||||
} from '@logto/schemas';
|
} from '@logto/schemas';
|
||||||
import { Hooks, Tenants } from '@logto/schemas/models';
|
import { Hooks, Tenants } from '@logto/schemas/models';
|
||||||
import type { DatabaseTransactionConnection } from 'slonik';
|
import type { DatabaseTransactionConnection } from 'slonik';
|
||||||
|
@ -128,7 +132,27 @@ export const seedTables = async (
|
||||||
await seedAdminData(connection, createMeApiInAdminTenant());
|
await seedAdminData(connection, createMeApiInAdminTenant());
|
||||||
|
|
||||||
const [cloudData, ...cloudAdditionalScopes] = createCloudApi();
|
const [cloudData, ...cloudAdditionalScopes] = createCloudApi();
|
||||||
|
const applicationRole = createTenantApplicationRole();
|
||||||
await seedAdminData(connection, cloudData, ...cloudAdditionalScopes);
|
await seedAdminData(connection, cloudData, ...cloudAdditionalScopes);
|
||||||
|
await connection.query(insertInto(applicationRole, 'roles'));
|
||||||
|
await assignScopesToRole(
|
||||||
|
connection,
|
||||||
|
adminTenantId,
|
||||||
|
applicationRole.id,
|
||||||
|
...cloudAdditionalScopes
|
||||||
|
.filter(({ name }) => name === CloudScope.SendSms || name === CloudScope.SendEmail)
|
||||||
|
.map(({ id }) => id)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add M2M app for default tenant
|
||||||
|
const defaultTenantApplication = createTenantMachineToMachineApplication(defaultTenantId);
|
||||||
|
await connection.query(insertInto(defaultTenantApplication, 'applications'));
|
||||||
|
await connection.query(
|
||||||
|
insertInto(
|
||||||
|
createAdminTenantApplicationRole(defaultTenantApplication.id, applicationRole.id),
|
||||||
|
'applications_roles'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// Assign all cloud API scopes to role `admin:admin`
|
// Assign all cloud API scopes to role `admin:admin`
|
||||||
await assignScopesToRole(
|
await assignScopesToRole(
|
||||||
|
|
|
@ -5,6 +5,9 @@ import {
|
||||||
import { generateStandardId } from '@logto/core-kit';
|
import { generateStandardId } from '@logto/core-kit';
|
||||||
import type { LogtoOidcConfigType, TenantInfo, TenantModel } from '@logto/schemas';
|
import type { LogtoOidcConfigType, TenantInfo, TenantModel } from '@logto/schemas';
|
||||||
import {
|
import {
|
||||||
|
createAdminTenantApplicationRole,
|
||||||
|
AdminTenantRole,
|
||||||
|
createTenantMachineToMachineApplication,
|
||||||
LogtoOidcConfigKey,
|
LogtoOidcConfigKey,
|
||||||
LogtoConfigs,
|
LogtoConfigs,
|
||||||
SignInExperiences,
|
SignInExperiences,
|
||||||
|
@ -18,7 +21,9 @@ import { createTenantMetadata } from '@logto/shared';
|
||||||
import type { ZodType } from 'zod';
|
import type { ZodType } from 'zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { createApplicationsQueries } from '#src/queries/application.js';
|
||||||
import type { Queries } from '#src/queries/index.js';
|
import type { Queries } from '#src/queries/index.js';
|
||||||
|
import { createRolesQuery } from '#src/queries/roles.js';
|
||||||
import { createTenantsQueries } from '#src/queries/tenants.js';
|
import { createTenantsQueries } from '#src/queries/tenants.js';
|
||||||
import { createUsersQueries } from '#src/queries/users.js';
|
import { createUsersQueries } from '#src/queries/users.js';
|
||||||
import { getDatabaseName } from '#src/queries/utils.js';
|
import { getDatabaseName } from '#src/queries/utils.js';
|
||||||
|
@ -61,6 +66,8 @@ export class TenantsLibrary {
|
||||||
const transaction = await this.queries.client.transaction();
|
const transaction = await this.queries.client.transaction();
|
||||||
const tenants = createTenantsQueries(transaction);
|
const tenants = createTenantsQueries(transaction);
|
||||||
const users = createUsersQueries(transaction);
|
const users = createUsersQueries(transaction);
|
||||||
|
const applications = createApplicationsQueries(transaction);
|
||||||
|
const roles = createRolesQuery(transaction);
|
||||||
|
|
||||||
/* --- Start --- */
|
/* --- Start --- */
|
||||||
await transaction.start();
|
await transaction.start();
|
||||||
|
@ -79,6 +86,16 @@ export class TenantsLibrary {
|
||||||
userId: forUserId,
|
userId: forUserId,
|
||||||
roleId: adminDataInAdminTenant.role.id,
|
roleId: adminDataInAdminTenant.role.id,
|
||||||
});
|
});
|
||||||
|
// Create M2M App
|
||||||
|
const m2mRoleId = await roles.findRoleIdByName(
|
||||||
|
AdminTenantRole.TenantApplication,
|
||||||
|
adminTenantId
|
||||||
|
);
|
||||||
|
const m2mApplication = createTenantMachineToMachineApplication(tenantId);
|
||||||
|
await applications.insertApplication(m2mApplication);
|
||||||
|
await applications.assignRoleToApplication(
|
||||||
|
createAdminTenantApplicationRole(m2mApplication.id, m2mRoleId)
|
||||||
|
);
|
||||||
|
|
||||||
// Create initial configs
|
// Create initial configs
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
|
17
packages/cloud/src/queries/application.ts
Normal file
17
packages/cloud/src/queries/application.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import type { CreateApplication, CreateApplicationsRole } from '@logto/schemas';
|
||||||
|
import type { PostgreSql } from '@withtyped/postgres';
|
||||||
|
import type { Queryable } from '@withtyped/server';
|
||||||
|
|
||||||
|
import { insertInto } from '#src/utils/query.js';
|
||||||
|
|
||||||
|
export type ApplicationsQuery = ReturnType<typeof createApplicationsQueries>;
|
||||||
|
|
||||||
|
export const createApplicationsQueries = (client: Queryable<PostgreSql>) => {
|
||||||
|
const insertApplication = async (data: CreateApplication) =>
|
||||||
|
client.query(insertInto(data, 'applications'));
|
||||||
|
|
||||||
|
const assignRoleToApplication = async (data: CreateApplicationsRole) =>
|
||||||
|
client.query(insertInto(data, 'applications_roles'));
|
||||||
|
|
||||||
|
return { insertApplication, assignRoleToApplication };
|
||||||
|
};
|
|
@ -3,6 +3,7 @@ import { createQueryClient } from '@withtyped/postgres';
|
||||||
import { EnvSet } from '#src/env-set/index.js';
|
import { EnvSet } from '#src/env-set/index.js';
|
||||||
import { parseDsn } from '#src/utils/postgres.js';
|
import { parseDsn } from '#src/utils/postgres.js';
|
||||||
|
|
||||||
|
import { createApplicationsQueries } from './application.js';
|
||||||
import { createTenantsQueries } from './tenants.js';
|
import { createTenantsQueries } from './tenants.js';
|
||||||
import { createUsersQueries } from './users.js';
|
import { createUsersQueries } from './users.js';
|
||||||
|
|
||||||
|
@ -12,4 +13,5 @@ export class Queries {
|
||||||
public readonly client = createQueryClient(parseDsn(EnvSet.global.dbUrl));
|
public readonly client = createQueryClient(parseDsn(EnvSet.global.dbUrl));
|
||||||
public readonly tenants = createTenantsQueries(this.client);
|
public readonly tenants = createTenantsQueries(this.client);
|
||||||
public readonly users = createUsersQueries(this.client);
|
public readonly users = createUsersQueries(this.client);
|
||||||
|
public readonly applications = createApplicationsQueries(this.client);
|
||||||
}
|
}
|
||||||
|
|
23
packages/cloud/src/queries/roles.ts
Normal file
23
packages/cloud/src/queries/roles.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import type { PostgreSql } from '@withtyped/postgres';
|
||||||
|
import { sql } from '@withtyped/postgres';
|
||||||
|
import type { Queryable } from '@withtyped/server';
|
||||||
|
|
||||||
|
export type RolesQuery = ReturnType<typeof createRolesQuery>;
|
||||||
|
|
||||||
|
export const createRolesQuery = (client: Queryable<PostgreSql>) => {
|
||||||
|
const findRoleIdByName = async (roleName: string, tenantId: string) => {
|
||||||
|
const { rows } = await client.query<{ id: string }>(sql`
|
||||||
|
select id from roles
|
||||||
|
where name=${roleName}
|
||||||
|
and tenant_id=${tenantId}
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (!rows[0]) {
|
||||||
|
throw new Error(`Role ${roleName} not found.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { findRoleIdByName };
|
||||||
|
};
|
|
@ -0,0 +1,179 @@
|
||||||
|
import { generateStandardId } from '@logto/core-kit';
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||||
|
|
||||||
|
const adminTenantId = 'admin';
|
||||||
|
const adminRoleName = 'admin:admin';
|
||||||
|
|
||||||
|
const alteration: AlterationScript = {
|
||||||
|
up: async (pool) => {
|
||||||
|
const scopeIds = {
|
||||||
|
sms: generateStandardId(),
|
||||||
|
email: generateStandardId(),
|
||||||
|
};
|
||||||
|
const { id: resourceId } = await pool.one<{ id: string }>(sql`
|
||||||
|
select id from resources
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and indicator = 'https://cloud.logto.io/api'
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Insert scopes
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into scopes (tenant_id, id, name, description, resource_id)
|
||||||
|
values (
|
||||||
|
${adminTenantId},
|
||||||
|
${scopeIds.sms},
|
||||||
|
'send:sms',
|
||||||
|
'Allow sending SMS. This scope is only available to M2M application.',
|
||||||
|
${resourceId}
|
||||||
|
), (
|
||||||
|
${adminTenantId},
|
||||||
|
${scopeIds.email},
|
||||||
|
'send:email',
|
||||||
|
'Allow sending emails. This scope is only available to M2M application.',
|
||||||
|
${resourceId}
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Insert role
|
||||||
|
const roleId = generateStandardId();
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into roles (tenant_id, id, name, description)
|
||||||
|
values (
|
||||||
|
${adminTenantId},
|
||||||
|
${roleId},
|
||||||
|
'tenantApplication',
|
||||||
|
'The role for M2M applications that represent a user tenant and send requests to Logto Cloud.'
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into roles_scopes (tenant_id, id, role_id, scope_id)
|
||||||
|
values (
|
||||||
|
${adminTenantId},
|
||||||
|
${generateStandardId()},
|
||||||
|
${roleId},
|
||||||
|
${scopeIds.sms}
|
||||||
|
), (
|
||||||
|
${adminTenantId},
|
||||||
|
${generateStandardId()},
|
||||||
|
${roleId},
|
||||||
|
${scopeIds.email}
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Insert new scopes to admin role
|
||||||
|
const { id: adminRoleId } = await pool.one<{ id: string }>(sql`
|
||||||
|
select id from roles
|
||||||
|
where name = ${adminRoleName}
|
||||||
|
and tenant_id = ${adminTenantId}
|
||||||
|
`);
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into roles_scopes (tenant_id, id, role_id, scope_id)
|
||||||
|
values (
|
||||||
|
${adminTenantId},
|
||||||
|
${generateStandardId()},
|
||||||
|
${adminRoleId},
|
||||||
|
${scopeIds.sms}
|
||||||
|
), (
|
||||||
|
${adminTenantId},
|
||||||
|
${generateStandardId()},
|
||||||
|
${adminRoleId},
|
||||||
|
${scopeIds.email}
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Insert m2m applications for each tenant (except admin tenant)
|
||||||
|
const { rows } = await pool.query<{ id: string }>(sql`
|
||||||
|
select id from tenants
|
||||||
|
`);
|
||||||
|
await Promise.all(
|
||||||
|
rows.map(async (row) => {
|
||||||
|
const tenantId = row.id;
|
||||||
|
|
||||||
|
if (tenantId === adminTenantId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const applicationId = generateStandardId();
|
||||||
|
const description = `Machine to machine application for tenant ${tenantId}`;
|
||||||
|
const oidcClientMetadata = { redirectUris: [], postLogoutRedirectUris: [] };
|
||||||
|
const customClientMetadata = { tenantId };
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into applications (tenant_id, id, name, description, secret, type, oidc_client_metadata, custom_client_metadata)
|
||||||
|
values (
|
||||||
|
'admin',
|
||||||
|
${applicationId},
|
||||||
|
'Cloud Service',
|
||||||
|
${description},
|
||||||
|
${generateStandardId()},
|
||||||
|
'MachineToMachine',
|
||||||
|
${JSON.stringify(oidcClientMetadata)},
|
||||||
|
${JSON.stringify(customClientMetadata)}
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
insert into applications_roles (tenant_id, id, role_id, application_id)
|
||||||
|
values (
|
||||||
|
'admin',
|
||||||
|
${generateStandardId()},
|
||||||
|
${roleId},
|
||||||
|
${applicationId}
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
const role = await pool.one<{ id: string }>(sql`
|
||||||
|
select id from roles
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and name='tenantApplication'
|
||||||
|
`);
|
||||||
|
const { rows: applications } = await pool.query<{ id: string }>(sql`
|
||||||
|
select application_id as id from applications_roles
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and role_id = ${role.id}
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(sql`
|
||||||
|
delete from applications_roles
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and role_id = ${role.id};
|
||||||
|
`);
|
||||||
|
await pool.query(sql`
|
||||||
|
delete from roles_scopes
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and role_id = ${role.id};
|
||||||
|
`);
|
||||||
|
await pool.query(sql`
|
||||||
|
delete from roles
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and id = ${role.id};
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (applications.length > 0) {
|
||||||
|
await pool.query(sql`
|
||||||
|
delete from applications
|
||||||
|
where tenant_id = ${adminTenantId}
|
||||||
|
and id in (${sql.join(
|
||||||
|
applications.map(({ id }) => id),
|
||||||
|
sql`, `
|
||||||
|
)});
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
await pool.query(sql`
|
||||||
|
delete from scopes
|
||||||
|
using resources
|
||||||
|
where resources.id = scopes.resource_id
|
||||||
|
and scopes.tenant_id = ${adminTenantId}
|
||||||
|
and resources.indicator = 'https://cloud.logto.io/api'
|
||||||
|
and (scopes.name='send:sms' or scopes.name='send:email');
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
|
@ -67,12 +67,14 @@ export enum CustomClientMetadataKey {
|
||||||
CorsAllowedOrigins = 'corsAllowedOrigins',
|
CorsAllowedOrigins = 'corsAllowedOrigins',
|
||||||
IdTokenTtl = 'idTokenTtl',
|
IdTokenTtl = 'idTokenTtl',
|
||||||
RefreshTokenTtl = 'refreshTokenTtl',
|
RefreshTokenTtl = 'refreshTokenTtl',
|
||||||
|
TenantId = 'tenantId',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const customClientMetadataGuard = z.object({
|
export const customClientMetadataGuard = z.object({
|
||||||
[CustomClientMetadataKey.CorsAllowedOrigins]: z.string().url().array().optional(),
|
[CustomClientMetadataKey.CorsAllowedOrigins]: z.string().url().array().optional(),
|
||||||
[CustomClientMetadataKey.IdTokenTtl]: z.number().optional(),
|
[CustomClientMetadataKey.IdTokenTtl]: z.number().optional(),
|
||||||
[CustomClientMetadataKey.RefreshTokenTtl]: z.number().optional(),
|
[CustomClientMetadataKey.RefreshTokenTtl]: z.number().optional(),
|
||||||
|
[CustomClientMetadataKey.TenantId]: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CustomClientMetadata = z.infer<typeof customClientMetadataGuard>;
|
export type CustomClientMetadata = z.infer<typeof customClientMetadataGuard>;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { generateStandardId } from '@logto/core-kit';
|
import { generateStandardId } from '@logto/core-kit';
|
||||||
|
|
||||||
import type { Application, CreateApplication } from '../db-entries/index.js';
|
import type {
|
||||||
|
Application,
|
||||||
|
CreateApplication,
|
||||||
|
CreateApplicationsRole,
|
||||||
|
} from '../db-entries/index.js';
|
||||||
import { ApplicationType } from '../db-entries/index.js';
|
import { ApplicationType } from '../db-entries/index.js';
|
||||||
import { adminTenantId } from './tenant.js';
|
import { adminTenantId } from './tenant.js';
|
||||||
|
|
||||||
|
@ -35,3 +39,34 @@ export const createDefaultAdminConsoleApplication = (): Readonly<CreateApplicati
|
||||||
type: ApplicationType.SPA,
|
type: ApplicationType.SPA,
|
||||||
oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] },
|
oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const createTenantMachineToMachineApplication = (
|
||||||
|
tenantId: string
|
||||||
|
): Readonly<CreateApplication> =>
|
||||||
|
Object.freeze({
|
||||||
|
tenantId: adminTenantId,
|
||||||
|
id: generateStandardId(),
|
||||||
|
name: 'Cloud Service',
|
||||||
|
description: `Machine to machine application for tenant ${tenantId}`,
|
||||||
|
secret: generateStandardId(),
|
||||||
|
type: ApplicationType.MachineToMachine,
|
||||||
|
oidcClientMetadata: {
|
||||||
|
redirectUris: [],
|
||||||
|
postLogoutRedirectUris: [],
|
||||||
|
},
|
||||||
|
customClientMetadata: {
|
||||||
|
tenantId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Create role for "tenant application (M2M)" in admin tenant */
|
||||||
|
export const createAdminTenantApplicationRole = (
|
||||||
|
applicationId: string,
|
||||||
|
roleId: string
|
||||||
|
): Readonly<CreateApplicationsRole> =>
|
||||||
|
Object.freeze({
|
||||||
|
id: generateStandardId(),
|
||||||
|
tenantId: adminTenantId,
|
||||||
|
applicationId,
|
||||||
|
roleId,
|
||||||
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { generateStandardId } from '@logto/core-kit';
|
import { generateStandardId } from '@logto/core-kit';
|
||||||
|
|
||||||
import type { CreateScope } from '../index.js';
|
import type { CreateScope, Role } from '../index.js';
|
||||||
import { AdminTenantRole } from '../types/index.js';
|
import { AdminTenantRole } from '../types/index.js';
|
||||||
import type { UpdateAdminData } from './management-api.js';
|
import type { UpdateAdminData } from './management-api.js';
|
||||||
import { adminTenantId } from './tenant.js';
|
import { adminTenantId } from './tenant.js';
|
||||||
|
@ -11,6 +11,8 @@ export const cloudApiIndicator = 'https://cloud.logto.io/api';
|
||||||
export enum CloudScope {
|
export enum CloudScope {
|
||||||
CreateTenant = 'create:tenant',
|
CreateTenant = 'create:tenant',
|
||||||
ManageTenant = 'manage:tenant',
|
ManageTenant = 'manage:tenant',
|
||||||
|
SendSms = 'send:sms',
|
||||||
|
SendEmail = 'send:email',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]> => {
|
export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]> => {
|
||||||
|
@ -41,5 +43,21 @@ export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]>
|
||||||
CloudScope.ManageTenant,
|
CloudScope.ManageTenant,
|
||||||
'Allow managing existing tenants, including create without limitation, update, and delete.'
|
'Allow managing existing tenants, including create without limitation, update, and delete.'
|
||||||
),
|
),
|
||||||
|
buildScope(
|
||||||
|
CloudScope.SendEmail,
|
||||||
|
'Allow sending emails. This scope is only available to M2M application.'
|
||||||
|
),
|
||||||
|
buildScope(
|
||||||
|
CloudScope.SendSms,
|
||||||
|
'Allow sending SMS. This scope is only available to M2M application.'
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createTenantApplicationRole = (): Readonly<Role> => ({
|
||||||
|
tenantId: adminTenantId,
|
||||||
|
id: generateStandardId(),
|
||||||
|
name: AdminTenantRole.TenantApplication,
|
||||||
|
description:
|
||||||
|
'The role for M2M applications that represent a user tenant and send requests to Logto Cloud.',
|
||||||
|
});
|
||||||
|
|
|
@ -36,6 +36,8 @@ export enum AdminTenantRole {
|
||||||
Admin = 'admin',
|
Admin = 'admin',
|
||||||
/** Common user role in admin tenant. */
|
/** Common user role in admin tenant. */
|
||||||
User = 'user',
|
User = 'user',
|
||||||
|
/** The role for machine to machine applications that represent a user tenant and send requests to Logto Cloud. */
|
||||||
|
TenantApplication = 'tenantApplication',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PredefinedScope {
|
export enum PredefinedScope {
|
||||||
|
|
Loading…
Reference in a new issue