@darthcav/ts-http-server 0.5.0 → 0.6.0
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.
- package/README.md +58 -8
- package/dist/__tests__/defaultPlugins.test.js +47 -2
- package/dist/__tests__/defaultPlugins.test.js.map +1 -1
- package/dist/__tests__/defaultRoutes.test.js +226 -1
- package/dist/__tests__/defaultRoutes.test.js.map +1 -1
- package/dist/__tests__/keycloak.test.d.ts +2 -0
- package/dist/__tests__/keycloak.test.d.ts.map +1 -0
- package/dist/__tests__/keycloak.test.js +105 -0
- package/dist/__tests__/keycloak.test.js.map +1 -0
- package/dist/__tests__/launcher.test.js +34 -0
- package/dist/__tests__/launcher.test.js.map +1 -1
- package/dist/auth/keycloak.d.ts +17 -0
- package/dist/auth/keycloak.d.ts.map +1 -0
- package/dist/auth/keycloak.js +35 -0
- package/dist/auth/keycloak.js.map +1 -0
- package/dist/defaults/defaultPlugins.d.ts +12 -7
- package/dist/defaults/defaultPlugins.d.ts.map +1 -1
- package/dist/defaults/defaultPlugins.js +109 -2
- package/dist/defaults/defaultPlugins.js.map +1 -1
- package/dist/defaults/defaultRoutes.d.ts +5 -0
- package/dist/defaults/defaultRoutes.d.ts.map +1 -1
- package/dist/defaults/defaultRoutes.js +30 -0
- package/dist/defaults/defaultRoutes.js.map +1 -1
- package/dist/hooks/authPreHandler.d.ts +19 -0
- package/dist/hooks/authPreHandler.d.ts.map +1 -0
- package/dist/hooks/authPreHandler.js +32 -0
- package/dist/hooks/authPreHandler.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts +2 -2
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +7 -2
- package/dist/launcher.js.map +1 -1
- package/dist/start.js +34 -5
- package/dist/start.js.map +1 -1
- package/dist/types.d.ts +64 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -3
- package/src/auth/keycloak.ts +43 -0
- package/src/defaults/defaultPlugins.ts +141 -7
- package/src/defaults/defaultRoutes.ts +30 -1
- package/src/hooks/authPreHandler.ts +41 -0
- package/src/index.ts +7 -0
- package/src/launcher.ts +11 -1
- package/src/openapi/api.yaml +150 -0
- package/src/openapi/schemas/Error.yaml +14 -0
- package/src/start.ts +44 -5
- package/src/types.ts +71 -2
- package/src/views/index.ejs +4 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Error schema
|
|
2
|
+
$id: https://darthcav.github.io/api/v1/schemas/Error
|
|
3
|
+
$schema: https://json-schema.org/draft/2020-12/schema
|
|
4
|
+
title: Error schema for the APIs
|
|
5
|
+
description: Error model for the APIs
|
|
6
|
+
type: object
|
|
7
|
+
required: [ message ]
|
|
8
|
+
properties:
|
|
9
|
+
message:
|
|
10
|
+
description: Error message.
|
|
11
|
+
type: string
|
|
12
|
+
code:
|
|
13
|
+
description: HTTP status code.
|
|
14
|
+
type: integer
|
package/src/start.ts
CHANGED
|
@@ -1,20 +1,59 @@
|
|
|
1
1
|
import process, { env } from "node:process"
|
|
2
2
|
import { getConsoleLogger, main } from "@darthcav/ts-utils"
|
|
3
3
|
import pkg from "../package.json" with { type: "json" }
|
|
4
|
-
import {
|
|
4
|
+
import { createKeycloakVerifier } from "./auth/keycloak.ts"
|
|
5
|
+
import {
|
|
6
|
+
defaultPlugins,
|
|
7
|
+
defaultRoutes,
|
|
8
|
+
type KeycloakAuthConfig,
|
|
9
|
+
launcher,
|
|
10
|
+
} from "./index.ts"
|
|
5
11
|
|
|
6
12
|
const logger = await getConsoleLogger(pkg.name, "info")
|
|
7
13
|
|
|
8
|
-
main(pkg.name, logger, () => {
|
|
14
|
+
main(pkg.name, logger, async () => {
|
|
15
|
+
const authPaths = env["API_AUTH_PATHS"]
|
|
16
|
+
?.split(",")
|
|
17
|
+
.map((p) => p.trim())
|
|
18
|
+
.filter((p) => p.length > 0)
|
|
19
|
+
|
|
20
|
+
const keycloakUrl = env["KEYCLOAK_URL"]?.trim()
|
|
21
|
+
const keycloakRealm = env["KEYCLOAK_REALM"]?.trim()
|
|
22
|
+
const keycloakClientId = env["KEYCLOAK_CLIENT_ID"]?.trim()
|
|
23
|
+
const keycloakClientSecret = env["KEYCLOAK_CLIENT_SECRET"]?.trim()
|
|
24
|
+
|
|
25
|
+
const keycloakAuth: KeycloakAuthConfig | undefined =
|
|
26
|
+
keycloakUrl && keycloakRealm && keycloakClientId && keycloakClientSecret
|
|
27
|
+
? {
|
|
28
|
+
url: keycloakUrl,
|
|
29
|
+
realm: keycloakRealm,
|
|
30
|
+
clientId: keycloakClientId,
|
|
31
|
+
clientSecret: keycloakClientSecret,
|
|
32
|
+
}
|
|
33
|
+
: undefined
|
|
34
|
+
|
|
9
35
|
const locals = {
|
|
10
36
|
pkg,
|
|
11
37
|
host: env["HOST"] ?? "localhost",
|
|
12
|
-
port: Number(env["CONTAINER_EXPOSE_PORT"])
|
|
38
|
+
port: Number(env["CONTAINER_EXPOSE_PORT"]) || 8888,
|
|
39
|
+
...(authPaths?.length ? { authPaths } : {}),
|
|
40
|
+
...(keycloakRealm ? { authRealm: keycloakRealm } : {}),
|
|
13
41
|
}
|
|
14
|
-
const plugins =
|
|
42
|
+
const plugins = keycloakAuth
|
|
43
|
+
? defaultPlugins({ locals, keycloakAuth })
|
|
44
|
+
: defaultPlugins({ locals })
|
|
15
45
|
const routes = defaultRoutes()
|
|
16
46
|
|
|
17
|
-
const fastify =
|
|
47
|
+
const fastify = keycloakAuth
|
|
48
|
+
? launcher({
|
|
49
|
+
logger,
|
|
50
|
+
locals,
|
|
51
|
+
plugins,
|
|
52
|
+
routes,
|
|
53
|
+
verifyToken: createKeycloakVerifier(keycloakAuth),
|
|
54
|
+
})
|
|
55
|
+
: launcher({ logger, locals, plugins, routes })
|
|
56
|
+
|
|
18
57
|
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
19
58
|
process.on(signal, async (signal) =>
|
|
20
59
|
fastify
|
package/src/types.ts
CHANGED
|
@@ -13,23 +13,60 @@ import type {
|
|
|
13
13
|
*/
|
|
14
14
|
export type FSTPlugin = {
|
|
15
15
|
/** The Fastify plugin function to register. */
|
|
16
|
-
// biome-ignore lint/suspicious/noExplicitAny: third-party
|
|
16
|
+
// biome-ignore lint/suspicious/noExplicitAny: plugin opts types vary per third-party plugin; any is required for assignability
|
|
17
17
|
plugin: FastifyPluginCallback<any> | FastifyPluginAsync<any>
|
|
18
18
|
/** Optional options forwarded to the plugin on registration. */
|
|
19
19
|
opts?: FastifyPluginOptions
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for Keycloak-backed JWT authentication.
|
|
24
|
+
*/
|
|
25
|
+
export type KeycloakAuthConfig = {
|
|
26
|
+
/** Keycloak server base URL, e.g. `https://auth.example.com`. */
|
|
27
|
+
url: string
|
|
28
|
+
/** Keycloak realm name. */
|
|
29
|
+
realm: string
|
|
30
|
+
/** Client ID registered in the realm; used as the expected audience. */
|
|
31
|
+
clientId: string
|
|
32
|
+
/** Client secret for the registered client. */
|
|
33
|
+
clientSecret: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Async function that verifies a bearer token from the `Authorization` header.
|
|
38
|
+
*
|
|
39
|
+
* Return `true` to allow the request, `false` to reject it with the default
|
|
40
|
+
* 401 response, or throw to surface a custom error.
|
|
41
|
+
*/
|
|
42
|
+
export type TokenVerifier = (
|
|
43
|
+
authorizationHeader: string | undefined,
|
|
44
|
+
) => Promise<boolean>
|
|
45
|
+
|
|
22
46
|
/**
|
|
23
47
|
* Application locals decorated onto the Fastify instance and available
|
|
24
48
|
* throughout the request lifecycle.
|
|
25
49
|
*/
|
|
26
50
|
export type LauncherLocals = {
|
|
27
51
|
/** Package metadata (e.g. contents of `package.json`). */
|
|
28
|
-
pkg?:
|
|
52
|
+
pkg?: Record<string, unknown>
|
|
29
53
|
/** Hostname the server will bind to. */
|
|
30
54
|
host?: string
|
|
31
55
|
/** Port the server will listen on. */
|
|
32
56
|
port?: number
|
|
57
|
+
/**
|
|
58
|
+
* Glob patterns (picomatch) for routes that require bearer-token
|
|
59
|
+
* authentication via the `verifyToken` Fastify decorator.
|
|
60
|
+
* When `undefined` or empty, authentication is disabled.
|
|
61
|
+
*
|
|
62
|
+
* Example: `["/api/**"]`
|
|
63
|
+
*/
|
|
64
|
+
authPaths?: string[]
|
|
65
|
+
/**
|
|
66
|
+
* Protection-space label used in the `WWW-Authenticate` challenge (RFC 6750).
|
|
67
|
+
* Typically the Keycloak realm name. Defaults to `"api"` when omitted.
|
|
68
|
+
*/
|
|
69
|
+
authRealm?: string
|
|
33
70
|
/** Any additional application-specific locals. */
|
|
34
71
|
[key: string]: unknown
|
|
35
72
|
}
|
|
@@ -48,8 +85,40 @@ export type LauncherOptions = {
|
|
|
48
85
|
routes: Map<string, RouteOptions>
|
|
49
86
|
/** Map of named decorators to add to the Fastify instance. */
|
|
50
87
|
decorators?: Map<string, unknown>
|
|
88
|
+
/**
|
|
89
|
+
* Token verifier registered as the `verifyToken` Fastify decorator.
|
|
90
|
+
*
|
|
91
|
+
* When omitted and `locals.authPaths` is set, all protected routes will
|
|
92
|
+
* respond with `401 Unauthorized`.
|
|
93
|
+
*/
|
|
94
|
+
verifyToken?: TokenVerifier
|
|
51
95
|
/** Optional Fastify server options (merged over {@link defaultFastifyOptions}). */
|
|
52
96
|
opts?: FastifyServerOptions
|
|
53
97
|
/** Optional callback invoked once the server is listening. */
|
|
54
98
|
done?: () => void
|
|
55
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Options accepted by the {@link defaultPlugins} function.
|
|
103
|
+
*/
|
|
104
|
+
export type DefaultPluginsOptions = {
|
|
105
|
+
/** Application locals; `locals.pkg` is exposed as the default EJS context. */
|
|
106
|
+
locals: LauncherLocals
|
|
107
|
+
/** Optional base directory for resolving the `src/` folder; defaults to the parent of `import.meta.dirname`. */
|
|
108
|
+
baseDir?: string | null
|
|
109
|
+
/** Optional Keycloak configuration used to mark the generated `/api/` OpenAPI operations as OpenID Connect–protected. */
|
|
110
|
+
keycloakAuth?: KeycloakAuthConfig
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Fastify module augmentation that exposes {@link LauncherLocals} and the
|
|
115
|
+
* {@link TokenVerifier} as first-class decorators on every `FastifyInstance`.
|
|
116
|
+
*
|
|
117
|
+
* Both are registered in {@link launcher} via `fastify.decorate(...)`.
|
|
118
|
+
*/
|
|
119
|
+
declare module "fastify" {
|
|
120
|
+
interface FastifyInstance {
|
|
121
|
+
locals: LauncherLocals
|
|
122
|
+
verifyToken: TokenVerifier
|
|
123
|
+
}
|
|
124
|
+
}
|