0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

feat(core,schemas): add isThirdParty column to the applications table (#5090)

* feat(core,schemas): add isThirdParty column to the applications table

add isThirdParty column to the applications table

* refactor(core): group the application routes under applications directory (#5091)

* refactor(core): group the application routes under applications directory

group the application routes under applications directory

* refactor(core,schemas): refactor the application api guard

refactor the application api guard

* fix(schemas): fix application patch guard

fix application patch guard

* fix(test): fix ut

fix ut
This commit is contained in:
simeng-li 2023-12-12 14:54:49 +08:00 committed by GitHub
parent 630a8c52cf
commit a93a39aa1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 90 additions and 30 deletions

View file

@ -38,6 +38,7 @@ export const mockApplication: Application = {
idTokenTtl: 5000,
refreshTokenTtl: 6_000_000,
},
isThirdParty: false,
createdAt: 1_645_334_775_356,
};

View file

@ -10,7 +10,7 @@ import koaRoleRlsErrorHandler from '#src/middleware/koa-role-rls-error-handler.j
import assertThat from '#src/utils/assert-that.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';
import type { AuthedRouter, RouterInitArgs } from './types.js';
import type { AuthedRouter, RouterInitArgs } from '../types.js';
export default function applicationRoleRoutes<T extends AuthedRouter>(
...[router, { queries }]: RouterInitArgs<T>

View file

@ -76,6 +76,7 @@ describe('application route', () => {
const response = await applicationRequest
.post('/applications')
.send({ name, type, description });
expect(response.status).toEqual(200);
expect(response.body).toEqual({
...mockApplication,
@ -153,19 +154,26 @@ describe('application route', () => {
});
it('PATCH /applications/:applicationId expect to throw with invalid properties', async () => {
await expect(
applicationRequest.patch('/applications/doo').send({ type: 'node' })
).resolves.toHaveProperty('status', 400);
await expect(
applicationRequest.patch('/applications/doo').send({
customClientMetadata: {
...customClientMetadata,
corsAllowedOrigins: [''],
},
customClientMetadata: 'test',
})
).resolves.toHaveProperty('status', 400);
});
it('PATCH /applications/:applicationId should not allow update application secret, isThirdParty and type', async () => {
const response = await applicationRequest.patch('/applications/foo').send({
type: ApplicationType.Native,
isThirdParty: true,
secret: 'test',
});
expect(response.status).toEqual(200);
// Should not update the secret, isThirdParty and type
expect(response.body).toEqual(mockApplication);
});
it('PATCH /applications/:applicationId should save the formatted URIs as per RFC', async () => {
await expect(
applicationRequest.patch('/applications/foo').send({

View file

@ -2,9 +2,9 @@ import type { Role } from '@logto/schemas';
import {
demoAppApplicationId,
buildDemoAppDataForTenant,
Applications,
InternalRole,
ApplicationType,
applicationPatchGuard,
} from '@logto/schemas';
import { generateStandardId, generateStandardSecret } from '@logto/shared';
import { boolean, object, string, z } from 'zod';
@ -16,7 +16,9 @@ import { buildOidcClientMetadata } from '#src/oidc/utils.js';
import assertThat from '#src/utils/assert-that.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';
import type { AuthedRouter, RouterInitArgs } from './types.js';
import type { AuthedRouter, RouterInitArgs } from '../types.js';
import { applicationResponseGuard, applicationCreateGuard } from './types.js';
const includesInternalAdminRole = (roles: Readonly<Array<{ role: Role }>>) =>
roles.some(({ role: { name } }) => name === InternalRole.Admin);
@ -63,7 +65,7 @@ export default function applicationRoutes<T extends AuthedRouter>(
.or(applicationTypeGuard.transform((type) => [type]))
.optional(),
}),
response: z.array(Applications.guard),
response: z.array(applicationResponseGuard),
status: 200,
}),
async (ctx, next) => {
@ -103,11 +105,8 @@ export default function applicationRoutes<T extends AuthedRouter>(
router.post(
'/applications',
koaGuard({
body: Applications.createGuard
.omit({ id: true, createdAt: true })
.partial()
.merge(Applications.createGuard.pick({ name: true, type: true })),
response: Applications.guard,
body: applicationCreateGuard,
response: applicationResponseGuard,
status: [200, 422],
}),
async (ctx, next) => {
@ -134,7 +133,7 @@ export default function applicationRoutes<T extends AuthedRouter>(
'/applications/:id',
koaGuard({
params: object({ id: string().min(1) }),
response: Applications.guard.merge(z.object({ isAdmin: z.boolean() })),
response: applicationResponseGuard.merge(z.object({ isAdmin: z.boolean() })),
status: [200, 404],
}),
async (ctx, next) => {
@ -165,15 +164,12 @@ export default function applicationRoutes<T extends AuthedRouter>(
'/applications/:id',
koaGuard({
params: object({ id: string().min(1) }),
body: Applications.createGuard
.omit({ id: true, createdAt: true })
.deepPartial()
.merge(
object({
isAdmin: boolean().optional(),
})
),
response: Applications.guard,
body: applicationPatchGuard.deepPartial().merge(
object({
isAdmin: boolean().optional(),
})
),
response: applicationResponseGuard,
status: [200, 404, 422, 500],
}),
async (ctx, next) => {

View file

@ -0,0 +1,15 @@
import {
Applications,
applicationCreateGuard as originalApplicationCreateGuard,
} from '@logto/schemas';
import { EnvSet } from '#src/env-set/index.js';
// FIXME: @simeng-li Remove this guard once Logto as IdP is ready
export const applicationResponseGuard = EnvSet.values.isDevFeaturesEnabled
? Applications.guard
: Applications.guard.omit({ isThirdParty: true });
export const applicationCreateGuard = EnvSet.values.isDevFeaturesEnabled
? originalApplicationCreateGuard
: originalApplicationCreateGuard.omit({ isThirdParty: true });

View file

@ -11,8 +11,8 @@ import type TenantContext from '#src/tenants/TenantContext.js';
import koaAuth from '../middleware/koa-auth/index.js';
import adminUserRoutes from './admin-user/index.js';
import applicationRoleRoutes from './application-role.js';
import applicationRoutes from './application.js';
import applicationRoleRoutes from './applications/application-role.js';
import applicationRoutes from './applications/application.js';
import authnRoutes from './authn.js';
import connectorRoutes from './connector/index.js';
import customPhraseRoutes from './custom-phrase.js';

View file

@ -1,4 +1,3 @@
import { Applications } from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { tryThat } from '@silverhand/essentials';
import { object, string } from 'zod';
@ -8,6 +7,7 @@ import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';
import { applicationResponseGuard } from './applications/types.js';
import type { AuthedRouter, RouterInitArgs } from './types.js';
export default function roleApplicationRoutes<T extends AuthedRouter>(
@ -29,7 +29,7 @@ export default function roleApplicationRoutes<T extends AuthedRouter>(
koaPagination(),
koaGuard({
params: object({ id: string().min(1) }),
response: Applications.guard.array(),
response: applicationResponseGuard.array(),
status: [200, 204, 400, 404],
}),
async (ctx, next) => {

View file

@ -0,0 +1,20 @@
import { sql } from 'slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const alteration: AlterationScript = {
up: async (pool) => {
await pool.query(sql`
alter table applications add is_third_party boolean not null default false;
create index applications__is_third_party on applications (tenant_id, is_third_party);
`);
},
down: async (pool) => {
await pool.query(sql`
drop index applications__is_third_party;
alter table applications drop is_third_party;
`);
},
};
export default alteration;

View file

@ -27,6 +27,7 @@ export const buildDemoAppDataForTenant = (tenantId: string): Application => ({
type: ApplicationType.SPA,
oidcClientMetadata: { redirectUris: [], postLogoutRedirectUris: [] },
customClientMetadata: {},
isThirdParty: false,
createdAt: 0,
});

View file

@ -16,3 +16,18 @@ export const featuredApplicationGuard = Applications.guard.pick({
name: true,
type: true,
}) satisfies z.ZodType<FeaturedApplication>;
export const applicationCreateGuard = Applications.createGuard
.omit({
id: true,
createdAt: true,
secret: true,
tenantId: true,
})
.partial()
.merge(Applications.createGuard.pick({ name: true, type: true }));
export const applicationPatchGuard = applicationCreateGuard.partial().omit({
type: true,
isThirdParty: true,
});

View file

@ -12,9 +12,13 @@ create table applications (
type application_type not null,
oidc_client_metadata jsonb /* @use OidcClientMetadata */ not null,
custom_client_metadata jsonb /* @use CustomClientMetadata */ not null default '{}'::jsonb,
is_third_party boolean not null default false,
created_at timestamptz not null default(now()),
primary key (id)
);
create index applications__id
on applications (tenant_id, id);
create index applications__is_third_party
on applications (tenant_id, is_third_party);