Skip to content

Commit

Permalink
feat: run middleware after auth middleware (#119)
Browse files Browse the repository at this point in the history
* wip globalAfterAuthMiddlewares

* add test for middleswareAfterAuth

* fix

* lint fix

* format

* yoda

* as const

* yarn lock

* format

---------

Co-authored-by: Severin Ibarluzea <seveibar@gmail.com>
  • Loading branch information
itelo and seveibar authored Oct 10, 2023
1 parent f5b748c commit 8caa88d
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 13 deletions.
1 change: 1 addition & 0 deletions apps/example-todo-app/lib/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./with-auth-token"
export * from "./with-route-spec"
export * from "./with-global-middeware-after-auth"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Middleware } from "nextlove"

export const withGlobalMiddlewareAfterAuth: Middleware<{
auth: {
authorized_by: "auth_token"
seam: "withGlobalMiddlewareAfterAuth"
}
}> = (next) => async (req, res) => {
req.auth = {
...req.auth,
seam: "withGlobalMiddlewareAfterAuth",
}

return next(req, res)
}

export default withGlobalMiddlewareAfterAuth
8 changes: 7 additions & 1 deletion apps/example-todo-app/lib/middlewares/with-route-spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createWithRouteSpec, QueryArrayFormats } from "nextlove"
import { createWithRouteSpec, Middleware, QueryArrayFormats } from "nextlove"
import { withAuthToken } from "./with-auth-token"
export { checkRouteSpec } from "nextlove"
import * as ZT from "lib/zod"
import withGlobalMiddlewareAfterAuth from "./with-global-middeware-after-auth"

