@prosopo/api-express-router 3.1.98 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +7 -9
  2. package/.turbo/turbo-build$colon$tsc.log +21 -24
  3. package/.turbo/turbo-build.log +7 -12
  4. package/CHANGELOG.md +54 -0
  5. package/LICENSE +201 -0
  6. package/dist/cjs/index.cjs +2 -0
  7. package/dist/cjs/middlewares/authMiddleware.cjs +25 -14
  8. package/dist/cjs/middlewares/jwtReplayGuard.cjs +28 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/middlewares/authMiddleware.d.ts +7 -1
  14. package/dist/middlewares/authMiddleware.d.ts.map +1 -1
  15. package/dist/middlewares/authMiddleware.js +26 -15
  16. package/dist/middlewares/authMiddleware.js.map +1 -1
  17. package/dist/middlewares/jwtReplayGuard.d.ts +5 -0
  18. package/dist/middlewares/jwtReplayGuard.d.ts.map +1 -0
  19. package/dist/middlewares/jwtReplayGuard.js +28 -0
  20. package/dist/middlewares/jwtReplayGuard.js.map +1 -0
  21. package/dist/tests/apiExpressRouter.test-d.js +1 -1
  22. package/dist/tests/apiExpressRouter.test-d.js.map +1 -1
  23. package/dist/tests/unit/middlewares/authMiddleware.unit.test.js +94 -8
  24. package/dist/tests/unit/middlewares/authMiddleware.unit.test.js.map +1 -1
  25. package/dist/tests/unit/middlewares/jwtReplayGuard.unit.test.d.ts +2 -0
  26. package/dist/tests/unit/middlewares/jwtReplayGuard.unit.test.d.ts.map +1 -0
  27. package/dist/tests/unit/middlewares/jwtReplayGuard.unit.test.js +36 -0
  28. package/dist/tests/unit/middlewares/jwtReplayGuard.unit.test.js.map +1 -0
  29. package/package.json +62 -59
  30. package/src/apiExpressRouterFactory.ts +0 -68
  31. package/src/endpointAdapter/apiExpressDefaultEndpointAdapter.ts +0 -92
  32. package/src/endpointAdapter/apiExpressEndpointAdapter.ts +0 -28
  33. package/src/errorHandler.ts +0 -33
  34. package/src/index.ts +0 -37
  35. package/src/middlewares/authMiddleware.ts +0 -93
  36. package/src/middlewares/requestLoggerMiddleware.ts +0 -104
  37. package/src/tests/apiExpressRouter.test-d.ts +0 -190
  38. package/src/tests/unit/apiExpressDefaultEndpointAdapter.unit.test.ts +0 -414
  39. package/src/tests/unit/apiExpressRouterFactory.unit.test.ts +0 -215
  40. package/src/tests/unit/errorHandler.unit.test.ts +0 -190
  41. package/src/tests/unit/index.unit.test.ts +0 -95
  42. package/src/tests/unit/middlewares/authMiddleware.unit.test.ts +0 -342
  43. package/src/tests/unit/middlewares/requestLoggerMiddleware.unit.test.ts +0 -368
  44. package/src/tests/unit/testDoubles.ts +0 -39
  45. package/tsconfig.cjs.json +0 -37
  46. package/tsconfig.json +0 -44
  47. package/tsconfig.tsbuildinfo +0 -1
  48. package/tsconfig.types.json +0 -9
