mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
Merge pull request #3285 from logto-io/gao-allow-admin-to-create-tenant
refactor: allow admin to create tenants
This commit is contained in:
commit
ea66dcbf2f
27 changed files with 255 additions and 134 deletions
16
.changeset-staged/gold-mugs-allow.md
Normal file
16
.changeset-staged/gold-mugs-allow.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
"@logto/cli": minor
|
||||
"@logto/cloud": minor
|
||||
"@logto/console": minor
|
||||
"@logto/core": minor
|
||||
"@logto/integration-tests": minor
|
||||
"@logto/phrases": minor
|
||||
"@logto/phrases-ui": minor
|
||||
"@logto/schemas": minor
|
||||
"@logto/shared": minor
|
||||
"@logto/connector-kit": minor
|
||||
"@logto/core-kit": minor
|
||||
"@logto/ui": minor
|
||||
---
|
||||
|
||||
Allow admin tenant admin to create tenants without limitation
|
|
@ -46,7 +46,7 @@
|
|||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/schemas": "workspace:*",
|
||||
"@logto/shared": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"chalk": "^5.0.0",
|
||||
"decamelize": "^6.0.0",
|
||||
"dotenv": "^16.0.0",
|
||||
|
|
|
@ -24,7 +24,7 @@ import { updateDatabaseTimestamp } from '../../../queries/system.js';
|
|||
import { getPathInModule, log } from '../../../utils.js';
|
||||
import { appendAdminConsoleRedirectUris } from './cloud.js';
|
||||
import { seedOidcConfigs } from './oidc-config.js';
|
||||
import { createTenant, seedAdminData } from './tenant.js';
|
||||
import { assignScopesToRole, createTenant, seedAdminData } from './tenant.js';
|
||||
|
||||
const getExplicitOrder = (query: string) => {
|
||||
const matched = /\/\*\s*init_order\s*=\s*([\d.]+)\s*\*\//.exec(query)?.[1];
|
||||
|
@ -123,9 +123,20 @@ export const seedTables = async (
|
|||
await createTenant(connection, adminTenantId);
|
||||
await seedOidcConfigs(connection, adminTenantId);
|
||||
await seedAdminData(connection, createAdminDataInAdminTenant(defaultTenantId));
|
||||
await seedAdminData(connection, createAdminDataInAdminTenant(adminTenantId));
|
||||
const adminAdminData = createAdminDataInAdminTenant(adminTenantId);
|
||||
await seedAdminData(connection, adminAdminData);
|
||||
await seedAdminData(connection, createMeApiInAdminTenant());
|
||||
await seedAdminData(connection, createCloudApi());
|
||||
|
||||
const [cloudData, ...cloudAdditionalScopes] = createCloudApi();
|
||||
await seedAdminData(connection, cloudData, ...cloudAdditionalScopes);
|
||||
|
||||
// Assign all cloud API scopes to role `admin:admin`
|
||||
await assignScopesToRole(
|
||||
connection,
|
||||
adminTenantId,
|
||||
adminAdminData.role.id,
|
||||
...cloudAdditionalScopes.map(({ id }) => id)
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
connection.query(insertInto(createDefaultAdminConsoleConfig(defaultTenantId), 'logto_configs')),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { generateStandardId } from '@logto/core-kit';
|
||||
import type { TenantModel, AdminData, UpdateAdminData } from '@logto/schemas';
|
||||
import type { TenantModel, AdminData, UpdateAdminData, CreateScope } from '@logto/schemas';
|
||||
import { CreateRolesScope } from '@logto/schemas';
|
||||
import { createTenantMetadata } from '@logto/shared';
|
||||
import { assert } from '@silverhand/essentials';
|
||||
|
@ -25,7 +25,8 @@ export const createTenant = async (pool: CommonQueryMethods, tenantId: string) =
|
|||
|
||||
export const seedAdminData = async (
|
||||
pool: CommonQueryMethods,
|
||||
data: AdminData | UpdateAdminData
|
||||
data: AdminData | UpdateAdminData,
|
||||
...additionalScopes: CreateScope[]
|
||||
) => {
|
||||
const { resource, scope, role } = data;
|
||||
|
||||
|
@ -53,6 +54,7 @@ export const seedAdminData = async (
|
|||
|
||||
await pool.query(insertInto(resource, 'resources'));
|
||||
await pool.query(insertInto(scope, 'scopes'));
|
||||
await Promise.all(additionalScopes.map(async (scope) => pool.query(insertInto(scope, 'scopes'))));
|
||||
|
||||
const roleId = await processRole();
|
||||
await pool.query(
|
||||
|
@ -67,3 +69,26 @@ export const seedAdminData = async (
|
|||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const assignScopesToRole = async (
|
||||
pool: CommonQueryMethods,
|
||||
tenantId: string,
|
||||
roleId: string,
|
||||
...scopeIds: string[]
|
||||
) => {
|
||||
await Promise.all(
|
||||
scopeIds.map(async (scopeId) =>
|
||||
pool.query(
|
||||
insertInto(
|
||||
{
|
||||
id: generateStandardId(),
|
||||
roleId,
|
||||
scopeId,
|
||||
tenantId,
|
||||
} satisfies CreateRolesScope,
|
||||
'roles_scopes'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/schemas": "workspace:*",
|
||||
"@logto/shared": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@withtyped/postgres": "^0.8.1",
|
||||
"@withtyped/server": "^0.8.0",
|
||||
"chalk": "^5.0.0",
|
||||
|
|
|
@ -12,13 +12,17 @@ export const tenants = createRouter<WithAuthContext, '/tenants'>('/tenants')
|
|||
return next({ ...context, json: await library.getAvailableTenants(context.auth.id) });
|
||||
})
|
||||
.post('/', { response: tenantInfoGuard }, async (context, next) => {
|
||||
if (!context.auth.scopes.includes(CloudScope.CreateTenant)) {
|
||||
if (
|
||||
![CloudScope.CreateTenant, CloudScope.ManageTenant].some((scope) =>
|
||||
context.auth.scopes.includes(scope)
|
||||
)
|
||||
) {
|
||||
throw new RequestError('Forbidden due to lack of permission.', 403);
|
||||
}
|
||||
|
||||
const tenants = await library.getAvailableTenants(context.auth.id);
|
||||
|
||||
if (tenants.length > 0) {
|
||||
if (!context.auth.scopes.includes(CloudScope.ManageTenant) && tenants.length > 0) {
|
||||
throw new RequestError('The user already has a tenant.', 409);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"@parcel/transformer-svg-react": "2.8.3",
|
||||
"@silverhand/eslint-config": "2.0.1",
|
||||
"@silverhand/eslint-config-react": "2.0.1",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@silverhand/ts-config": "2.0.3",
|
||||
"@silverhand/ts-config-react": "2.0.3",
|
||||
"@tsconfig/docusaurus": "^1.0.5",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { UserScope } from '@logto/core-kit';
|
||||
import { LogtoProvider } from '@logto/react';
|
||||
import { adminConsoleApplicationId, PredefinedScope } from '@logto/schemas';
|
||||
import { deduplicate } from '@silverhand/essentials';
|
||||
import { conditionalArray, deduplicate } from '@silverhand/essentials';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import 'overlayscrollbars/styles/overlayscrollbars.css';
|
||||
|
@ -28,21 +28,26 @@ void initI18n();
|
|||
const Content = () => {
|
||||
const { tenants, isSettle, currentTenantId } = useContext(TenantsContext);
|
||||
|
||||
const resources = deduplicate([
|
||||
// Explicitly add `currentTenantId` and deduplicate since the user may directly
|
||||
// access a URL with Tenant ID, adding the ID from the URL here can possibly remove one
|
||||
// additional redirect.
|
||||
...(currentTenantId && [getManagementApi(currentTenantId).indicator]),
|
||||
...(tenants ?? []).map(({ id }) => getManagementApi(id).indicator),
|
||||
...(isCloud ? [cloudApi.indicator] : []),
|
||||
meApi.indicator,
|
||||
]);
|
||||
const resources = deduplicate(
|
||||
conditionalArray(
|
||||
// Explicitly add `currentTenantId` and deduplicate since the user may directly
|
||||
// access a URL with Tenant ID, adding the ID from the URL here can possibly remove one
|
||||
// additional redirect.
|
||||
currentTenantId && getManagementApi(currentTenantId).indicator,
|
||||
...(tenants ?? []).map(({ id }) => getManagementApi(id).indicator),
|
||||
isCloud && cloudApi.indicator,
|
||||
meApi.indicator
|
||||
)
|
||||
);
|
||||
const scopes = [
|
||||
UserScope.Email,
|
||||
UserScope.Identities,
|
||||
UserScope.CustomData,
|
||||
PredefinedScope.All,
|
||||
cloudApi.scopes.CreateTenant, // It's fine to keep scope here since core will filter
|
||||
...conditionalArray(
|
||||
isCloud && cloudApi.scopes.CreateTenant,
|
||||
isCloud && cloudApi.scopes.ManageTenant
|
||||
),
|
||||
];
|
||||
|
||||
return (
|
||||
|
@ -54,26 +59,26 @@ const Content = () => {
|
|||
scopes,
|
||||
}}
|
||||
>
|
||||
{!isCloud || isSettle ? (
|
||||
<AppEndpointsProvider>
|
||||
<AppConfirmModalProvider>
|
||||
<Main />
|
||||
</AppConfirmModalProvider>
|
||||
</AppEndpointsProvider>
|
||||
) : (
|
||||
<CloudApp />
|
||||
)}
|
||||
<ErrorBoundary>
|
||||
{!isCloud || isSettle ? (
|
||||
<AppEndpointsProvider>
|
||||
<AppConfirmModalProvider>
|
||||
<Main />
|
||||
</AppConfirmModalProvider>
|
||||
</AppEndpointsProvider>
|
||||
) : (
|
||||
<CloudApp />
|
||||
)}
|
||||
</ErrorBoundary>
|
||||
</LogtoProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<TenantsProvider>
|
||||
<Content />
|
||||
</TenantsProvider>
|
||||
</ErrorBoundary>
|
||||
<TenantsProvider>
|
||||
<Content />
|
||||
</TenantsProvider>
|
||||
);
|
||||
};
|
||||
export default App;
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHref } from 'react-router-dom';
|
||||
|
||||
import AppError from '../AppError';
|
||||
import Button from '../Button';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const SessionExpired = () => {
|
||||
const { error, signIn } = useLogto();
|
||||
const href = useHref('/callback');
|
||||
type Props = {
|
||||
error: Error;
|
||||
callbackHref?: string;
|
||||
};
|
||||
|
||||
const SessionExpired = ({ callbackHref = '/callback', error }: Props) => {
|
||||
const { signIn, signOut } = useLogto();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
return (
|
||||
<AppError
|
||||
title={t('session_expired.title')}
|
||||
errorMessage={t('session_expired.subtitle')}
|
||||
callStack={error?.stack}
|
||||
callStack={error.stack}
|
||||
>
|
||||
<Button
|
||||
className={styles.retryButton}
|
||||
|
@ -23,7 +26,7 @@ const SessionExpired = () => {
|
|||
type="outline"
|
||||
title="session_expired.button"
|
||||
onClick={() => {
|
||||
void signIn(new URL(href, window.location.origin).toString());
|
||||
void signIn(new URL(callbackHref, window.location.origin).toString());
|
||||
}}
|
||||
/>
|
||||
</AppError>
|
||||
|
|
|
@ -46,7 +46,7 @@ const AppLayout = () => {
|
|||
|
||||
if (error) {
|
||||
if (error instanceof LogtoClientError) {
|
||||
return <SessionExpired />;
|
||||
return <SessionExpired error={error} callbackHref={href} />;
|
||||
}
|
||||
|
||||
if (error instanceof LogtoError && error.code === 'crypto_subtle_unavailable') {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import { HTTPError } from 'ky';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Component } from 'react';
|
||||
|
||||
import SessionExpired from '@/components/SessionExpired';
|
||||
|
||||
import AppError from '../../components/AppError';
|
||||
|
||||
type Props = {
|
||||
|
@ -9,30 +12,15 @@ type Props = {
|
|||
};
|
||||
|
||||
type State = {
|
||||
callStack?: string;
|
||||
componentStack?: string;
|
||||
errorMessage?: string;
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
};
|
||||
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
static getDerivedStateFromError(error: Error): State {
|
||||
const errorMessage = String(error);
|
||||
|
||||
const callStack = conditional(
|
||||
typeof error === 'object' &&
|
||||
typeof error.stack === 'string' &&
|
||||
error.stack.split('\n').slice(1).join('\n')
|
||||
);
|
||||
|
||||
return { callStack, errorMessage, hasError: true };
|
||||
return { error };
|
||||
}
|
||||
|
||||
public state: State = {
|
||||
callStack: undefined,
|
||||
errorMessage: undefined,
|
||||
hasError: false,
|
||||
};
|
||||
public state: State = {};
|
||||
|
||||
promiseRejectionHandler(error: unknown) {
|
||||
this.setState(
|
||||
|
@ -54,10 +42,20 @@ class ErrorBoundary extends Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { callStack, errorMessage, hasError } = this.state;
|
||||
const { error } = this.state;
|
||||
|
||||
if (hasError) {
|
||||
return <AppError errorMessage={errorMessage} callStack={callStack} />;
|
||||
if (error) {
|
||||
if (error instanceof HTTPError && error.response.status === 401) {
|
||||
return <SessionExpired error={error} />;
|
||||
}
|
||||
|
||||
const callStack = conditional(
|
||||
typeof error === 'object' &&
|
||||
typeof error.stack === 'string' &&
|
||||
error.stack.split('\n').slice(1).join('\n')
|
||||
);
|
||||
|
||||
return <AppError errorMessage={error.message} callStack={callStack} />;
|
||||
}
|
||||
|
||||
return children;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import type { RequestErrorBody } from '@logto/schemas';
|
||||
import { conditionalArray } from '@silverhand/essentials';
|
||||
import ky from 'ky';
|
||||
import { useCallback, useContext, useMemo } from 'react';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -76,15 +77,14 @@ export const useStaticApi = ({
|
|||
prefixUrl,
|
||||
timeout: requestTimeout,
|
||||
hooks: {
|
||||
beforeError: hideErrorToast
|
||||
? []
|
||||
: [
|
||||
async (error) => {
|
||||
await toastError(error.response);
|
||||
beforeError: conditionalArray(
|
||||
!hideErrorToast &&
|
||||
(async (error) => {
|
||||
await toastError(error.response);
|
||||
|
||||
return error;
|
||||
},
|
||||
],
|
||||
return error;
|
||||
})
|
||||
),
|
||||
beforeRequest: [
|
||||
async (request) => {
|
||||
if (isAuthenticated) {
|
||||
|
|
|
@ -28,7 +28,7 @@ const Welcome = () => {
|
|||
|
||||
if (error) {
|
||||
if (error instanceof LogtoClientError) {
|
||||
return <SessionExpired />;
|
||||
return <SessionExpired error={error} callbackHref={href} />;
|
||||
}
|
||||
|
||||
return <AppError errorMessage={error.message} callStack={error.stack} />;
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"@logto/phrases-ui": "workspace:*",
|
||||
"@logto/schemas": "workspace:*",
|
||||
"@logto/shared": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@withtyped/postgres": "^0.8.1",
|
||||
"@withtyped/server": "^0.8.0",
|
||||
"chalk": "^5.0.0",
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
InteractionEvent,
|
||||
adminConsoleApplicationId,
|
||||
} from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { conditional, conditionalArray } from '@silverhand/essentials';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
|
@ -16,7 +16,6 @@ import { assignInteractionResults } from '#src/libraries/session.js';
|
|||
import { encryptUserPassword } from '#src/libraries/user.js';
|
||||
import type { LogEntry } from '#src/middleware/koa-audit-log.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import { conditionalArray } from '#src/utils/array.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
import type { WithInteractionDetailsContext } from '../middleware/koa-interaction-details.js';
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import type { Falsy } from '@silverhand/essentials';
|
||||
import { notFalsy } from '@silverhand/essentials';
|
||||
|
||||
export const conditionalArray = <T>(...exp: Array<T | Falsy>): T[] =>
|
||||
exp.filter((value): value is Exclude<T, Falsy> => notFalsy(value));
|
|
@ -27,7 +27,7 @@
|
|||
"@logto/schemas": "workspace:*",
|
||||
"@peculiar/webcrypto": "^1.3.3",
|
||||
"@silverhand/eslint-config": "2.0.1",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@silverhand/ts-config": "2.0.3",
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/jest-environment-puppeteer": "^5.0.2",
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"zod": "^3.20.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import { generateStandardId } from '@logto/core-kit';
|
||||
import { sql } from 'slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const adminTenantId = 'admin';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
const scopeId = 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'
|
||||
`);
|
||||
|
||||
await pool.query(sql`
|
||||
insert into scopes (tenant_id, id, name, description, resource_id)
|
||||
values (
|
||||
${adminTenantId},
|
||||
${scopeId},
|
||||
'manage:tenant',
|
||||
'Allow managing existing tenants, including create without limitation, update, and delete.',
|
||||
${resourceId}
|
||||
);
|
||||
`);
|
||||
|
||||
const { id: roleId } = await pool.one<{ id: string }>(sql`
|
||||
select id from roles
|
||||
where tenant_id = ${adminTenantId}
|
||||
and name = 'admin:admin'
|
||||
`);
|
||||
|
||||
await pool.query(sql`
|
||||
insert into roles_scopes (tenant_id, id, role_id, scope_id)
|
||||
values (
|
||||
${adminTenantId},
|
||||
${generateStandardId()},
|
||||
${roleId},
|
||||
${scopeId}
|
||||
);
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
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='manage:tenant';
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -41,7 +41,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "2.0.1",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@silverhand/ts-config": "2.0.3",
|
||||
"@types/inquirer": "^9.0.0",
|
||||
"@types/jest": "^29.1.2",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { generateStandardId } from '@logto/core-kit';
|
||||
|
||||
import type { CreateScope } from '../index.js';
|
||||
import { UserRole } from '../types/index.js';
|
||||
import type { UpdateAdminData } from './management-api.js';
|
||||
import { adminTenantId } from './tenant.js';
|
||||
|
@ -9,28 +10,36 @@ export const cloudApiIndicator = 'https://cloud.logto.io/api';
|
|||
|
||||
export enum CloudScope {
|
||||
CreateTenant = 'create:tenant',
|
||||
ManageTenant = 'manage:tenant',
|
||||
}
|
||||
|
||||
export const createCloudApi = (): Readonly<UpdateAdminData> => {
|
||||
export const createCloudApi = (): Readonly<[UpdateAdminData, ...CreateScope[]]> => {
|
||||
const resourceId = generateStandardId();
|
||||
|
||||
return Object.freeze({
|
||||
resource: {
|
||||
tenantId: adminTenantId,
|
||||
id: resourceId,
|
||||
indicator: cloudApiIndicator,
|
||||
name: `Logto Cloud API`,
|
||||
},
|
||||
scope: {
|
||||
tenantId: adminTenantId,
|
||||
id: generateStandardId(),
|
||||
name: CloudScope.CreateTenant,
|
||||
description: 'Allow creating new tenants.',
|
||||
resourceId,
|
||||
},
|
||||
role: {
|
||||
tenantId: adminTenantId,
|
||||
name: UserRole.User,
|
||||
},
|
||||
const buildScope = (name: CloudScope, description: string) => ({
|
||||
tenantId: adminTenantId,
|
||||
id: generateStandardId(),
|
||||
name,
|
||||
description,
|
||||
resourceId,
|
||||
});
|
||||
|
||||
return Object.freeze([
|
||||
{
|
||||
resource: {
|
||||
tenantId: adminTenantId,
|
||||
id: resourceId,
|
||||
indicator: cloudApiIndicator,
|
||||
name: `Logto Cloud API`,
|
||||
},
|
||||
scope: buildScope(CloudScope.CreateTenant, 'Allow creating new tenants.'),
|
||||
role: {
|
||||
tenantId: adminTenantId,
|
||||
name: UserRole.User,
|
||||
},
|
||||
},
|
||||
buildScope(
|
||||
CloudScope.ManageTenant,
|
||||
'Allow managing existing tenants, including create without limitation, update, and delete.'
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/schemas": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"chalk": "^5.0.0",
|
||||
"find-up": "^6.3.0",
|
||||
"nanoid": "^4.0.0",
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.2.0"
|
||||
"@silverhand/essentials": "2.3.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"zod": "^3.20.2"
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"@jest/types": "^29.0.3",
|
||||
"@silverhand/eslint-config": "2.0.1",
|
||||
"@silverhand/eslint-config-react": "2.0.1",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@silverhand/ts-config": "2.0.3",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/jest": "^29.0.3",
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"@react-spring/web": "^9.6.1",
|
||||
"@silverhand/eslint-config": "2.0.1",
|
||||
"@silverhand/eslint-config-react": "2.0.1",
|
||||
"@silverhand/essentials": "2.2.0",
|
||||
"@silverhand/essentials": "2.3.0",
|
||||
"@silverhand/jest-config": "1.2.2",
|
||||
"@silverhand/ts-config": "2.0.3",
|
||||
"@silverhand/ts-config-react": "2.0.3",
|
||||
|
|
|
@ -31,7 +31,7 @@ importers:
|
|||
'@logto/schemas': workspace:*
|
||||
'@logto/shared': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/inquirer': ^9.0.0
|
||||
'@types/jest': ^29.1.2
|
||||
|
@ -68,7 +68,7 @@ importers:
|
|||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/schemas': link:../schemas
|
||||
'@logto/shared': link:../shared
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
chalk: 5.1.2
|
||||
decamelize: 6.0.0
|
||||
dotenv: 16.0.0
|
||||
|
@ -111,7 +111,7 @@ importers:
|
|||
'@logto/schemas': workspace:*
|
||||
'@logto/shared': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/http-proxy': ^1.17.9
|
||||
'@types/mime-types': ^2.1.1
|
||||
|
@ -137,7 +137,7 @@ importers:
|
|||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/schemas': link:../schemas
|
||||
'@logto/shared': link:../shared
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@withtyped/postgres': 0.8.1_@withtyped+server@0.8.0
|
||||
'@withtyped/server': 0.8.0
|
||||
chalk: 5.1.2
|
||||
|
@ -178,7 +178,7 @@ importers:
|
|||
'@parcel/transformer-svg-react': 2.8.3
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/eslint-config-react': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@silverhand/ts-config-react': 2.0.3
|
||||
'@tsconfig/docusaurus': ^1.0.5
|
||||
|
@ -252,7 +252,7 @@ importers:
|
|||
'@parcel/transformer-svg-react': 2.8.3_@parcel+core@2.8.3
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
'@silverhand/eslint-config-react': 2.0.1_kz2ighe3mj4zdkvq5whtl3dq4u
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||
'@silverhand/ts-config-react': 2.0.3_typescript@4.9.4
|
||||
'@tsconfig/docusaurus': 1.0.5
|
||||
|
@ -323,7 +323,7 @@ importers:
|
|||
'@logto/schemas': workspace:*
|
||||
'@logto/shared': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/debug': ^4.1.7
|
||||
'@types/etag': ^1.8.1
|
||||
|
@ -400,7 +400,7 @@ importers:
|
|||
'@logto/phrases-ui': link:../phrases-ui
|
||||
'@logto/schemas': link:../schemas
|
||||
'@logto/shared': link:../shared
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@withtyped/postgres': 0.8.1_@withtyped+server@0.8.0
|
||||
'@withtyped/server': 0.8.0
|
||||
chalk: 5.1.2
|
||||
|
@ -545,7 +545,7 @@ importers:
|
|||
'@logto/schemas': workspace:*
|
||||
'@peculiar/webcrypto': ^1.3.3
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/jest': ^29.1.2
|
||||
'@types/jest-environment-puppeteer': ^5.0.2
|
||||
|
@ -573,7 +573,7 @@ importers:
|
|||
'@logto/schemas': link:../schemas
|
||||
'@peculiar/webcrypto': 1.3.3
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||
'@types/jest': 29.1.2
|
||||
'@types/jest-environment-puppeteer': 5.0.2
|
||||
|
@ -596,7 +596,7 @@ importers:
|
|||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
eslint: ^8.34.0
|
||||
lint-staged: ^13.0.0
|
||||
|
@ -606,7 +606,7 @@ importers:
|
|||
dependencies:
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/language-kit': link:../toolkit/language-kit
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
zod: 3.20.2
|
||||
devDependencies:
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
|
@ -621,7 +621,7 @@ importers:
|
|||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
buffer: ^5.7.1
|
||||
eslint: ^8.34.0
|
||||
|
@ -632,7 +632,7 @@ importers:
|
|||
dependencies:
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/language-kit': link:../toolkit/language-kit
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
zod: 3.20.2
|
||||
devDependencies:
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
|
@ -651,7 +651,7 @@ importers:
|
|||
'@logto/phrases': workspace:*
|
||||
'@logto/phrases-ui': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/inquirer': ^9.0.0
|
||||
'@types/jest': ^29.1.2
|
||||
|
@ -680,7 +680,7 @@ importers:
|
|||
zod: 3.20.2
|
||||
devDependencies:
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||
'@types/inquirer': 9.0.3
|
||||
'@types/jest': 29.1.2
|
||||
|
@ -704,7 +704,7 @@ importers:
|
|||
'@logto/core-kit': workspace:*
|
||||
'@logto/schemas': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/jest': ^29.1.2
|
||||
'@types/node': ^18.11.18
|
||||
|
@ -720,7 +720,7 @@ importers:
|
|||
dependencies:
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/schemas': link:../schemas
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
chalk: 5.1.2
|
||||
find-up: 6.3.0
|
||||
nanoid: 4.0.0
|
||||
|
@ -742,7 +742,7 @@ importers:
|
|||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/node': ^18.11.18
|
||||
eslint: ^8.34.0
|
||||
|
@ -754,7 +754,7 @@ importers:
|
|||
dependencies:
|
||||
'@logto/core-kit': link:../core-kit
|
||||
'@logto/language-kit': link:../language-kit
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
optionalDependencies:
|
||||
zod: 3.20.2
|
||||
devDependencies:
|
||||
|
@ -773,7 +773,7 @@ importers:
|
|||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/eslint-config-react': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@types/color': ^3.0.3
|
||||
'@types/jest': ^29.0.3
|
||||
|
@ -800,7 +800,7 @@ importers:
|
|||
'@jest/types': 29.3.1
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
'@silverhand/eslint-config-react': 2.0.1_wfldc7mlde5bb3fdzap5arn6me
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||
'@types/color': 3.0.3
|
||||
'@types/jest': 29.1.2
|
||||
|
@ -860,7 +860,7 @@ importers:
|
|||
'@react-spring/web': ^9.6.1
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/eslint-config-react': 2.0.1
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/jest-config': 1.2.2
|
||||
'@silverhand/ts-config': 2.0.3
|
||||
'@silverhand/ts-config-react': 2.0.3
|
||||
|
@ -918,7 +918,7 @@ importers:
|
|||
'@react-spring/web': 9.6.1_biqbaboplfbrettd7655fr4n2y
|
||||
'@silverhand/eslint-config': 2.0.1_kjzxg5porcw5dx54sezsklj5cy
|
||||
'@silverhand/eslint-config-react': 2.0.1_kz2ighe3mj4zdkvq5whtl3dq4u
|
||||
'@silverhand/essentials': 2.2.0
|
||||
'@silverhand/essentials': 2.3.0
|
||||
'@silverhand/jest-config': 1.2.2_ky6c64xxalg2hsll4xx3evq2dy
|
||||
'@silverhand/ts-config': 2.0.3_typescript@4.9.4
|
||||
'@silverhand/ts-config-react': 2.0.3_typescript@4.9.4
|
||||
|
@ -3623,8 +3623,8 @@ packages:
|
|||
lodash.pick: 4.4.0
|
||||
dev: true
|
||||
|
||||
/@silverhand/essentials/2.2.0:
|
||||
resolution: {integrity: sha512-xoj/wAnPUt9ZAzt7QCHhSKZPweZnNJU7tBYrDTf54db6L+++SiXYIyckKDY+vKkGACn9kTAWPF74qSfYt1OQtA==}
|
||||
/@silverhand/essentials/2.3.0:
|
||||
resolution: {integrity: sha512-vZ8eT0ew2bTIo86vwcPSduL1o2oXxH9DPnQe8sV3K3g1/sSgCyAj9ULHObZLjg/YNGlp16Wiby1hSs8P9VtU7g==}
|
||||
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^7}
|
||||
|
||||
/@silverhand/jest-config/1.2.2_ky6c64xxalg2hsll4xx3evq2dy:
|
||||
|
|
Loading…
Reference in a new issue