const defaultRouteSpec = {
authMiddlewareMap: { auth_token: withAuthToken },
Expand All @@ -17,6 +18,11 @@ const defaultRouteSpec = {

export const withRouteSpec = createWithRouteSpec(defaultRouteSpec)

export const withRouteSpecWithGlobalMiddlewareAfterAuth = createWithRouteSpec({
globalMiddlewaresAfterAuth: [withGlobalMiddlewareAfterAuth],
...defaultRouteSpec,
} as const)

export const withRouteSpecSupportedArrayFormats = (
supportedArrayFormats: QueryArrayFormats
) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
checkRouteSpec,
withRouteSpecWithGlobalMiddlewareAfterAuth,
} from "lib/middlewares"
import { z } from "zod"
import { v4 as uuidv4 } from "uuid"

export const jsonBody = z.object({
id: z.string().uuid().optional().default(uuidv4()),
title: z.string(),
completed: z.boolean().optional().default(false),
})

export const route_spec = checkRouteSpec({
methods: ["POST"],
auth: "auth_token",
jsonBody,
jsonResponse: z.object({
ok: z.boolean(),
auth: z.object({
authorized_by: z.literal("auth_token"),
seam: z.literal("withGlobalMiddlewareAfterAuth"),
}),
}),
})

export default withRouteSpecWithGlobalMiddlewareAfterAuth(route_spec)(
async (req, res) => {
return res.status(200).json({ ok: true, auth: req.auth })
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import test from "ava"
import getTestServer from "tests/fixtures/get-test-server"

test("POST /todo/add-with-global-middeware-after-auth", async (t) => {
const { axios } = await getTestServer(t)

axios.defaults.headers.common.Authorization = `Bearer auth_token`

const successfulRes = await axios
.post("/todo/add-with-global-middeware-after-auth", { title: "Todo Title" })
.catch((err) => err)
t.is(successfulRes.status, 200)
t.is(successfulRes.data.ok, true)
t.is(successfulRes.data.auth.authorized_by, "auth_token")
t.is(successfulRes.data.auth.seam, "withGlobalMiddlewareAfterAuth")
})
19 changes: 13 additions & 6 deletions packages/nextlove/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ export type QueryArrayFormats = readonly QueryArrayFormat[]

export interface SetupParams<
AuthMW extends AuthMiddlewares = AuthMiddlewares,
GlobalMW extends Middleware<any, any>[] = any[]
GlobalMW extends Middleware<any, any>[] = any[],
GlobalMWAfterAuth extends Middleware<any, any>[] = any[]
> {
authMiddlewareMap: AuthMW
globalMiddlewares: GlobalMW
globalMiddlewaresAfterAuth?: GlobalMWAfterAuth
exceptionHandlingMiddleware?: ((next: Function) => Function) | null

// These improve OpenAPI generation
Expand Down Expand Up @@ -110,17 +112,22 @@ type ErrorNextApiResponseMethods = {
json: Send<any>
}

export type RouteFunction<
SP extends SetupParams<AuthMiddlewares>,
RS extends RouteSpec
> = (
export type RouteFunction<SP extends SetupParams, RS extends RouteSpec> = (
req: (SP["authMiddlewareMap"] &
typeof defaultMiddlewareMap)[RS["auth"]] extends Middleware<
infer AuthMWOut,
any
>
? Omit<NextApiRequest, "query" | "body"> &
AuthMWOut &
MiddlewareChainOutput<
SP["globalMiddlewaresAfterAuth"] extends readonly Middleware<
any,
any
>[]
? SP["globalMiddlewaresAfterAuth"]
: []
> &
MiddlewareChainOutput<
RS["middlewares"] extends readonly Middleware<any, any>[]
? [...SP["globalMiddlewares"], ...RS["middlewares"]]
Expand Down Expand Up @@ -149,7 +156,7 @@ export type RouteFunction<
) => Promise<void>

export type CreateWithRouteSpecFunction = <
SP extends SetupParams<AuthMiddlewares, any>
SP extends SetupParams<AuthMiddlewares, any, any>
>(
setupParams: SP
) => <RS extends RouteSpec<string, any, any, any, any, any, z.ZodTypeAny, any>>(
Expand Down
2 changes: 2 additions & 0 deletions packages/nextlove/src/with-route-spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const createWithRouteSpec: CreateWithRouteSpecFunction = ((
const {
authMiddlewareMap = {},
globalMiddlewares = [],
globalMiddlewaresAfterAuth = [],
shouldValidateResponses,
shouldValidateGetRequestBody = true,
exceptionHandlingMiddleware = withExceptionHandling({
Expand Down Expand Up @@ -97,6 +98,7 @@ export const createWithRouteSpec: CreateWithRouteSpecFunction = ((
: []) as [any]),
...((globalMiddlewares || []) as []),
auth_middleware,
...((globalMiddlewaresAfterAuth || []) as []),
...((spec.middlewares || []) as []),
withMethods(spec.methods),
withValidation({
Expand Down
52 changes: 46 additions & 6 deletions packages/nextlove/tests/route-spec-types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { MiddlewareChainOutput } from "../src/types"
import {
checkRouteSpec,
createWithRouteSpec,
Middleware,
RouteSpec,
} from "../src"
import { checkRouteSpec, createWithRouteSpec, Middleware } from "../src"
import { expectTypeOf } from "expect-type"
import { z } from "zod"

Expand Down Expand Up @@ -227,3 +222,48 @@ export const myRoute9 = withRouteSpec(myRoute9Spec)(async (req, res) => {
const errorApiResponse400 = res.status(400)
expectTypeOf(errorApiResponse400.json).toMatchTypeOf<(body: any) => void>()
})

export const myRoute10 = createWithRouteSpec({
authMiddlewareMap: {},
globalMiddlewaresAfterAuth: [
null as any as Middleware<{
good: true
}>,
],
apiName: "",
globalMiddlewares: [],
productionServerUrl: "",
} as const)({
auth: "none",
methods: ["GET"],
} as const)(async (req, res) => {
expectTypeOf(req.good).toMatchTypeOf<true>()
})

export const myRoute11 = createWithRouteSpec({
authMiddlewareMap: {
// seam: (next) => (req, res) => {},
},
globalMiddlewaresAfterAuth: [
null as any as Middleware<{
auth: {
authorized_by: "auth_token"
seam: "withGlobalMiddlewareAfterAuth"
}
}>,
],
apiName: "",
globalMiddlewares: [
((next) => async (req, res) => {
next(req, res)
}) as any as Middleware<{
gmw: true
}>,
],
productionServerUrl: "",
} as const)({
auth: "none",
methods: ["GET"],
} as const)(async (req, res) => {
expectTypeOf(req.auth.seam).toMatchTypeOf<"withGlobalMiddlewareAfterAuth">()
})

0 comments on commit 8caa88d

Please sign in to comment.