@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.
Files changed (51) hide show
  1. package/README.md +58 -8
  2. package/dist/__tests__/defaultPlugins.test.js +47 -2
  3. package/dist/__tests__/defaultPlugins.test.js.map +1 -1
  4. package/dist/__tests__/defaultRoutes.test.js +226 -1
  5. package/dist/__tests__/defaultRoutes.test.js.map +1 -1
  6. package/dist/__tests__/keycloak.test.d.ts +2 -0
  7. package/dist/__tests__/keycloak.test.d.ts.map +1 -0
  8. package/dist/__tests__/keycloak.test.js +105 -0
  9. package/dist/__tests__/keycloak.test.js.map +1 -0
  10. package/dist/__tests__/launcher.test.js +34 -0
  11. package/dist/__tests__/launcher.test.js.map +1 -1
  12. package/dist/auth/keycloak.d.ts +17 -0
  13. package/dist/auth/keycloak.d.ts.map +1 -0
  14. package/dist/auth/keycloak.js +35 -0
  15. package/dist/auth/keycloak.js.map +1 -0
  16. package/dist/defaults/defaultPlugins.d.ts +12 -7
  17. package/dist/defaults/defaultPlugins.d.ts.map +1 -1
  18. package/dist/defaults/defaultPlugins.js +109 -2
  19. package/dist/defaults/defaultPlugins.js.map +1 -1
  20. package/dist/defaults/defaultRoutes.d.ts +5 -0
  21. package/dist/defaults/defaultRoutes.d.ts.map +1 -1
  22. package/dist/defaults/defaultRoutes.js +30 -0
  23. package/dist/defaults/defaultRoutes.js.map +1 -1
  24. package/dist/hooks/authPreHandler.d.ts +19 -0
  25. package/dist/hooks/authPreHandler.d.ts.map +1 -0
  26. package/dist/hooks/authPreHandler.js +32 -0
  27. package/dist/hooks/authPreHandler.js.map +1 -0
  28. package/dist/index.d.ts +4 -2
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/launcher.d.ts +2 -2
  33. package/dist/launcher.d.ts.map +1 -1
  34. package/dist/launcher.js +7 -2
  35. package/dist/launcher.js.map +1 -1
  36. package/dist/start.js +34 -5
  37. package/dist/start.js.map +1 -1
  38. package/dist/types.d.ts +64 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +10 -3
  41. package/src/auth/keycloak.ts +43 -0
  42. package/src/defaults/defaultPlugins.ts +141 -7
  43. package/src/defaults/defaultRoutes.ts +30 -1
  44. package/src/hooks/authPreHandler.ts +41 -0
  45. package/src/index.ts +7 -0
  46. package/src/launcher.ts +11 -1
  47. package/src/openapi/api.yaml +150 -0
  48. package/src/openapi/schemas/Error.yaml +14 -0
  49. package/src/start.ts +44 -5
  50. package/src/types.ts +71 -2
  51. 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 { defaultPlugins, defaultRoutes, launcher } from "./index.ts"
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"]) ?? 8888,
38
+ port: Number(env["CONTAINER_EXPOSE_PORT"]) || 8888,
39
+ ...(authPaths?.length ? { authPaths } : {}),
40
+ ...(keycloakRealm ? { authRealm: keycloakRealm } : {}),
13
41
  }
14
- const plugins = defaultPlugins({ locals })
42
+ const plugins = keycloakAuth
43
+ ? defaultPlugins({ locals, keycloakAuth })
44
+ : defaultPlugins({ locals })
15
45
  const routes = defaultRoutes()
16
46
 
17
- const fastify = launcher({ logger, locals, plugins, routes })
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 plugins use varied option types
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?: object
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
+ }
@@ -25,6 +25,10 @@
25
25
  <code><%= process.pid %></code>
26
26
  </td>
27
27
  </tr>
28
+ <tr>
29
+ <th>Documentation</th>
30
+ <td><a href="/docs/">OpenAPI documentation</a></td>
31
+ </tr>
28
32
  </table>
29
33
 
30
34
  <%- include ("./include/footer.ejs") -%>