mirror of
https://github.com/logto-io/logto.git
synced 2025-01-27 21:39:16 -05:00
fix(core): not allow to modify management api resource (#5626)
This commit is contained in:
parent
be57ec2e19
commit
5b03030de2
22 changed files with 161 additions and 21 deletions
9
.changeset/four-goats-rush.md
Normal file
9
.changeset/four-goats-rush.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
"@logto/integration-tests": patch
|
||||||
|
"@logto/phrases": patch
|
||||||
|
"@logto/core": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Not allow to modify management API resource through API.
|
||||||
|
|
||||||
|
Previously, management API resource and its scopes are readonly in Console. But it was possible to modify through the API. This is not allowed anymore.
|
|
@ -1,4 +1,8 @@
|
||||||
import { type Resource, type CreateResource } from '@logto/schemas';
|
import {
|
||||||
|
type Resource,
|
||||||
|
type CreateResource,
|
||||||
|
getManagementApiResourceIndicator,
|
||||||
|
} from '@logto/schemas';
|
||||||
import { pickDefault } from '@logto/shared/esm';
|
import { pickDefault } from '@logto/shared/esm';
|
||||||
import { type Nullable } from '@silverhand/essentials';
|
import { type Nullable } from '@silverhand/essentials';
|
||||||
|
|
||||||
|
@ -91,6 +95,19 @@ describe('resource scope routes', () => {
|
||||||
expect(response.status).toEqual(400);
|
expect(response.status).toEqual(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('POST /resources/:id/scopes should throw when the resource is management API', async () => {
|
||||||
|
const { findResourceById } = resources;
|
||||||
|
findResourceById.mockResolvedValueOnce({
|
||||||
|
...mockResource,
|
||||||
|
indicator: getManagementApiResourceIndicator('mock'),
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
resourceScopeRequest
|
||||||
|
.post('/resources/foo/scopes')
|
||||||
|
.send({ name: 'name', description: 'description' })
|
||||||
|
).resolves.toHaveProperty('status', 400);
|
||||||
|
});
|
||||||
|
|
||||||
it('PATCH /resources/:id/scopes/:scopeId', async () => {
|
it('PATCH /resources/:id/scopes/:scopeId', async () => {
|
||||||
const name = 'write:users';
|
const name = 'write:users';
|
||||||
const description = 'description';
|
const description = 'description';
|
||||||
|
@ -107,10 +124,35 @@ describe('resource scope routes', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PATCH /resources/:id/scopes/:scopeId should throw when the resource is management API', async () => {
|
||||||
|
const { findResourceById } = resources;
|
||||||
|
findResourceById.mockResolvedValueOnce({
|
||||||
|
...mockResource,
|
||||||
|
indicator: getManagementApiResourceIndicator('mock'),
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
resourceScopeRequest
|
||||||
|
.patch('/resources/foo/scopes/foz')
|
||||||
|
.send({ name: 'name', description: 'description' })
|
||||||
|
).resolves.toHaveProperty('status', 400);
|
||||||
|
});
|
||||||
|
|
||||||
it('DELETE /resources/:id/scopes/:scopeId', async () => {
|
it('DELETE /resources/:id/scopes/:scopeId', async () => {
|
||||||
await expect(resourceScopeRequest.delete('/resources/foo/scopes/foz')).resolves.toHaveProperty(
|
await expect(resourceScopeRequest.delete('/resources/foo/scopes/foz')).resolves.toHaveProperty(
|
||||||
'status',
|
'status',
|
||||||
204
|
204
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('DELETE /resources/:id/scopes/:scopeId should throw when the resource is management API', async () => {
|
||||||
|
const { findResourceById } = resources;
|
||||||
|
findResourceById.mockResolvedValueOnce({
|
||||||
|
...mockResource,
|
||||||
|
indicator: getManagementApiResourceIndicator('mock'),
|
||||||
|
});
|
||||||
|
await expect(resourceScopeRequest.delete('/resources/foo/scopes/foz')).resolves.toHaveProperty(
|
||||||
|
'status',
|
||||||
|
400
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Scopes } from '@logto/schemas';
|
import { Scopes, isManagementApi } from '@logto/schemas';
|
||||||
import { generateStandardId } from '@logto/shared';
|
import { generateStandardId } from '@logto/shared';
|
||||||
import { tryThat } from '@silverhand/essentials';
|
import { tryThat } from '@silverhand/essentials';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
@ -93,15 +93,10 @@ export default function resourceScopeRoutes<T extends AuthedRouter>(
|
||||||
|
|
||||||
assertThat(!/\s/.test(body.name), 'scope.name_with_space');
|
assertThat(!/\s/.test(body.name), 'scope.name_with_space');
|
||||||
|
|
||||||
|
const { indicator } = await findResourceById(resourceId);
|
||||||
assertThat(
|
assertThat(
|
||||||
await findResourceById(resourceId),
|
!isManagementApi(indicator),
|
||||||
new RequestError({
|
new RequestError({ code: 'resource.cannot_modify_management_api' })
|
||||||
code: 'entity.not_exists_with_id',
|
|
||||||
name: 'resource',
|
|
||||||
id: resourceId,
|
|
||||||
resourceId,
|
|
||||||
status: 404,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assertThat(
|
assertThat(
|
||||||
|
@ -138,15 +133,10 @@ export default function resourceScopeRoutes<T extends AuthedRouter>(
|
||||||
body,
|
body,
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
|
|
||||||
|
const { indicator } = await findResourceById(resourceId);
|
||||||
assertThat(
|
assertThat(
|
||||||
await findResourceById(resourceId),
|
!isManagementApi(indicator),
|
||||||
new RequestError({
|
new RequestError({ code: 'resource.cannot_modify_management_api' })
|
||||||
code: 'entity.not_exists_with_id',
|
|
||||||
name: 'resource',
|
|
||||||
id: resourceId,
|
|
||||||
resourceId,
|
|
||||||
status: 404,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (body.name) {
|
if (body.name) {
|
||||||
|
@ -171,13 +161,19 @@ export default function resourceScopeRoutes<T extends AuthedRouter>(
|
||||||
'/resources/:resourceId/scopes/:scopeId',
|
'/resources/:resourceId/scopes/:scopeId',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: object({ resourceId: string().min(1), scopeId: string().min(1) }),
|
params: object({ resourceId: string().min(1), scopeId: string().min(1) }),
|
||||||
status: [204, 404],
|
status: [204, 400, 404],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
params: { scopeId },
|
params: { scopeId, resourceId },
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
|
|
||||||
|
const { indicator } = await findResourceById(resourceId);
|
||||||
|
assertThat(
|
||||||
|
!isManagementApi(indicator),
|
||||||
|
new RequestError({ code: 'resource.cannot_modify_management_api' })
|
||||||
|
);
|
||||||
|
|
||||||
await deleteScopeById(scopeId);
|
await deleteScopeById(scopeId);
|
||||||
|
|
||||||
ctx.status = 204;
|
ctx.status = 204;
|
||||||
|
|
|
@ -152,6 +152,15 @@ describe('resource routes', () => {
|
||||||
expect(response.status).toEqual(400);
|
expect(response.status).toEqual(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PATCH /resources/:id should throw when trying to modify management API', async () => {
|
||||||
|
const { findResourceById } = resources;
|
||||||
|
findResourceById.mockResolvedValueOnce({
|
||||||
|
...mockResource,
|
||||||
|
indicator: getManagementApiResourceIndicator('mock'),
|
||||||
|
});
|
||||||
|
await expect(resourceRequest.patch('/resources/foo')).resolves.toHaveProperty('status', 400);
|
||||||
|
});
|
||||||
|
|
||||||
it('DELETE /resources/:id', async () => {
|
it('DELETE /resources/:id', async () => {
|
||||||
await expect(resourceRequest.delete('/resources/foo')).resolves.toHaveProperty('status', 204);
|
await expect(resourceRequest.delete('/resources/foo')).resolves.toHaveProperty('status', 204);
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,7 +136,7 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
// Use the dedicated API `PATCH /resources/:id/is-default` to update.
|
// Use the dedicated API `PATCH /resources/:id/is-default` to update.
|
||||||
body: Resources.createGuard.omit({ id: true, indicator: true, isDefault: true }).partial(),
|
body: Resources.createGuard.omit({ id: true, indicator: true, isDefault: true }).partial(),
|
||||||
response: Resources.guard,
|
response: Resources.guard,
|
||||||
status: [200, 404],
|
status: [200, 400, 404],
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const {
|
const {
|
||||||
|
@ -144,6 +144,12 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
body,
|
body,
|
||||||
} = ctx.guard;
|
} = ctx.guard;
|
||||||
|
|
||||||
|
const { indicator } = await findResourceById(id);
|
||||||
|
assertThat(
|
||||||
|
!isManagementApi(indicator),
|
||||||
|
new RequestError({ code: 'resource.cannot_modify_management_api' })
|
||||||
|
);
|
||||||
|
|
||||||
const resource = await updateResourceById(id, body);
|
const resource = await updateResourceById(id, body);
|
||||||
ctx.body = resource;
|
ctx.body = resource;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { HTTPError } from 'ky';
|
||||||
|
|
||||||
import { createResource } from '#src/api/index.js';
|
import { createResource } from '#src/api/index.js';
|
||||||
import { createScope, deleteScope, getScopes, updateScope } from '#src/api/scope.js';
|
import { createScope, deleteScope, getScopes, updateScope } from '#src/api/scope.js';
|
||||||
|
import { expectRejects } from '#src/helpers/index.js';
|
||||||
import { generateName, generateScopeName } from '#src/utils.js';
|
import { generateName, generateScopeName } from '#src/utils.js';
|
||||||
|
|
||||||
describe('scopes', () => {
|
describe('scopes', () => {
|
||||||
|
@ -56,6 +57,13 @@ describe('scopes', () => {
|
||||||
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
|
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when creating scope under management API resource', async () => {
|
||||||
|
await expectRejects(createScope(defaultManagementApi.resource.id, 'scope'), {
|
||||||
|
code: 'resource.cannot_modify_management_api',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should update scope successfully', async () => {
|
it('should update scope successfully', async () => {
|
||||||
const resource = await createResource();
|
const resource = await createResource();
|
||||||
const scope = await createScope(resource.id);
|
const scope = await createScope(resource.id);
|
||||||
|
@ -114,6 +122,23 @@ describe('scopes', () => {
|
||||||
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
|
expect(response instanceof HTTPError && response.response.status === 404).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when updating scope under management API resource', async () => {
|
||||||
|
const scopes = await getScopes(defaultManagementApi.resource.id);
|
||||||
|
const scopeId = scopes[0]?.id;
|
||||||
|
if (!scopeId) {
|
||||||
|
throw new Error('No scope for management API resource found');
|
||||||
|
}
|
||||||
|
await expectRejects(
|
||||||
|
updateScope(defaultManagementApi.resource.id, scopeId, {
|
||||||
|
name: 'scope',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
code: 'resource.cannot_modify_management_api',
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should delete scope successfully', async () => {
|
it('should delete scope successfully', async () => {
|
||||||
const resource = await createResource();
|
const resource = await createResource();
|
||||||
const scope = await createScope(resource.id);
|
const scope = await createScope(resource.id);
|
||||||
|
@ -126,4 +151,16 @@ describe('scopes', () => {
|
||||||
|
|
||||||
expect(scopes.some(({ name }) => name === scope.name)).toBeFalsy();
|
expect(scopes.some(({ name }) => name === scope.name)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when deleting scope under management API resource', async () => {
|
||||||
|
const scopes = await getScopes(defaultManagementApi.resource.id);
|
||||||
|
const scopeId = scopes[0]?.id;
|
||||||
|
if (!scopeId) {
|
||||||
|
throw new Error('No scope for management API resource found');
|
||||||
|
}
|
||||||
|
await expectRejects(deleteScope(defaultManagementApi.resource.id, scopeId), {
|
||||||
|
code: 'resource.cannot_modify_management_api',
|
||||||
|
status: 400,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -96,6 +96,18 @@ describe('admin console api resources', () => {
|
||||||
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
|
expect(response instanceof HTTPError && response.response.status === 400).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw when updating management api resource', async () => {
|
||||||
|
await expectRejects(
|
||||||
|
updateResource(defaultManagementApi.resource.id, {
|
||||||
|
name: 'new-name',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
code: 'resource.cannot_modify_management_api',
|
||||||
|
status: 400,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not update api resource indicator', async () => {
|
it('should not update api resource indicator', async () => {
|
||||||
const resource = await createResource();
|
const resource = await createResource();
|
||||||
const newResourceName = `new_${resource.name}`;
|
const newResourceName = `new_${resource.name}`;
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'Die API-Kennung {{indicator}} wird bereits verwendet',
|
resource_identifier_in_use: 'Die API-Kennung {{indicator}} wird bereits verwendet',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const resource = {
|
const resource = {
|
||||||
resource_identifier_in_use: 'The API identifier {{indicator}} is already in use',
|
resource_identifier_in_use: 'The API identifier {{indicator}} is already in use',
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: '"El identificador de API {{indicator}} ya está en uso',
|
resource_identifier_in_use: '"El identificador de API {{indicator}} ya está en uso',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: "L'identifiant d'API {{indicator}} est déjà utilisé",
|
resource_identifier_in_use: "L'identifiant d'API {{indicator}} est déjà utilisé",
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: "L'identificatore API {{indicator}} è già in uso",
|
resource_identifier_in_use: "L'identificatore API {{indicator}} è già in uso",
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API識別子 {{indicator}} はすでに使用されています',
|
resource_identifier_in_use: 'API識別子 {{indicator}} はすでに使用されています',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API 식별자 {{indicator}}가 이미 사용 중입니다',
|
resource_identifier_in_use: 'API 식별자 {{indicator}}가 이미 사용 중입니다',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'Identyfikator API {{indicator}} jest już używany',
|
resource_identifier_in_use: 'Identyfikator API {{indicator}} jest już używany',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'O identificador de API {{indicator}} já está em uso',
|
resource_identifier_in_use: 'O identificador de API {{indicator}} já está em uso',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'O identificador da API {{indicator}} já está em uso',
|
resource_identifier_in_use: 'O identificador da API {{indicator}} já está em uso',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'Идентификатор API {{indicator}} уже используется',
|
resource_identifier_in_use: 'Идентификатор API {{indicator}} уже используется',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API tanımlayıcısı {{indicator}} zaten kullanımda',
|
resource_identifier_in_use: 'API tanımlayıcısı {{indicator}} zaten kullanımda',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API 标识符 {{indicator}} 已被使用',
|
resource_identifier_in_use: 'API 标识符 {{indicator}} 已被使用',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API 識別碼 {{indicator}} 已經被使用',
|
resource_identifier_in_use: 'API 識別碼 {{indicator}} 已經被使用',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
|
@ -2,6 +2,8 @@ const resource = {
|
||||||
resource_identifier_in_use: 'API 識別碼 {{indicator}} 已經被使用',
|
resource_identifier_in_use: 'API 識別碼 {{indicator}} 已經被使用',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
cannot_delete_management_api: 'Cannot delete Logto management API.',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
cannot_modify_management_api: 'Cannot modify Logto management API.',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(resource);
|
export default Object.freeze(resource);
|
||||||
|
|
Loading…
Add table
Reference in a new issue