0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

feat(core): add 401,403 status guard to all management api routes (#3831)

add 401,403 status guard to all management api routes
This commit is contained in:
simeng-li 2023-05-15 15:36:23 +08:00 committed by GitHub
parent beb6ebad50
commit e62b0bae5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 26 deletions

View file

@ -5,7 +5,7 @@ import type { Optional } from '@silverhand/essentials';
import type { JWK } from 'jose';
import { createLocalJWKSet, jwtVerify } from 'jose';
import type { MiddlewareType, Request } from 'koa';
import type { IRouterParamContext } from 'koa-router';
import type { IMiddleware, IRouterParamContext } from 'koa-router';
import { z } from 'zod';
import { EnvSet } from '#src/env-set/index.js';
@ -103,11 +103,17 @@ export const verifyBearerTokenFromRequest = async (
}
};
export const isKoaAuthMiddleware = <Type extends IMiddleware>(function_: Type) =>
function_.name === 'authMiddleware';
export default function koaAuth<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
envSet: EnvSet,
audience: string
): MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> {
return async (ctx, next) => {
const authMiddleware: MiddlewareType<StateT, WithAuthContext<ContextT>, ResponseBodyT> = async (
ctx,
next
) => {
const { sub, clientId, scopes } = await verifyBearerTokenFromRequest(
envSet,
ctx.request,
@ -126,4 +132,6 @@ export default function koaAuth<StateT, ContextT extends IRouterParamContext, Re
return next();
};
return authMiddleware;
}

View file

@ -42,7 +42,7 @@ export default function roleScopeRoutes<T extends AuthedRouter>(
koaGuard({
params: object({ id: string().min(1) }),
response: scopeResponseGuard.array(),
status: [200, 404],
status: [200, 400, 404],
}),
async (ctx, next) => {
const {

View file

@ -4,6 +4,7 @@ import type Router from 'koa-router';
import type { OpenAPIV3 } from 'openapi-types';
import { ZodObject, ZodOptional } from 'zod';
import { isKoaAuthMiddleware } from '#src/middleware/koa-auth/index.js';
import type { WithGuardConfig } from '#src/middleware/koa-guard.js';
import { isGuardMiddleware } from '#src/middleware/koa-guard.js';
import { fallbackDefaultPageSize, isPaginationMiddleware } from '#src/middleware/koa-pagination.js';
@ -77,7 +78,11 @@ const buildTag = (path: string) => {
return toTitle(root ?? 'General');
};
const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.OperationObject => {
const buildOperation = (
stack: IMiddleware[],
path: string,
isAuthGuarded: boolean
): OpenAPIV3.OperationObject => {
const guard = stack.find((function_): function_ is WithGuardConfig<IMiddleware> =>
isGuardMiddleware(function_)
);
@ -101,9 +106,9 @@ const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.Operation
const hasInputGuard = Boolean(params ?? query ?? body);
const responses: OpenAPIV3.ResponsesObject = Object.fromEntries(
deduplicate(conditionalArray(status ?? 200, hasInputGuard && 400)).map<
[number, OpenAPIV3.ResponseObject]
>((status) => {
deduplicate(
conditionalArray(status ?? 200, hasInputGuard && 400, isAuthGuarded && [401, 403])
).map<[number, OpenAPIV3.ResponseObject]>((status) => {
const description = codeToMessage[status];
if (!description) {
@ -136,6 +141,11 @@ const buildOperation = (stack: IMiddleware[], path: string): OpenAPIV3.Operation
};
};
const isManagementApiRouter = ({ stack }: Router) =>
stack
.filter(({ path }) => !path.includes('.*'))
.some(({ stack }) => stack.some((function_) => isKoaAuthMiddleware(function_)));
// Keep using `any` to accept various custom context types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export default function swaggerRoutes<T extends AnonymousRouter, R extends Router<unknown, any>>(
@ -143,26 +153,30 @@ export default function swaggerRoutes<T extends AnonymousRouter, R extends Route
allRouters: R[]
) {
router.get('/swagger.json', async (ctx, next) => {
const routes = allRouters.flatMap<RouteObject>((router) =>
router.stack
// Filter out universal routes (mostly like a proxy route to withtyped)
.filter(({ path }) => !path.includes('.*'))
.flatMap<RouteObject>(({ path: routerPath, stack, methods }) =>
methods
.map((method) => method.toLowerCase())
// There is no need to show the HEAD method.
.filter((method): method is OpenAPIV3.HttpMethods => method !== 'head')
.map((httpMethod) => {
const path = `/api${routerPath}`;
const routes = allRouters.flatMap<RouteObject>((router) => {
const isAuthGuarded = isManagementApiRouter(router);
return {
path,
method: httpMethod,
operation: buildOperation(stack, routerPath),
};
})
)
);
return (
router.stack
// Filter out universal routes (mostly like a proxy route to withtyped)
.filter(({ path }) => !path.includes('.*'))
.flatMap<RouteObject>(({ path: routerPath, stack, methods, name }) =>
methods
.map((method) => method.toLowerCase())
// There is no need to show the HEAD method.
.filter((method): method is OpenAPIV3.HttpMethods => method !== 'head')
.map((httpMethod) => {
const path = `/api${routerPath}`;
return {
path,
method: httpMethod,
operation: buildOperation(stack, routerPath, isAuthGuarded),
};
})
)
);
});
const pathMap = new Map<string, MethodMap>();