mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(core,phrases): add post application user consent scopes api (#5101)
add post application user consent scopes api
This commit is contained in:
parent
fc71c8ae33
commit
cb43ebb7d1
28 changed files with 489 additions and 4 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -2,7 +2,7 @@
|
|||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"[scss]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.stylelint": true
|
||||
"source.fixAll.stylelint": "explicit"
|
||||
}
|
||||
},
|
||||
"stylelint.validate": [
|
||||
|
@ -24,7 +24,7 @@
|
|||
"typescriptreact",
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true,
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
"json.schemas": [
|
||||
{
|
||||
|
|
|
@ -44,3 +44,26 @@ export const buildFindEntityByIdWithPool =
|
|||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const buildFindEntitiesByIdsWithPool =
|
||||
(pool: CommonQueryMethods) =>
|
||||
<
|
||||
Key extends string,
|
||||
CreateSchema extends Partial<SchemaLike<WithId<Key>>>,
|
||||
Schema extends SchemaLike<WithId<Key>>,
|
||||
>(
|
||||
schema: GeneratedSchema<WithId<Key>, CreateSchema, Schema>
|
||||
) => {
|
||||
const { table, fields } = convertToIdentifiers(schema);
|
||||
const isKeyOfSchema = isKeyOf(schema);
|
||||
|
||||
// Make sure id is key of the schema
|
||||
assertThat(isKeyOfSchema('id'), 'entity.not_exists');
|
||||
|
||||
return async (ids: string[]) =>
|
||||
pool.any<Schema>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
where ${fields.id} in (${ids.length > 0 ? sql.join(ids, sql`, `) : sql`null`})
|
||||
`);
|
||||
};
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import type { Scope } from '@logto/schemas';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
export const createApplicationLibrary = (queries: Queries) => {
|
||||
const {
|
||||
applications: {
|
||||
findApplicationById,
|
||||
userConsentOrganizationScopes,
|
||||
userConsentResourceScopes,
|
||||
useConsentUserScopes,
|
||||
},
|
||||
applicationsRoles: { findApplicationsRolesByApplicationId },
|
||||
rolesScopes: { findRolesScopesByRoleIds },
|
||||
scopes: { findScopesByIdsAndResourceIndicator },
|
||||
organizations: { scopes: organizationScopesQuery },
|
||||
scopes: { findScopesByIdsAndResourceIndicator, findScopesByIds },
|
||||
} = queries;
|
||||
|
||||
const findApplicationScopesForResourceIndicator = async (
|
||||
|
@ -25,7 +34,88 @@ export const createApplicationLibrary = (queries: Queries) => {
|
|||
return scopes;
|
||||
};
|
||||
|
||||
// Guard application exists and is a third party application
|
||||
const validateThirdPartyApplicationById = async (applicationId: string) => {
|
||||
const application = await findApplicationById(applicationId);
|
||||
|
||||
assertThat(
|
||||
application.isThirdParty,
|
||||
'application.user_consent_scopes_only_for_third_party_applications'
|
||||
);
|
||||
};
|
||||
|
||||
// Guard that all scopes exist
|
||||
const validateApplicationUserConsentScopes = async ({
|
||||
organizationScopes = [],
|
||||
resourceScopes = [],
|
||||
}: {
|
||||
organizationScopes?: string[];
|
||||
resourceScopes?: string[];
|
||||
}) => {
|
||||
const [organizationScopesData, resourceScopesData] = await Promise.all([
|
||||
organizationScopesQuery.findByIds(organizationScopes),
|
||||
findScopesByIds(resourceScopes),
|
||||
]);
|
||||
|
||||
// Assert that all scopes exist, return the missing ones
|
||||
const invalidOrganizationScopes = organizationScopes.filter(
|
||||
(scope) => !organizationScopesData.some(({ id }) => id === scope)
|
||||
);
|
||||
|
||||
const invalidResourceScopes = resourceScopes.filter(
|
||||
(scope) => !resourceScopesData.some(({ id }) => id === scope)
|
||||
);
|
||||
|
||||
assertThat(
|
||||
invalidOrganizationScopes.length === 0 && invalidResourceScopes.length === 0,
|
||||
new RequestError(
|
||||
{
|
||||
code: 'application.user_consent_scopes_not_found',
|
||||
status: 422,
|
||||
},
|
||||
{ invalidOrganizationScopes, invalidResourceScopes }
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Assign consent scopes to application
|
||||
const assignApplicationUserConsentScopes = async (
|
||||
applicationId: string,
|
||||
{
|
||||
organizationScopes,
|
||||
resourceScopes,
|
||||
userScopes,
|
||||
}: {
|
||||
organizationScopes?: string[];
|
||||
resourceScopes?: string[];
|
||||
userScopes?: string[];
|
||||
}
|
||||
) => {
|
||||
if (organizationScopes) {
|
||||
await userConsentOrganizationScopes.insert(
|
||||
...organizationScopes.map<[string, string]>((scope) => [applicationId, scope])
|
||||
);
|
||||
}
|
||||
|
||||
if (resourceScopes) {
|
||||
await userConsentResourceScopes.insert(
|
||||
...resourceScopes.map<[string, string]>((scope) => [applicationId, scope])
|
||||
);
|
||||
}
|
||||
|
||||
if (userScopes) {
|
||||
await Promise.all(
|
||||
userScopes.map(async (userScope) =>
|
||||
useConsentUserScopes.insert({ applicationId, userScope })
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
validateThirdPartyApplicationById,
|
||||
findApplicationScopesForResourceIndicator,
|
||||
validateApplicationUserConsentScopes,
|
||||
assignApplicationUserConsentScopes,
|
||||
};
|
||||
};
|
||||
|
|
40
packages/core/src/queries/application-user-consent-scopes.ts
Normal file
40
packages/core/src/queries/application-user-consent-scopes.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
ApplicationUserConsentOrganizationScopes,
|
||||
ApplicationUserConsentResourceScopes,
|
||||
ApplicationUserConsentUserScopes,
|
||||
Applications,
|
||||
OrganizationScopes,
|
||||
Scopes,
|
||||
} from '@logto/schemas';
|
||||
import { type CommonQueryMethods } from 'slonik';
|
||||
|
||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
import { TwoRelationsQueries } from '#src/utils/RelationQueries.js';
|
||||
|
||||
export class ApplicationUserConsentOrganizationScopeQueries extends TwoRelationsQueries<
|
||||
typeof Applications,
|
||||
typeof OrganizationScopes
|
||||
> {
|
||||
constructor(pool: CommonQueryMethods) {
|
||||
super(pool, ApplicationUserConsentOrganizationScopes.table, Applications, OrganizationScopes);
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationUserConsentResourceScopeQueries extends TwoRelationsQueries<
|
||||
typeof Applications,
|
||||
typeof Scopes
|
||||
> {
|
||||
constructor(pool: CommonQueryMethods) {
|
||||
super(pool, ApplicationUserConsentResourceScopes.table, Applications, Scopes);
|
||||
}
|
||||
}
|
||||
|
||||
export const createApplicationUserConsentUserScopeQueries = (pool: CommonQueryMethods) => {
|
||||
const insert = buildInsertIntoWithPool(pool)(ApplicationUserConsentUserScopes, {
|
||||
onConflict: { ignore: true },
|
||||
});
|
||||
|
||||
return {
|
||||
insert,
|
||||
};
|
||||
};
|
|
@ -13,6 +13,12 @@ import { DeletionError } from '#src/errors/SlonikError/index.js';
|
|||
import { buildConditionsFromSearch } from '#src/utils/search.js';
|
||||
import type { Search } from '#src/utils/search.js';
|
||||
|
||||
import {
|
||||
ApplicationUserConsentOrganizationScopeQueries,
|
||||
ApplicationUserConsentResourceScopeQueries,
|
||||
createApplicationUserConsentUserScopeQueries,
|
||||
} from './application-user-consent-scopes.js';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Applications);
|
||||
|
||||
const buildApplicationConditions = (search: Search) => {
|
||||
|
@ -233,5 +239,8 @@ export const createApplicationQueries = (pool: CommonQueryMethods) => {
|
|||
findM2mApplicationsByIds,
|
||||
findApplicationsByIds,
|
||||
deleteApplicationById,
|
||||
userConsentOrganizationScopes: new ApplicationUserConsentOrganizationScopeQueries(pool),
|
||||
userConsentResourceScopes: new ApplicationUserConsentResourceScopeQueries(pool),
|
||||
useConsentUserScopes: createApplicationUserConsentUserScopeQueries(pool),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"paths": {
|
||||
"/api/applications/{applicationId}/user-consent-scopes": {
|
||||
"post": {
|
||||
"summary": "Assign user consent scopes to application.",
|
||||
"description": "Assign the user consent scopes to an application by application id",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"organizationScopes": {
|
||||
"description": "A list of organization scope id to assign to the application. Throws error if any given organization scope is not found."
|
||||
},
|
||||
"resourceScopes": {
|
||||
"description": "A list of resource scope id to assign to the application. Throws error if any given resource scope is not found."
|
||||
},
|
||||
"userScopes": {
|
||||
"description": "A list of user scope enum value to assign to the application."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "All the user consent scopes are assigned to the application successfully"
|
||||
},
|
||||
"404": {
|
||||
"description": "The application is not found"
|
||||
},
|
||||
"422": {
|
||||
"description": "Any of the given organization scope, resource scope or user scope is not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import { UserScope } from '@logto/core-kit';
|
||||
import { object, string, nativeEnum } from 'zod';
|
||||
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
|
||||
import type { AuthedRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
export default function applicationUserConsentScopeRoutes<T extends AuthedRouter>(
|
||||
...[
|
||||
router,
|
||||
{
|
||||
libraries: {
|
||||
applications: {
|
||||
validateThirdPartyApplicationById,
|
||||
validateApplicationUserConsentScopes,
|
||||
assignApplicationUserConsentScopes,
|
||||
},
|
||||
},
|
||||
},
|
||||
]: RouterInitArgs<T>
|
||||
) {
|
||||
router.post(
|
||||
'/applications/:applicationId/user-consent-scopes',
|
||||
koaGuard({
|
||||
params: object({
|
||||
applicationId: string(),
|
||||
}),
|
||||
body: object({
|
||||
organizationScopes: string().array().optional(),
|
||||
resourceScopes: string().array().optional(),
|
||||
userScopes: nativeEnum(UserScope).array().optional(),
|
||||
}),
|
||||
status: [201, 404, 422],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const {
|
||||
params: { applicationId },
|
||||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
await validateThirdPartyApplicationById(applicationId);
|
||||
|
||||
await validateApplicationUserConsentScopes(body);
|
||||
|
||||
await assignApplicationUserConsentScopes(applicationId, body);
|
||||
|
||||
ctx.status = 201;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
|
@ -12,6 +12,7 @@ import koaAuth from '../middleware/koa-auth/index.js';
|
|||
|
||||
import adminUserRoutes from './admin-user/index.js';
|
||||
import applicationRoleRoutes from './applications/application-role.js';
|
||||
import applicationUserConsentScopeRoutes from './applications/application-user-consent-scope.js';
|
||||
import applicationRoutes from './applications/application.js';
|
||||
import authnRoutes from './authn.js';
|
||||
import connectorRoutes from './connector/index.js';
|
||||
|
@ -45,6 +46,11 @@ const createRouters = (tenant: TenantContext) => {
|
|||
|
||||
applicationRoutes(managementRouter, tenant);
|
||||
applicationRoleRoutes(managementRouter, tenant);
|
||||
|
||||
if (EnvSet.values.isDevFeaturesEnabled) {
|
||||
applicationUserConsentScopeRoutes(managementRouter, tenant);
|
||||
}
|
||||
|
||||
logtoConfigRoutes(managementRouter, tenant);
|
||||
connectorRoutes(managementRouter, tenant);
|
||||
resourceRoutes(managementRouter, tenant);
|
||||
|
|
|
@ -4,7 +4,10 @@ import { type CommonQueryMethods } from 'slonik';
|
|||
|
||||
import { buildDeleteByIdWithPool } from '#src/database/delete-by-id.js';
|
||||
import { buildFindAllEntitiesWithPool } from '#src/database/find-all-entities.js';
|
||||
import { buildFindEntityByIdWithPool } from '#src/database/find-entity-by-id.js';
|
||||
import {
|
||||
buildFindEntitiesByIdsWithPool,
|
||||
buildFindEntityByIdWithPool,
|
||||
} from '#src/database/find-entity-by-id.js';
|
||||
import { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||
import { buildGetTotalRowCountWithPool } from '#src/database/row-count.js';
|
||||
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
||||
|
@ -31,6 +34,9 @@ export default class SchemaQueries<
|
|||
) => Promise<readonly Schema[]>;
|
||||
|
||||
#findById: (id: string) => Promise<Readonly<Schema>>;
|
||||
|
||||
#findByIds: (ids: string[]) => Promise<readonly Schema[]>;
|
||||
|
||||
#insert: (data: OmitAutoSetFields<CreateSchema>) => Promise<Readonly<Schema>>;
|
||||
|
||||
#updateById: <SetKey extends Key | 'id', WhereKey extends Key | 'id'>(
|
||||
|
@ -47,6 +53,7 @@ export default class SchemaQueries<
|
|||
this.#findTotalNumber = buildGetTotalRowCountWithPool(this.pool, this.schema);
|
||||
this.#findAll = buildFindAllEntitiesWithPool(this.pool)(this.schema, orderBy && [orderBy]);
|
||||
this.#findById = buildFindEntityByIdWithPool(this.pool)(this.schema);
|
||||
this.#findByIds = buildFindEntitiesByIdsWithPool(this.pool)(this.schema);
|
||||
this.#insert = buildInsertIntoWithPool(this.pool)(this.schema, { returning: true });
|
||||
this.#updateById = buildUpdateWhereWithPool(this.pool)(this.schema, true);
|
||||
this.#deleteById = buildDeleteByIdWithPool(this.pool, this.schema.table);
|
||||
|
@ -64,6 +71,10 @@ export default class SchemaQueries<
|
|||
return this.#findById(id);
|
||||
}
|
||||
|
||||
async findByIds(ids: string[]): Promise<readonly Schema[]> {
|
||||
return this.#findByIds(ids);
|
||||
}
|
||||
|
||||
async insert(data: CreateSchema): Promise<Readonly<Schema>> {
|
||||
return this.#insert(data);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"@jest/test-sequencer": "^29.5.0",
|
||||
"@jest/types": "^29.1.2",
|
||||
"@logto/connector-kit": "workspace:^2.0.0",
|
||||
"@logto/core-kit": "workspace:^",
|
||||
"@logto/js": "^3.0.1",
|
||||
"@logto/node": "^2.2.0",
|
||||
"@logto/schemas": "workspace:^1.12.0",
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { type UserScope } from '@logto/core-kit';
|
||||
|
||||
import { authedAdminApi } from './api.js';
|
||||
|
||||
export const assignUserConsentScopes = async (
|
||||
applicationId: string,
|
||||
payload: {
|
||||
organizationScopes?: string[];
|
||||
resourceScopes?: string[];
|
||||
userScopes?: UserScope[];
|
||||
}
|
||||
) => authedAdminApi.post(`applications/${applicationId}/user-consent-scopes`, { json: payload });
|
|
@ -0,0 +1,125 @@
|
|||
import { UserScope } from '@logto/core-kit';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
|
||||
import { assignUserConsentScopes } from '#src/api/application-user-consent-scope.js';
|
||||
import { createApplication, deleteApplication } from '#src/api/application.js';
|
||||
import { OrganizationScopeApi } from '#src/api/organization-scope.js';
|
||||
import { createResource, deleteResource } from '#src/api/resource.js';
|
||||
import { createScope } from '#src/api/scope.js';
|
||||
import { expectRejects } from '#src/helpers/index.js';
|
||||
|
||||
describe('assign user consent scopes to application', () => {
|
||||
const applicationIds = new Map<string, string>();
|
||||
const organizationScopes = new Map<string, string>();
|
||||
const resourceScopes = new Map<string, string>();
|
||||
const resourceIds = new Set<string>();
|
||||
|
||||
const organizationScopeApi = new OrganizationScopeApi();
|
||||
|
||||
beforeAll(async () => {
|
||||
const firstPartyApp = await createApplication('first-party-application', ApplicationType.SPA);
|
||||
const thirdPartyApp = await createApplication(
|
||||
'third-party-application',
|
||||
ApplicationType.Traditional,
|
||||
{
|
||||
isThirdParty: true,
|
||||
}
|
||||
);
|
||||
|
||||
applicationIds.set('firstPartyApp', firstPartyApp.id);
|
||||
applicationIds.set('thirdPartyApp', thirdPartyApp.id);
|
||||
|
||||
const organizationScope1 = await organizationScopeApi.create({
|
||||
name: 'organization-scope-1',
|
||||
});
|
||||
|
||||
const organizationScope2 = await organizationScopeApi.create({
|
||||
name: 'organization-scope-2',
|
||||
});
|
||||
|
||||
organizationScopes.set('organizationScope1', organizationScope1.id);
|
||||
organizationScopes.set('organizationScope2', organizationScope2.id);
|
||||
|
||||
const resource = await createResource();
|
||||
resourceIds.add(resource.id);
|
||||
|
||||
const resourceScope1 = await createScope(resource.id);
|
||||
const resourceScope2 = await createScope(resource.id);
|
||||
|
||||
resourceScopes.set('resourceScope1', resourceScope1.id);
|
||||
resourceScopes.set('resourceScope2', resourceScope2.id);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(
|
||||
Array.from(resourceIds).map(async (resourceId) => deleteResource(resourceId))
|
||||
);
|
||||
await Promise.all(
|
||||
Array.from(organizationScopes.values()).map(async (organizationScopeId) =>
|
||||
organizationScopeApi.delete(organizationScopeId)
|
||||
)
|
||||
);
|
||||
await Promise.all(
|
||||
Array.from(applicationIds.values()).map(async (applicationId) =>
|
||||
deleteApplication(applicationId)
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when trying to assign scopes to non-third-party application', async () => {
|
||||
await expectRejects(
|
||||
assignUserConsentScopes(applicationIds.get('firstPartyApp')!, {
|
||||
organizationScopes: Array.from(organizationScopes.values()),
|
||||
resourceScopes: Array.from(resourceScopes.values()),
|
||||
}),
|
||||
{
|
||||
code: 'application.user_consent_scopes_only_for_third_party_applications',
|
||||
statusCode: 400,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when trying to assign a non-existing organization scope', async () => {
|
||||
await expectRejects(
|
||||
assignUserConsentScopes(applicationIds.get('thirdPartyApp')!, {
|
||||
organizationScopes: ['non-existing-organization-scope'],
|
||||
}),
|
||||
{
|
||||
code: 'application.user_consent_scopes_not_found',
|
||||
statusCode: 422,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error when trying to assign a non-existing resource scope', async () => {
|
||||
await expectRejects(
|
||||
assignUserConsentScopes(applicationIds.get('thirdPartyApp')!, {
|
||||
resourceScopes: ['non-existing-resource-scope'],
|
||||
}),
|
||||
{
|
||||
code: 'application.user_consent_scopes_not_found',
|
||||
statusCode: 422,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should assign scopes to third-party application successfully', async () => {
|
||||
await expect(
|
||||
assignUserConsentScopes(applicationIds.get('thirdPartyApp')!, {
|
||||
organizationScopes: Array.from(organizationScopes.values()),
|
||||
resourceScopes: Array.from(resourceScopes.values()),
|
||||
userScopes: [UserScope.Profile, UserScope.Email, UserScope.OrganizationRoles],
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should not throw error when trying to assign existing consent scopes', async () => {
|
||||
await expect(
|
||||
assignUserConsentScopes(applicationIds.get('thirdPartyApp')!, {
|
||||
organizationScopes: [organizationScopes.get('organizationScope1')!],
|
||||
resourceScopes: [resourceScopes.get('resourceScope1')!],
|
||||
userScopes: [UserScope.Profile],
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
});
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -4,6 +4,9 @@ const application = {
|
|||
invalid_role_type: 'Can not assign user type role to machine to machine application.',
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
|||
/** UNTRANSLATED */
|
||||
invalid_third_party_application_type:
|
||||
'Only traditional web applications can be marked as a third-party app.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_only_for_third_party_applications:
|
||||
'Only third-party applications can manage user consent scopes.',
|
||||
/** UNTRANSLATED */
|
||||
user_consent_scopes_not_found: 'Invalid user consent scopes.',
|
||||
};
|
||||
|
||||
export default Object.freeze(application);
|
||||
|
|
|
@ -3747,6 +3747,9 @@ importers:
|
|||
'@logto/connector-kit':
|
||||
specifier: workspace:^2.0.0
|
||||
version: link:../toolkit/connector-kit
|
||||
'@logto/core-kit':
|
||||
specifier: workspace:^
|
||||
version: link:../toolkit/core-kit
|
||||
'@logto/js':
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1
|
||||
|
|
Loading…
Reference in a new issue