mirror of
https://github.com/logto-io/logto.git
synced 2025-01-13 21:30:30 -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",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"[scss]": {
|
"[scss]": {
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.stylelint": true
|
"source.fixAll.stylelint": "explicit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stylelint.validate": [
|
"stylelint.validate": [
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"typescriptreact",
|
"typescriptreact",
|
||||||
],
|
],
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true,
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
"json.schemas": [
|
"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 type { Scope } from '@logto/schemas';
|
||||||
|
|
||||||
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
import type Queries from '#src/tenants/Queries.js';
|
import type Queries from '#src/tenants/Queries.js';
|
||||||
|
import assertThat from '#src/utils/assert-that.js';
|
||||||
|
|
||||||
export const createApplicationLibrary = (queries: Queries) => {
|
export const createApplicationLibrary = (queries: Queries) => {
|
||||||
const {
|
const {
|
||||||
|
applications: {
|
||||||
|
findApplicationById,
|
||||||
|
userConsentOrganizationScopes,
|
||||||
|
userConsentResourceScopes,
|
||||||
|
useConsentUserScopes,
|
||||||
|
},
|
||||||
applicationsRoles: { findApplicationsRolesByApplicationId },
|
applicationsRoles: { findApplicationsRolesByApplicationId },
|
||||||
rolesScopes: { findRolesScopesByRoleIds },
|
rolesScopes: { findRolesScopesByRoleIds },
|
||||||
scopes: { findScopesByIdsAndResourceIndicator },
|
organizations: { scopes: organizationScopesQuery },
|
||||||
|
scopes: { findScopesByIdsAndResourceIndicator, findScopesByIds },
|
||||||
} = queries;
|
} = queries;
|
||||||
|
|
||||||
const findApplicationScopesForResourceIndicator = async (
|
const findApplicationScopesForResourceIndicator = async (
|
||||||
|
@ -25,7 +34,88 @@ export const createApplicationLibrary = (queries: Queries) => {
|
||||||
return scopes;
|
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 {
|
return {
|
||||||
|
validateThirdPartyApplicationById,
|
||||||
findApplicationScopesForResourceIndicator,
|
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 { buildConditionsFromSearch } from '#src/utils/search.js';
|
||||||
import type { Search } 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 { table, fields } = convertToIdentifiers(Applications);
|
||||||
|
|
||||||
const buildApplicationConditions = (search: Search) => {
|
const buildApplicationConditions = (search: Search) => {
|
||||||
|
@ -233,5 +239,8 @@ export const createApplicationQueries = (pool: CommonQueryMethods) => {
|
||||||
findM2mApplicationsByIds,
|
findM2mApplicationsByIds,
|
||||||
findApplicationsByIds,
|
findApplicationsByIds,
|
||||||
deleteApplicationById,
|
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 adminUserRoutes from './admin-user/index.js';
|
||||||
import applicationRoleRoutes from './applications/application-role.js';
|
import applicationRoleRoutes from './applications/application-role.js';
|
||||||
|
import applicationUserConsentScopeRoutes from './applications/application-user-consent-scope.js';
|
||||||
import applicationRoutes from './applications/application.js';
|
import applicationRoutes from './applications/application.js';
|
||||||
import authnRoutes from './authn.js';
|
import authnRoutes from './authn.js';
|
||||||
import connectorRoutes from './connector/index.js';
|
import connectorRoutes from './connector/index.js';
|
||||||
|
@ -45,6 +46,11 @@ const createRouters = (tenant: TenantContext) => {
|
||||||
|
|
||||||
applicationRoutes(managementRouter, tenant);
|
applicationRoutes(managementRouter, tenant);
|
||||||
applicationRoleRoutes(managementRouter, tenant);
|
applicationRoleRoutes(managementRouter, tenant);
|
||||||
|
|
||||||
|
if (EnvSet.values.isDevFeaturesEnabled) {
|
||||||
|
applicationUserConsentScopeRoutes(managementRouter, tenant);
|
||||||
|
}
|
||||||
|
|
||||||
logtoConfigRoutes(managementRouter, tenant);
|
logtoConfigRoutes(managementRouter, tenant);
|
||||||
connectorRoutes(managementRouter, tenant);
|
connectorRoutes(managementRouter, tenant);
|
||||||
resourceRoutes(managementRouter, tenant);
|
resourceRoutes(managementRouter, tenant);
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { type CommonQueryMethods } from 'slonik';
|
||||||
|
|
||||||
import { buildDeleteByIdWithPool } from '#src/database/delete-by-id.js';
|
import { buildDeleteByIdWithPool } from '#src/database/delete-by-id.js';
|
||||||
import { buildFindAllEntitiesWithPool } from '#src/database/find-all-entities.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 { buildInsertIntoWithPool } from '#src/database/insert-into.js';
|
||||||
import { buildGetTotalRowCountWithPool } from '#src/database/row-count.js';
|
import { buildGetTotalRowCountWithPool } from '#src/database/row-count.js';
|
||||||
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
import { buildUpdateWhereWithPool } from '#src/database/update-where.js';
|
||||||
|
@ -31,6 +34,9 @@ export default class SchemaQueries<
|
||||||
) => Promise<readonly Schema[]>;
|
) => Promise<readonly Schema[]>;
|
||||||
|
|
||||||
#findById: (id: string) => Promise<Readonly<Schema>>;
|
#findById: (id: string) => Promise<Readonly<Schema>>;
|
||||||
|
|
||||||
|
#findByIds: (ids: string[]) => Promise<readonly Schema[]>;
|
||||||
|
|
||||||
#insert: (data: OmitAutoSetFields<CreateSchema>) => Promise<Readonly<Schema>>;
|
#insert: (data: OmitAutoSetFields<CreateSchema>) => Promise<Readonly<Schema>>;
|
||||||
|
|
||||||
#updateById: <SetKey extends Key | 'id', WhereKey extends Key | 'id'>(
|
#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.#findTotalNumber = buildGetTotalRowCountWithPool(this.pool, this.schema);
|
||||||
this.#findAll = buildFindAllEntitiesWithPool(this.pool)(this.schema, orderBy && [orderBy]);
|
this.#findAll = buildFindAllEntitiesWithPool(this.pool)(this.schema, orderBy && [orderBy]);
|
||||||
this.#findById = buildFindEntityByIdWithPool(this.pool)(this.schema);
|
this.#findById = buildFindEntityByIdWithPool(this.pool)(this.schema);
|
||||||
|
this.#findByIds = buildFindEntitiesByIdsWithPool(this.pool)(this.schema);
|
||||||
this.#insert = buildInsertIntoWithPool(this.pool)(this.schema, { returning: true });
|
this.#insert = buildInsertIntoWithPool(this.pool)(this.schema, { returning: true });
|
||||||
this.#updateById = buildUpdateWhereWithPool(this.pool)(this.schema, true);
|
this.#updateById = buildUpdateWhereWithPool(this.pool)(this.schema, true);
|
||||||
this.#deleteById = buildDeleteByIdWithPool(this.pool, this.schema.table);
|
this.#deleteById = buildDeleteByIdWithPool(this.pool, this.schema.table);
|
||||||
|
@ -64,6 +71,10 @@ export default class SchemaQueries<
|
||||||
return this.#findById(id);
|
return this.#findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findByIds(ids: string[]): Promise<readonly Schema[]> {
|
||||||
|
return this.#findByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
async insert(data: CreateSchema): Promise<Readonly<Schema>> {
|
async insert(data: CreateSchema): Promise<Readonly<Schema>> {
|
||||||
return this.#insert(data);
|
return this.#insert(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"@jest/test-sequencer": "^29.5.0",
|
"@jest/test-sequencer": "^29.5.0",
|
||||||
"@jest/types": "^29.1.2",
|
"@jest/types": "^29.1.2",
|
||||||
"@logto/connector-kit": "workspace:^2.0.0",
|
"@logto/connector-kit": "workspace:^2.0.0",
|
||||||
|
"@logto/core-kit": "workspace:^",
|
||||||
"@logto/js": "^3.0.1",
|
"@logto/js": "^3.0.1",
|
||||||
"@logto/node": "^2.2.0",
|
"@logto/node": "^2.2.0",
|
||||||
"@logto/schemas": "workspace:^1.12.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 */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
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_role_type: 'Can not assign user type role to machine to machine application.',
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -6,6 +6,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
|
@ -5,6 +5,11 @@ const application = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
invalid_third_party_application_type:
|
invalid_third_party_application_type:
|
||||||
'Only traditional web applications can be marked as a third-party app.',
|
'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);
|
export default Object.freeze(application);
|
||||||
|
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
@ -3747,6 +3747,9 @@ importers:
|
||||||
'@logto/connector-kit':
|
'@logto/connector-kit':
|
||||||
specifier: workspace:^2.0.0
|
specifier: workspace:^2.0.0
|
||||||
version: link:../toolkit/connector-kit
|
version: link:../toolkit/connector-kit
|
||||||
|
'@logto/core-kit':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../toolkit/core-kit
|
||||||
'@logto/js':
|
'@logto/js':
|
||||||
specifier: ^3.0.1
|
specifier: ^3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
|
|
Loading…
Add table
Reference in a new issue