@@ -1,92 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { ApiEndpoint } from "@prosopo/api-route";
16
- import {
17
- ProsopoApiError,
18
- ProsopoBaseError,
19
- unwrapError,
20
- } from "@prosopo/common";
21
- import type { LogLevel } from "@prosopo/logger";
22
- import { stringifyBigInts } from "@prosopo/util";
23
- import type { NextFunction, Request, Response } from "express";
24
- import type { ZodType } from "zod";
25
- import type { ApiExpressEndpointAdapter } from "./apiExpressEndpointAdapter.js";
26
-
27
- class ApiExpressDefaultEndpointAdapter implements ApiExpressEndpointAdapter {
28
- public constructor(
29
- private readonly logLevel: LogLevel,
30
- private readonly errorStatusCode: number,
31
- ) {}
32
-
33
- public async handleRequest(
34
- endpoint: ApiEndpoint<ZodType | undefined>,
35
- request: Request,
36
- response: Response,
37
- next: NextFunction,
38
- ): Promise<void> {
39
- let args: unknown;
40
- try {
41
- args = endpoint.getRequestArgsSchema()?.parse(request.body);
42
- } catch (error) {
43
- return next(
44
- new ProsopoApiError("API.PARSE_ERROR", {
45
- context: { code: 400, error: error },
46
- }),
47
- );
48
- }
49
-
50
- try {
51
- const apiEndpointResponse = await endpoint.processRequest(
52
- args,
53
- request.logger,
54
- );
55
-
56
- // otherwise .json() will throw "TypeError: Do not know how to serialize a BigInt" as uses JSON.stringify
57
- const responseObject = stringifyBigInts(apiEndpointResponse);
58
-
59
- response.json(responseObject);
60
- } catch (error) {
61
- request.logger.error(() => ({
62
- err: error,
63
- }));
64
-
65
- // Errors that already carry an explicit HTTP status code (e.g. a 400
66
- // admin auth/validation error) should surface that code with the
67
- // standard `{ error: ... }` JSON envelope. Everything else — base
68
- // errors without a status code, or non-Prosopo errors — is treated as
69
- // an unexpected failure and mapped to this adapter's configured
70
- // errorStatusCode. In both cases the envelope is produced by
71
- // `unwrapError` so the message/key stay consistent and localised.
72
- const responseError =
73
- error instanceof ProsopoApiError ||
74
- (error instanceof ProsopoBaseError &&
75
- typeof error.context?.code === "number")
76
- ? error
77
- : new ProsopoApiError("API.UNKNOWN", {
78
- context: { code: this.errorStatusCode },
79
- silent: true,
80
- });
81
-
82
- const { code, statusMessage, jsonError } = unwrapError(
83
- responseError,
84
- request.i18n,
85
- );
86
- response.statusMessage = statusMessage;
87
- response.status(code).json({ error: jsonError });
88
- }
89
- }
90
- }
91
-
92
- export { ApiExpressDefaultEndpointAdapter };
@@ -1,28 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { ApiEndpoint } from "@prosopo/api-route";
16
- import type { NextFunction, Request, Response } from "express";
17
- import type { ZodType } from "zod";
18
-
19
- interface ApiExpressEndpointAdapter {
20
- handleRequest(
21
- endpoint: ApiEndpoint<ZodType | undefined>,
22
- request: Request,
23
- response: Response,
24
- next: NextFunction,
25
- ): Promise<void>;
26
- }
27
-
28
- export type { ApiExpressEndpointAdapter };
@@ -1,33 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- // We need the unused params to make express recognise this function as an error handler
16
-
17
- import { type ProsopoApiError, unwrapError } from "@prosopo/common";
18
- import type { NextFunction, Request, Response } from "express";
19
- import type { ZodError } from "zod";
20
-
21
- export const handleErrors = (
22
- err: ProsopoApiError | SyntaxError | ZodError,
23
- request: Request,
24
- response: Response,
25
- next: NextFunction,
26
- ) => {
27
- const { code, statusMessage, jsonError } = unwrapError(err, request.i18n);
28
- response.statusMessage = statusMessage;
29
- response.set("content-type", "application/json");
30
- response.status(code);
31
- response.send({ error: jsonError });
32
- response.end();
33
- };
package/src/index.ts DELETED
@@ -1,37 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { LogLevel } from "@prosopo/logger";
16
- import { ApiExpressRouterFactory } from "./apiExpressRouterFactory.js";
17
- import { ApiExpressDefaultEndpointAdapter } from "./endpointAdapter/apiExpressDefaultEndpointAdapter.js";
18
- import type { ApiExpressEndpointAdapter } from "./endpointAdapter/apiExpressEndpointAdapter.js";
19
-
20
- const apiExpressRouterFactory = new ApiExpressRouterFactory();
21
-
22
- const createApiExpressDefaultEndpointAdapter = (
23
- logLevel: LogLevel,
24
- errorStatusCode = 500,
25
- ): ApiExpressEndpointAdapter => {
26
- return new ApiExpressDefaultEndpointAdapter(logLevel, errorStatusCode);
27
- };
28
-
29
- export {
30
- apiExpressRouterFactory,
31
- createApiExpressDefaultEndpointAdapter,
32
- type ApiExpressEndpointAdapter,
33
- };
34
-
35
- export * from "./errorHandler.js";
36
- export * from "./middlewares/authMiddleware.js";
37
- export * from "./middlewares/requestLoggerMiddleware.js";
@@ -1,93 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import { hexToU8a } from "@polkadot/util";
16
- import { ProsopoApiError, ProsopoEnvError } from "@prosopo/common";
17
- import type { KeyringPair } from "@prosopo/types";
18
- import type { JWT } from "@prosopo/util-crypto";
19
- import type { NextFunction, Request, Response } from "express";
20
-
21
- export const authMiddleware = (
22
- pair: KeyringPair | undefined,
23
- authAccount?: KeyringPair | undefined,
24
- ) => {
25
- return async (req: Request, res: Response, next: NextFunction) => {
26
- try {
27
- const jwt = extractJWT(req);
28
-
29
- let error: ProsopoApiError | undefined;
30
-
31
- if (authAccount?.jwtVerify(jwt).isValid) {
32
- next();
33
- return;
34
- }
35
-
36
- if (pair?.jwtVerify(jwt).isValid) {
37
- next();
38
- return;
39
- }
40
-
41
- res.status(401).json({
42
- error: new ProsopoEnvError(error || "API.UNAUTHORIZED", {
43
- context: { i18n: req.i18n, code: 401 },
44
- }),
45
- });
46
- return;
47
- } catch (err) {
48
- req.logger.error(() => ({ err, msg: "Auth Middleware Error" }));
49
- res.status(401).json({ error: "Unauthorized", message: err });
50
- return;
51
- }
52
- };
53
- };
54
-
55
- const extractJWT = (req: Request) => {
56
- const authHeader = req.headers.Authorization || req.headers.authorization;
57
-
58
- if (!authHeader || typeof authHeader !== "string") {
59
- throw new ProsopoApiError("GENERAL.MISSING_AUTH_HEADER", {
60
- context: { error: "Missing Authorization header", code: 401 },
61
- });
62
- }
63
-
64
- const jwt = authHeader.replace("Bearer ", "");
65
-
66
- if (!jwt) {
67
- throw new ProsopoApiError("GENERAL.INVALID_JWT", {
68
- context: { error: "Missing JWT", code: 400 },
69
- });
70
- }
71
-
72
- return jwt as JWT;
73
- };
74
-
75
- export const verifySignature = (
76
- signature: string,
77
- message: string,
78
- pair: KeyringPair,
79
- ) => {
80
- const u8Sig = hexToU8a(signature);
81
-
82
- if (!pair.verify(message, u8Sig, pair.publicKey)) {
83
- throw new ProsopoApiError("GENERAL.INVALID_SIGNATURE", {
84
- context: {
85
- error: "Signature verification failed",
86
- code: 401,
87
- account: pair.address,
88
- message,
89
- signature,
90
- },
91
- });
92
- }
93
- };
@@ -1,104 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { ProviderEnvironment } from "@prosopo/env";
16
- import { getLogger, parseLogLevel } from "@prosopo/logger";
17
- import type { NextFunction, Request, Response } from "express";
18
- import { v4 as uuidv4 } from "uuid";
19
-
20
- const getHeaderValue = (
21
- req: Request,
22
- headerName: string,
23
- ): string | undefined => {
24
- if (headerName in req.headers && req.headers[headerName]) {
25
- return req.headers[headerName].toString();
26
- }
27
- return undefined;
28
- };
29
-
30
- const HEALTH_CHECK_PATHS = new Set(["/healthz", "/health", "/readyz"]);
31
-
32
- export function requestLoggerMiddleware(env: ProviderEnvironment) {
33
- return (req: Request, res: Response, next: NextFunction) => {
34
- // Honour an inbound `x-request-id` (Caddy/upstream proxy assigns one and
35
- // we want the same value to link Caddy access logs to Node app logs).
36
- // Fall back to `e-<uuid>` when the upstream didn't set one.
37
- const requestId =
38
- (req.headers["x-request-id"] as string) || `e-${uuidv4()}`;
39
- const user = getHeaderValue(req, "prosopo-user");
40
- const siteKey = getHeaderValue(req, "prosopo-site-key");
41
- const sessionId = req.body?.sessionId ? req.body.sessionId : undefined;
42
-
43
- const logger = getLogger(
44
- parseLogLevel(env.config.logLevel),
45
- "provider:request",
46
- ).with({
47
- requestId,
48
- ...(user ? { user } : {}),
49
- ...(siteKey ? { siteKey } : {}),
50
- ...(sessionId ? { sessionId } : {}),
51
- });
52
-
53
- req.logger = logger;
54
- req.requestId = requestId;
55
-
56
- // Mirror the requestId back to the client so downstream systems can
57
- // correlate their own logs to ours.
58
- res.setHeader("x-request-id", requestId);
59
-
60
- // Skip request/response envelope logging for high-volume health probes
61
- // so they don't drown out useful captcha-endpoint entries.
62
- if (HEALTH_CHECK_PATHS.has(req.path)) {
63
- next();
64
- return;
65
- }
66
-
67
- // Emit a single request-received line so every endpoint has an entry
68
- // log in OpenObserve even when the handler itself doesn't log. Kept at
69
- // info level; drop to debug if the volume becomes a problem.
70
- const startNs = process.hrtime.bigint();
71
- logger.info(() => ({
72
- msg: "Request received",
73
- data: {
74
- method: req.method,
75
- path: req.path,
76
- },
77
- }));
78
-
79
- // Log a response-finished line. `finish` fires when the response was
80
- // sent successfully; `close` fires when the client disconnects before
81
- // the response was fully sent. Guard against both firing (once() would
82
- // only cover a single event source).
83
- let finished = false;
84
- const emitFinish = (outcome: "finish" | "close") => {
85
- if (finished) return;
86
- finished = true;
87
- const durationMs = Number(process.hrtime.bigint() - startNs) / 1e6;
88
- logger.info(() => ({
89
- msg: "Response sent",
90
- data: {
91
- method: req.method,
92
- path: req.path,
93
- status: res.statusCode,
94
- durationMs,
95
- outcome,
96
- },
97
- }));
98
- };
99
- res.on("finish", () => emitFinish("finish"));
100
- res.on("close", () => emitFinish("close"));
101
-
102
- next();
103
- };
104
- }
@@ -1,190 +0,0 @@
1
- // Copyright 2021-2026 Prosopo (UK) Ltd.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { ApiEndpoint, ApiRoutesProvider } from "@prosopo/api-route";
16
- import type { LogLevel } from "@prosopo/logger";
17
- import type { KeyringPair } from "@prosopo/types";
18
- import type {
19
- NextFunction,
20
- Request,
21
- RequestHandler,
22
- Response,
23
- Router,
24
- } from "express";
25
- import { assertType, describe, expectTypeOf, test } from "vitest";
26
- import type { ZodType } from "zod";
27
- import type * as barrel from "../index.js";
28
- import {
29
- type ApiExpressEndpointAdapter,
30
- apiExpressRouterFactory,
31
- authMiddleware,
32
- createApiExpressDefaultEndpointAdapter,
33
- handleErrors,
34
- requestLoggerMiddleware,
35
- verifySignature,
36
- } from "../index.js";
37
-
38
- /**
39
- * These pin the package's public type surface. Consumers wire routers and
40
- * middleware from this barrel alone, so a widened parameter or a dropped export
41
- * is a break they would only find at their own call sites.
42
- */
43
-
44
- describe("the barrel's shape", () => {
45
- test("exports exactly the intended names", () => {
46
- expectTypeOf<keyof typeof barrel>().toEqualTypeOf<
47
- | "apiExpressRouterFactory"
48
- | "createApiExpressDefaultEndpointAdapter"
49
- | "handleErrors"
50
- | "authMiddleware"
51
- | "verifySignature"
52
- | "requestLoggerMiddleware"
53
- >();
54
- });
55
- });
56
-
57
- describe("createApiExpressDefaultEndpointAdapter", () => {
58
- test("takes a log level and an optional status code", () => {
59
- expectTypeOf(
60
- createApiExpressDefaultEndpointAdapter,
61
- ).parameters.toEqualTypeOf<
62
- [logLevel: LogLevel, errorStatusCode?: number]
63
- >();
64
- });
65
-
66
- test("returns the interface, not the concrete class", () => {
67
- // Callers depend on the interface so an alternative adapter can be
68
- // substituted without a cast.
69
- expectTypeOf(
70
- createApiExpressDefaultEndpointAdapter,
71
- ).returns.toEqualTypeOf<ApiExpressEndpointAdapter>();
72
- });
73
-
74
- test("rejects a log level that is not one of the known levels", () => {
75
- // @ts-expect-error - an arbitrary string is not a LogLevel
76
- createApiExpressDefaultEndpointAdapter("chatty");
77
- });
78
-
79
- test("rejects a non-numeric status code", () => {
80
- // @ts-expect-error - the status code is a number
81
- createApiExpressDefaultEndpointAdapter("info", "500");
82
- });
83
- });
84
-
85
- describe("the router factory", () => {
86
- test("takes a routes provider and an adapter, and returns a Router", () => {
87
- expectTypeOf(apiExpressRouterFactory.createRouter).toEqualTypeOf<
88
- (
89
- apiRoutesProvider: ApiRoutesProvider,
90
- apiExpressEndpointAdapter: ApiExpressEndpointAdapter,
91
- ) => Router
92
- >();
93
- });
94
-
95
- test("rejects a bare routes object in place of a provider", () => {
96
- const adapter: ApiExpressEndpointAdapter = {
97
- handleRequest: async () => undefined,
98
- };
99
- // @ts-expect-error - the factory wants a provider, not the routes it returns
100
- apiExpressRouterFactory.createRouter({}, adapter);
101
- });
102
- });
103
-
104
- describe("the endpoint adapter interface", () => {
105
- test("takes the endpoint and the express trio and resolves to void", () => {
106
- expectTypeOf<ApiExpressEndpointAdapter["handleRequest"]>().toEqualTypeOf<
107
- (
108
- endpoint: ApiEndpoint<ZodType | undefined>,
109
- request: Request,
110
- response: Response,
111
- next: NextFunction,
112
- ) => Promise<void>
113
- >();
114
- });
115
-
116
- test("is satisfied by any object with a matching handleRequest", () => {
117
- // Structural, deliberately: a consumer can supply its own adapter
118
- // without importing or extending the default class.
119
- assertType<ApiExpressEndpointAdapter>({
120
- handleRequest: async (): Promise<void> => undefined,
121
- });
122
- });
123
- });
124
-
125
- describe("the middleware", () => {
126
- test("authMiddleware takes an optional pair and an optional auth account", () => {
127
- expectTypeOf(authMiddleware).parameters.toEqualTypeOf<
128
- [pair: KeyringPair | undefined, authAccount?: KeyringPair | undefined]
129
- >();
130
- });
131
-
132
- test("authMiddleware returns an async express handler", () => {
133
- expectTypeOf(authMiddleware).returns.toEqualTypeOf<
134
- (req: Request, res: Response, next: NextFunction) => Promise<void>
135
- >();
136
- });
137
-
138
- test("authMiddleware still requires the first argument to be passed", () => {
139
- // It is `KeyringPair | undefined` rather than optional, so a provider
140
- // with no key has to say so explicitly instead of forgetting to wire it.
141
- // @ts-expect-error - the first parameter is required
142
- authMiddleware();
143
- });
144
-
145
- test("requestLoggerMiddleware returns a synchronous express handler", () => {
146
- expectTypeOf(requestLoggerMiddleware).returns.toEqualTypeOf<
147
- (req: Request, res: Response, next: NextFunction) => void
148
- >();
149
- });
150
-
151
- test("the handler each factory builds is usable as express middleware", () => {
152
- // The exports are factories, so it is what they return — not the
153
- // factories themselves — that gets mounted with app.use().
154
- assertType<RequestHandler>(
155
- requestLoggerMiddleware(
156
- {} as Parameters<typeof requestLoggerMiddleware>[0],
157
- ),
158
- );
159
- assertType<RequestHandler>(authMiddleware(undefined));
160
- });
161
-
162
- test("verifySignature takes a signature, a message and a pair, and returns void", () => {
163
- expectTypeOf(verifySignature).parameters.toEqualTypeOf<
164
- [signature: string, message: string, pair: KeyringPair]
165
- >();
166
- expectTypeOf(verifySignature).returns.toEqualTypeOf<void>();
167
- });
168
-
169
- test("verifySignature rejects a byte-array signature", () => {
170
- // The hex string is decoded internally; passing bytes would be decoded
171
- // a second time and silently verify the wrong value.
172
- // @ts-expect-error - the signature is a hex string
173
- verifySignature(new Uint8Array([1]), "msg", {} as KeyringPair);
174
- });
175
- });
176
-
177
- describe("the error handler", () => {
178
- test("takes an error first, which is what makes express treat it as one", () => {
179
- // A three-parameter handler would be mounted as ordinary middleware and
180
- // never see an error at all.
181
- expectTypeOf(handleErrors).parameters.toMatchTypeOf<
182
- [unknown, Request, Response, NextFunction]
183
- >();
184
- });
185
-
186
- test("rejects being mounted as an ordinary handler", () => {
187
- // @ts-expect-error - the first parameter is an error, not a request
188
- assertType<RequestHandler>(handleErrors);
189
- });
190
- });