mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat: support default API Resource
This commit is contained in:
parent
0dbacb89aa
commit
c933bf58f7
22 changed files with 160 additions and 33 deletions
|
@ -7,6 +7,7 @@ import { useOutletContext } from 'react-router-dom';
|
||||||
import DetailsForm from '@/components/DetailsForm';
|
import DetailsForm from '@/components/DetailsForm';
|
||||||
import FormCard from '@/components/FormCard';
|
import FormCard from '@/components/FormCard';
|
||||||
import FormField from '@/components/FormField';
|
import FormField from '@/components/FormField';
|
||||||
|
import Switch from '@/components/Switch';
|
||||||
import TextInput from '@/components/TextInput';
|
import TextInput from '@/components/TextInput';
|
||||||
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
|
||||||
import useApi from '@/hooks/use-api';
|
import useApi from '@/hooks/use-api';
|
||||||
|
@ -32,14 +33,20 @@ function ApiResourceSettings() {
|
||||||
|
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (formData) => {
|
const onSubmit = handleSubmit(async ({ isDefault, ...rest }) => {
|
||||||
if (isSubmitting) {
|
if (isSubmitting) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedApiResource = await api
|
const [data] = await Promise.all([
|
||||||
.patch(`api/resources/${resource.id}`, { json: formData })
|
api.patch(`api/resources/${resource.id}`, { json: rest }).json<Resource>(),
|
||||||
.json<Resource>();
|
api
|
||||||
|
.patch(`api/resources/${resource.id}/is-default`, { json: { isDefault } })
|
||||||
|
.json<Resource>(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// We cannot ensure the order of API requests, manually combine the results
|
||||||
|
const updatedApiResource = { ...data, isDefault };
|
||||||
reset(updatedApiResource);
|
reset(updatedApiResource);
|
||||||
onResourceUpdated(updatedApiResource);
|
onResourceUpdated(updatedApiResource);
|
||||||
toast.success(t('general.saved'));
|
toast.success(t('general.saved'));
|
||||||
|
@ -77,6 +84,11 @@ function ApiResourceSettings() {
|
||||||
placeholder={t('api_resource_details.token_expiration_time_in_seconds_placeholder')}
|
placeholder={t('api_resource_details.token_expiration_time_in_seconds_placeholder')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{!isLogtoManagementApiResource && (
|
||||||
|
<FormField title="api_resources.default_api">
|
||||||
|
<Switch {...register('isDefault')} label={t('api_resources.default_api_label')} />
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
</FormCard>
|
</FormCard>
|
||||||
</DetailsForm>
|
</DetailsForm>
|
||||||
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />
|
<UnsavedChangesAlertModal hasUnsavedChanges={!isDeleting && isDirty} />
|
||||||
|
|
|
@ -3,14 +3,8 @@ import type { OmitAutoSetFields, UpdateWhereData } from '@logto/shared';
|
||||||
import { SlonikError } from 'slonik';
|
import { SlonikError } from 'slonik';
|
||||||
|
|
||||||
export class DeletionError extends SlonikError {
|
export class DeletionError extends SlonikError {
|
||||||
table?: string;
|
public constructor(public readonly table?: string, public readonly id?: string) {
|
||||||
id?: string;
|
|
||||||
|
|
||||||
public constructor(table?: string, id?: string) {
|
|
||||||
super('Resource not found.');
|
super('Resource not found.');
|
||||||
|
|
||||||
this.table = table;
|
|
||||||
this.id = id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,17 +12,11 @@ export class UpdateError<
|
||||||
CreateSchema extends SchemaLike,
|
CreateSchema extends SchemaLike,
|
||||||
Schema extends CreateSchema
|
Schema extends CreateSchema
|
||||||
> extends SlonikError {
|
> extends SlonikError {
|
||||||
schema: GeneratedSchema<CreateSchema, Schema>;
|
|
||||||
detail: UpdateWhereData<Schema>;
|
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
public readonly schema: GeneratedSchema<CreateSchema, Schema>,
|
||||||
detail: UpdateWhereData<Schema>
|
public readonly detail: Partial<UpdateWhereData<Schema>>
|
||||||
) {
|
) {
|
||||||
super('Resource not found.');
|
super('Resource not found.');
|
||||||
|
|
||||||
this.schema = schema;
|
|
||||||
this.detail = detail;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,16 +24,10 @@ export class InsertionError<
|
||||||
CreateSchema extends SchemaLike,
|
CreateSchema extends SchemaLike,
|
||||||
Schema extends CreateSchema
|
Schema extends CreateSchema
|
||||||
> extends SlonikError {
|
> extends SlonikError {
|
||||||
schema: GeneratedSchema<CreateSchema, Schema>;
|
|
||||||
detail?: OmitAutoSetFields<CreateSchema>;
|
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
schema: GeneratedSchema<CreateSchema, Schema>,
|
public readonly schema: GeneratedSchema<CreateSchema, Schema>,
|
||||||
detail?: OmitAutoSetFields<CreateSchema>
|
public readonly detail?: OmitAutoSetFields<CreateSchema>
|
||||||
) {
|
) {
|
||||||
super('Create Error.');
|
super('Create Error.');
|
||||||
|
|
||||||
this.schema = schema;
|
|
||||||
this.detail = detail;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default function initOidc(
|
||||||
defaultRefreshTokenTtl,
|
defaultRefreshTokenTtl,
|
||||||
} = envSet.oidc;
|
} = envSet.oidc;
|
||||||
const {
|
const {
|
||||||
resources: { findResourceByIndicator },
|
resources: { findResourceByIndicator, findDefaultResource },
|
||||||
users: { findUserById },
|
users: { findUserById },
|
||||||
} = queries;
|
} = queries;
|
||||||
const { findUserScopesForResourceIndicator } = libraries.users;
|
const { findUserScopesForResourceIndicator } = libraries.users;
|
||||||
|
@ -105,7 +105,10 @@ export default function initOidc(
|
||||||
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#featuresresourceindicators
|
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#featuresresourceindicators
|
||||||
resourceIndicators: {
|
resourceIndicators: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
defaultResource: () => '',
|
defaultResource: async () => {
|
||||||
|
const resource = await findDefaultResource();
|
||||||
|
return resource?.indicator ?? '';
|
||||||
|
},
|
||||||
// Disable the auto use of authorization_code granted resource feature
|
// Disable the auto use of authorization_code granted resource feature
|
||||||
useGrantedResource: () => false,
|
useGrantedResource: () => false,
|
||||||
getResourceServerInfo: async (ctx, indicator) => {
|
getResourceServerInfo: async (ctx, indicator) => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js'
|
||||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||||
import { getTotalRowCountWithPool } from '#src/database/row-count.js';
|
import { getTotalRowCountWithPool } from '#src/database/row-count.js';
|
||||||
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
||||||
import { DeletionError } from '#src/errors/SlonikError/index.js';
|
import { DeletionError, UpdateError } from '#src/errors/SlonikError/index.js';
|
||||||
|
|
||||||
const { table, fields } = convertToIdentifiers(Resources);
|
const { table, fields } = convertToIdentifiers(Resources);
|
||||||
|
|
||||||
|
@ -26,6 +26,35 @@ export const createResourceQueries = (pool: CommonQueryMethods) => {
|
||||||
where ${fields.indicator}=${indicator}
|
where ${fields.indicator}=${indicator}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const findDefaultResource = async () =>
|
||||||
|
pool.maybeOne<Resource>(sql`
|
||||||
|
select ${sql.join(Object.values(fields), sql`, `)}
|
||||||
|
from ${table}
|
||||||
|
where ${fields.isDefault}=true
|
||||||
|
`);
|
||||||
|
|
||||||
|
const setDefaultResource = async (id: string) => {
|
||||||
|
return pool.transaction(async (connection) => {
|
||||||
|
await connection.query(sql`
|
||||||
|
update ${table}
|
||||||
|
set ${fields.isDefault}=false
|
||||||
|
where ${fields.id}!=${id};
|
||||||
|
`);
|
||||||
|
const returning = await connection.maybeOne<Resource>(sql`
|
||||||
|
update ${table}
|
||||||
|
set ${fields.isDefault}=true
|
||||||
|
where ${fields.id}=${id}
|
||||||
|
returning *;
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (!returning) {
|
||||||
|
throw new UpdateError(Resources, { set: { isDefault: true }, where: { id } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return returning;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const findResourceById = buildFindEntityByIdWithPool(pool)(Resources);
|
const findResourceById = buildFindEntityByIdWithPool(pool)(Resources);
|
||||||
|
|
||||||
const findResourcesByIds = async (resourceIds: string[]) =>
|
const findResourcesByIds = async (resourceIds: string[]) =>
|
||||||
|
@ -64,6 +93,8 @@ export const createResourceQueries = (pool: CommonQueryMethods) => {
|
||||||
findTotalNumberOfResources,
|
findTotalNumberOfResources,
|
||||||
findAllResources,
|
findAllResources,
|
||||||
findResourceByIndicator,
|
findResourceByIndicator,
|
||||||
|
findDefaultResource,
|
||||||
|
setDefaultResource,
|
||||||
findResourceById,
|
findResourceById,
|
||||||
findResourcesByIds,
|
findResourcesByIds,
|
||||||
insertResource,
|
insertResource,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Resources, Scopes } from '@logto/schemas';
|
import { Resources, Scopes } from '@logto/schemas';
|
||||||
import { buildIdGenerator } from '@logto/shared';
|
import { buildIdGenerator } from '@logto/shared';
|
||||||
import { tryThat, yes } from '@silverhand/essentials';
|
import { tryThat, yes } from '@silverhand/essentials';
|
||||||
import { object, string } from 'zod';
|
import { boolean, object, string } from 'zod';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
import koaGuard from '#src/middleware/koa-guard.js';
|
import koaGuard from '#src/middleware/koa-guard.js';
|
||||||
|
@ -23,6 +23,7 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
findAllResources,
|
findAllResources,
|
||||||
findResourceById,
|
findResourceById,
|
||||||
findResourceByIndicator,
|
findResourceByIndicator,
|
||||||
|
setDefaultResource,
|
||||||
insertResource,
|
insertResource,
|
||||||
updateResourceById,
|
updateResourceById,
|
||||||
deleteResourceById,
|
deleteResourceById,
|
||||||
|
@ -76,7 +77,9 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
router.post(
|
router.post(
|
||||||
'/resources',
|
'/resources',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: Resources.createGuard.omit({ id: true }),
|
// Intentionally omit `isDefault` since it'll affect other rows.
|
||||||
|
// Use the dedicated API `PATCH /resources/:id/is-default` to update.
|
||||||
|
body: Resources.createGuard.omit({ id: true, isDefault: true }),
|
||||||
response: Resources.guard.extend({ scopes: Scopes.guard.array().optional() }),
|
response: Resources.guard.extend({ scopes: Scopes.guard.array().optional() }),
|
||||||
status: [201, 422],
|
status: [201, 422],
|
||||||
}),
|
}),
|
||||||
|
@ -128,7 +131,9 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
'/resources/:id',
|
'/resources/:id',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
params: object({ id: string().min(1) }),
|
params: object({ id: string().min(1) }),
|
||||||
body: Resources.createGuard.omit({ id: true, indicator: true }).partial(),
|
// Intentionally omit `isDefault` since it'll affect other rows.
|
||||||
|
// Use the dedicated API `PATCH /resources/:id/is-default` to update.
|
||||||
|
body: Resources.createGuard.omit({ id: true, indicator: true, isDefault: true }).partial(),
|
||||||
response: Resources.guard,
|
response: Resources.guard,
|
||||||
status: [200, 404],
|
status: [200, 404],
|
||||||
}),
|
}),
|
||||||
|
@ -145,6 +150,27 @@ export default function resourceRoutes<T extends AuthedRouter>(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
'/resources/:id/is-default',
|
||||||
|
koaGuard({
|
||||||
|
params: object({ id: string().min(1) }),
|
||||||
|
body: object({ isDefault: boolean() }),
|
||||||
|
response: Resources.guard,
|
||||||
|
status: [200, 404],
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const {
|
||||||
|
params: { id },
|
||||||
|
body: { isDefault },
|
||||||
|
} = ctx.guard;
|
||||||
|
|
||||||
|
// Only 0 or 1 default resource is allowed per tenant, so use a dedicated transaction query for setting the default.
|
||||||
|
ctx.body = await (isDefault ? setDefaultResource(id) : updateResourceById(id, { isDefault }));
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/resources/:id',
|
'/resources/:id',
|
||||||
koaGuard({ params: object({ id: string().min(1) }), status: [204, 404] }),
|
koaGuard({ params: object({ id: string().min(1) }), status: [204, 404] }),
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'API Identifikator',
|
api_identifier: 'API Identifikator',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'Der eindeutige Identifikator der API Ressource muss eine absolute URI ohne Fragmentbezeichner (#) sein. Entspricht dem <a>Ressourcen Parameter</a> in OAuth 2.0.',
|
'Der eindeutige Identifikator der API Ressource muss eine absolute URI ohne Fragmentbezeichner (#) sein. Entspricht dem <a>Ressourcen Parameter</a> in OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'Die API Ressource {{name}} wurde erfolgreich angelegt',
|
api_resource_created: 'Die API Ressource {{name}} wurde erfolgreich angelegt',
|
||||||
api_identifier_placeholder: 'https://dein-api-identifikator/',
|
api_identifier_placeholder: 'https://dein-api-identifikator/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'API identifier',
|
api_identifier: 'API identifier',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'The unique identifier to the API resource. It must be an absolute URI and has no fragment (#) component. Equals to the <a>resource parameter</a> in OAuth 2.0.',
|
'The unique identifier to the API resource. It must be an absolute URI and has no fragment (#) component. Equals to the <a>resource parameter</a> in OAuth 2.0.',
|
||||||
|
default_api: 'Default API',
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API.\nWhen a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.',
|
||||||
api_resource_created: 'The API resource {{name}} has been successfully created',
|
api_resource_created: 'The API resource {{name}} has been successfully created',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Identificador de API',
|
api_identifier: 'Identificador de API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'El identificador único para el recurso de API. Debe ser una URI absoluta y no tiene componente de fragmento (#). Es igual al <a>parámetro de recurso</a> en OAuth 2.0.',
|
'El identificador único para el recurso de API. Debe ser una URI absoluta y no tiene componente de fragmento (#). Es igual al <a>parámetro de recurso</a> en OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'El recurso de API {{name}} se ha creado correctamente',
|
api_resource_created: 'El recurso de API {{name}} se ha creado correctamente',
|
||||||
api_identifier_placeholder: 'https://su-identificador-de-api/',
|
api_identifier_placeholder: 'https://su-identificador-de-api/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Identifiant API',
|
api_identifier: 'Identifiant API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
"L'identifiant unique de la ressource API. Il doit s'agir d'un URI absolu et ne doit pas comporter de fragment (#). Équivaut au <a>paramètre de ressource</> dans OAuth 2.0.",
|
"L'identifiant unique de la ressource API. Il doit s'agir d'un URI absolu et ne doit pas comporter de fragment (#). Équivaut au <a>paramètre de ressource</> dans OAuth 2.0.",
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'La ressource API {{name}} a été créée avec succès.',
|
api_resource_created: 'La ressource API {{name}} a été créée avec succès.',
|
||||||
api_identifier_placeholder: 'https://votre-identifiant-api/',
|
api_identifier_placeholder: 'https://votre-identifiant-api/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Identificatore API',
|
api_identifier: 'Identificatore API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
"L'identificatore univoco della risorsa API. Deve essere un URI assoluto e non ha componenti di frammento (#). Corrisponde al parametro <a>risorsa</a> in OAuth 2.0.",
|
"L'identificatore univoco della risorsa API. Deve essere un URI assoluto e non ha componenti di frammento (#). Corrisponde al parametro <a>risorsa</a> in OAuth 2.0.",
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'La risorsa API {{name}} è stata creata con successo',
|
api_resource_created: 'La risorsa API {{name}} è stata creata con successo',
|
||||||
api_identifier_placeholder: 'https://tuo-identificatore-api/',
|
api_identifier_placeholder: 'https://tuo-identificatore-api/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'API 識別子',
|
api_identifier: 'API 識別子',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'API リソースの一意の識別子です。絶対URIで、フラグメント(#)コンポーネントはありません。OAuth 2.0での<a>resource parameter</a>に等しいです。',
|
'API リソースの一意の識別子です。絶対URIで、フラグメント(#)コンポーネントはありません。OAuth 2.0での<a>resource parameter</a>に等しいです。',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'APIリソース{{name}}が正常に作成されました',
|
api_resource_created: 'APIリソース{{name}}が正常に作成されました',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'API 식별자',
|
api_identifier: 'API 식별자',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'API 리소스에 대한 고유한 식별자예요. 절대 URI여야 하며 조각 (#) 컴포넌트가 없어야 해요. OAuth 2.0의 <a>resource parameter</a>와 같아요.',
|
'API 리소스에 대한 고유한 식별자예요. 절대 URI여야 하며 조각 (#) 컴포넌트가 없어야 해요. OAuth 2.0의 <a>resource parameter</a>와 같아요.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: '{{name}} API 리소스가 성공적으로 생성되었어요.',
|
api_resource_created: '{{name}} API 리소스가 성공적으로 생성되었어요.',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Identyfikator API',
|
api_identifier: 'Identyfikator API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'Unikalny identyfikator zasobu API. Musi to być bezwzględny adres URI bez składnika fragmentu (#). Jest równy <a>parametrowi zasobu</a> w standardzie OAuth 2.0.',
|
'Unikalny identyfikator zasobu API. Musi to być bezwzględny adres URI bez składnika fragmentu (#). Jest równy <a>parametrowi zasobu</a> w standardzie OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'Zasób API {{name}} został pomyślnie utworzony',
|
api_resource_created: 'Zasób API {{name}} został pomyślnie utworzony',
|
||||||
api_identifier_placeholder: 'https://identyfikator-twojego-api/',
|
api_identifier_placeholder: 'https://identyfikator-twojego-api/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Identificador de API',
|
api_identifier: 'Identificador de API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'O identificador exclusivo para o recurso da API. Deve ser um URI absoluto e não tem nenhum componente de fragmento (#). Igual ao <a>parâmetro de recurso</a> em OAuth 2.0.',
|
'O identificador exclusivo para o recurso da API. Deve ser um URI absoluto e não tem nenhum componente de fragmento (#). Igual ao <a>parâmetro de recurso</a> em OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'O recurso API {{name}} foi criado com sucesso',
|
api_resource_created: 'O recurso API {{name}} foi criado com sucesso',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'identificador da API',
|
api_identifier: 'identificador da API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'O identificador exclusivo para o recurso API. Deve ser um URI absoluto e não tem componente de fragmento (#). Igual ao <a>resource parameter</a> no OAuth 2.0.',
|
'O identificador exclusivo para o recurso API. Deve ser um URI absoluto e não tem componente de fragmento (#). Igual ao <a>resource parameter</a> no OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'O recurso API {{name}} foi criado com sucesso',
|
api_resource_created: 'O recurso API {{name}} foi criado com sucesso',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'Идентификатор API',
|
api_identifier: 'Идентификатор API',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'Уникальный идентификатор для ресурса API. Он должен быть абсолютным URI и не иметь фрагмента (#). Равен параметру <a>resource</a> в OAuth 2.0.',
|
'Уникальный идентификатор для ресурса API. Он должен быть абсолютным URI и не иметь фрагмента (#). Равен параметру <a>resource</a> в OAuth 2.0.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: 'Ресурс API {{name}} был успешно создан',
|
api_resource_created: 'Ресурс API {{name}} был успешно создан',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,9 @@ const api_resources = {
|
||||||
api_identifier: 'API belirteci',
|
api_identifier: 'API belirteci',
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'Api kaynağına özgün belirteç. Mutlak URI olmalı ve parça bileşeni (#) içermemeli. OAuth 2.0deki <a>kaynak parametresine</a> eşittir.',
|
'Api kaynağına özgün belirteç. Mutlak URI olmalı ve parça bileşeni (#) içermemeli. OAuth 2.0deki <a>kaynak parametresine</a> eşittir.',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
api_resource_created: '{{name}} API kaynağı başarıyla oluşturuldu',
|
api_resource_created: '{{name}} API kaynağı başarıyla oluşturuldu',
|
||||||
api_identifier_placeholder: 'https://your-api-identifier/',
|
api_identifier_placeholder: 'https://your-api-identifier/',
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,9 @@ const api_resources = {
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'对于 API 资源的唯一标识符。它必须是一个绝对 URI 并没有 fragment (#) 组件。等价于 OAuth 2.0 中的 <a>resource parameter</a>。',
|
'对于 API 资源的唯一标识符。它必须是一个绝对 URI 并没有 fragment (#) 组件。等价于 OAuth 2.0 中的 <a>resource parameter</a>。',
|
||||||
api_resource_created: ' API 资源 {{name}} 已成功创建。',
|
api_resource_created: ' API 资源 {{name}} 已成功创建。',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api_resources;
|
export default api_resources;
|
||||||
|
|
|
@ -10,6 +10,9 @@ const api_resources = {
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'對於 API 資源的唯一標識符。它必須是一個絕對 URI 並沒有 fragment (#) 組件。等價於 OAuth 2.0 中的 <a>resource parameter</a>。',
|
'對於 API 資源的唯一標識符。它必須是一個絕對 URI 並沒有 fragment (#) 組件。等價於 OAuth 2.0 中的 <a>resource parameter</a>。',
|
||||||
api_resource_created: ' API 資源 {{name}} 已成功創建。',
|
api_resource_created: ' API 資源 {{name}} 已成功創建。',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api_resources;
|
export default api_resources;
|
||||||
|
|
|
@ -10,6 +10,9 @@ const api_resources = {
|
||||||
api_identifier_tip:
|
api_identifier_tip:
|
||||||
'對於 API 資源的唯一標識符。它必須是一個絕對 URI,並沒有 fragment (#) 組件。等價於 OAuth 2.0 中的 <a>resource parameter</a>。',
|
'對於 API 資源的唯一標識符。它必須是一個絕對 URI,並沒有 fragment (#) 組件。等價於 OAuth 2.0 中的 <a>resource parameter</a>。',
|
||||||
api_resource_created: ' API 資源 {{name}} 已成功創建。',
|
api_resource_created: ' API 資源 {{name}} 已成功創建。',
|
||||||
|
default_api: 'Default API', // UNTRANSLATED
|
||||||
|
default_api_label:
|
||||||
|
'If the current API Resource is set as the default API for the tenant, while each tenant can have either 0 or 1 default API. When a default API is designated, the resource parameter can be omitted in the auth request. Subsequent token exchanges will use that API as the audience by default, resulting in the issuance of JWTs.', // UNTRANSLATED
|
||||||
};
|
};
|
||||||
|
|
||||||
export default api_resources;
|
export default api_resources;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { sql } from 'slonik';
|
||||||
|
|
||||||
|
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||||
|
|
||||||
|
const alteration: AlterationScript = {
|
||||||
|
up: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table resources
|
||||||
|
add column is_default boolean not null default (false);
|
||||||
|
create unique index resources__is_default_true
|
||||||
|
on resources (tenant_id)
|
||||||
|
where is_default = true;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
down: async (pool) => {
|
||||||
|
await pool.query(sql`
|
||||||
|
alter table resources
|
||||||
|
drop is_default;
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default alteration;
|
|
@ -6,6 +6,7 @@ create table resources (
|
||||||
id varchar(21) not null,
|
id varchar(21) not null,
|
||||||
name text not null,
|
name text not null,
|
||||||
indicator text not null, /* resource indicator also used as audience */
|
indicator text not null, /* resource indicator also used as audience */
|
||||||
|
is_default boolean not null default (false),
|
||||||
access_token_ttl bigint not null default(3600), /* expiration value in seconds, default is 1h */
|
access_token_ttl bigint not null default(3600), /* expiration value in seconds, default is 1h */
|
||||||
primary key (id),
|
primary key (id),
|
||||||
constraint resources__indicator
|
constraint resources__indicator
|
||||||
|
@ -14,3 +15,7 @@ create table resources (
|
||||||
|
|
||||||
create index resources__id
|
create index resources__id
|
||||||
on resources (tenant_id, id);
|
on resources (tenant_id, id);
|
||||||
|
|
||||||
|
create unique index resources__is_default_true
|
||||||
|
on resources (tenant_id)
|
||||||
|
where is_default = true;
|
||||||
|
|
Loading…
Reference in a new issue