@nxgt/shared-hono 1.0.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 (41) hide show
  1. package/README.md +15 -0
  2. package/dist/env.d.ts +23 -0
  3. package/dist/env.d.ts.map +1 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +401 -0
  7. package/dist/index.js.map +18 -0
  8. package/dist/mcp/helpers.d.ts +3 -0
  9. package/dist/mcp/helpers.d.ts.map +1 -0
  10. package/dist/mcp/index.d.ts +4 -0
  11. package/dist/mcp/index.d.ts.map +1 -0
  12. package/dist/mcp/index.js +47 -0
  13. package/dist/mcp/index.js.map +11 -0
  14. package/dist/middlewares/accept-query.d.ts +16 -0
  15. package/dist/middlewares/accept-query.d.ts.map +1 -0
  16. package/dist/middlewares/current-user.d.ts +5 -0
  17. package/dist/middlewares/current-user.d.ts.map +1 -0
  18. package/dist/middlewares/error-handler.d.ts +9 -0
  19. package/dist/middlewares/error-handler.d.ts.map +1 -0
  20. package/dist/middlewares/index.d.ts +8 -0
  21. package/dist/middlewares/index.d.ts.map +1 -0
  22. package/dist/middlewares/openfetch-service-user.d.ts +3 -0
  23. package/dist/middlewares/openfetch-service-user.d.ts.map +1 -0
  24. package/dist/middlewares/ory-auth.d.ts +53 -0
  25. package/dist/middlewares/ory-auth.d.ts.map +1 -0
  26. package/dist/middlewares/rate-limiter.d.ts +21 -0
  27. package/dist/middlewares/rate-limiter.d.ts.map +1 -0
  28. package/dist/middlewares/secured.d.ts +18 -0
  29. package/dist/middlewares/secured.d.ts.map +1 -0
  30. package/dist/openapi-fetch.d.ts +4 -0
  31. package/dist/openapi-fetch.d.ts.map +1 -0
  32. package/dist/openapi-fetch.js +11 -0
  33. package/dist/openapi-fetch.js.map +10 -0
  34. package/dist/types/hono.d.ts +27 -0
  35. package/dist/types/index.d.ts +2 -0
  36. package/dist/types/index.d.ts.map +1 -0
  37. package/dist/utils/index.d.ts +2 -0
  38. package/dist/utils/index.d.ts.map +1 -0
  39. package/dist/utils/test.utils.d.ts +44 -0
  40. package/dist/utils/test.utils.d.ts.map +1 -0
  41. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # @nxgt/shared-api
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run src/index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.9. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/dist/env.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ declare const envSchema: z.ZodObject<{
3
+ NODE_ENV: z.ZodDefault<z.ZodEnum<{
4
+ development: "development";
5
+ production: "production";
6
+ test: "test";
7
+ }>>;
8
+ PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
9
+ LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
10
+ error: "error";
11
+ warn: "warn";
12
+ info: "info";
13
+ debug: "debug";
14
+ }>>;
15
+ }, z.core.$strip>;
16
+ export type Env = z.infer<typeof envSchema>;
17
+ export declare const env: {
18
+ NODE_ENV: "development" | "production" | "test";
19
+ PORT: number;
20
+ LOG_LEVEL: "error" | "warn" | "info" | "debug";
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,QAAA,MAAM,SAAS;;;;;;;;;;;;;iBAWb,CAAC;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAgB5C,eAAO,MAAM,GAAG;;;;CAGd,CAAC"}
@@ -0,0 +1,5 @@
1
+ import './types';
2
+ export * from './middlewares';
3
+ export * from './types';
4
+ export * from './utils';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAC;AAEjB,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,401 @@
1
+ // src/middlewares/accept-query.ts
2
+ import { createMiddleware } from "hono/factory";
3
+ var ACCEPT_QUERY_MEDIA_TYPE = "application/json";
4
+ function acceptQuery(mediaTypes = ACCEPT_QUERY_MEDIA_TYPE) {
5
+ return createMiddleware(async (ctx, next) => {
6
+ await next();
7
+ ctx.header("Accept-Query", mediaTypes);
8
+ });
9
+ }
10
+ // src/middlewares/current-user.ts
11
+ import { USER_HEADERS } from "@nxgt/shared/models";
12
+ import { logger } from "@nxgt/shared-logging";
13
+ import { createMiddleware as createMiddleware2 } from "hono/factory";
14
+ var currentUser = () => createMiddleware2(async (ctx, next) => {
15
+ if (!ctx.req.header(USER_HEADERS.ID) && !ctx.req.header(USER_HEADERS.CLIENT)) {
16
+ logger.warn("No user information found in headers");
17
+ return next();
18
+ }
19
+ const user = {
20
+ id: ctx.req.header(USER_HEADERS.ID),
21
+ username: ctx.req.header(USER_HEADERS.USERNAME),
22
+ email: ctx.req.header(USER_HEADERS.EMAIL),
23
+ firstName: ctx.req.header(USER_HEADERS.FIRST_NAME),
24
+ lastName: ctx.req.header(USER_HEADERS.LAST_NAME),
25
+ birthDate: ctx.req.header(USER_HEADERS.BIRTH_DATE) ? new Date(ctx.req.header(USER_HEADERS.BIRTH_DATE) || "") : null,
26
+ authorities: ctx.req.header(USER_HEADERS.AUTHORITIES)?.split(",") || [],
27
+ clientId: ctx.req.header(USER_HEADERS.CLIENT) || null,
28
+ roles: ctx.req.header(USER_HEADERS.ROLES)?.split(",") || [],
29
+ scopes: ctx.req.header(USER_HEADERS.SCOPES)?.split(",") || []
30
+ };
31
+ user.name = user.username || user.clientId || undefined;
32
+ logger.info(`Resolving principal : name[${user.name}] email[${user.email}] id[${user.id}] client[${user.clientId}]`);
33
+ ctx.set(USER_HEADERS.ID, user.id);
34
+ ctx.set(USER_HEADERS.USERNAME, user.username);
35
+ ctx.set(USER_HEADERS.EMAIL, user.email);
36
+ ctx.set(USER_HEADERS.NAME, user.name);
37
+ ctx.set(USER_HEADERS.FIRST_NAME, user.firstName);
38
+ ctx.set(USER_HEADERS.LAST_NAME, user.lastName);
39
+ ctx.set(USER_HEADERS.BIRTH_DATE, user.birthDate ? user.birthDate.toISOString() : null);
40
+ ctx.set(USER_HEADERS.AUTHORITIES, user.authorities);
41
+ ctx.set(USER_HEADERS.CLIENT, ctx.req.header(USER_HEADERS.CLIENT) || null);
42
+ ctx.set(USER_HEADERS.ROLES, ctx.req.header(USER_HEADERS.ROLES)?.split(",") || []);
43
+ ctx.set(USER_HEADERS.SCOPES, ctx.req.header(USER_HEADERS.SCOPES)?.split(",") || []);
44
+ ctx.set("principal", user);
45
+ return next();
46
+ });
47
+ // src/middlewares/error-handler.ts
48
+ import { CustomException } from "@nxgt/shared-exceptions";
49
+ import { logger as logger3 } from "@nxgt/shared-logging";
50
+ import { HTTPException } from "hono/http-exception";
51
+
52
+ // src/env.ts
53
+ import { logger as logger2 } from "@nxgt/shared-logging";
54
+ import { z } from "zod";
55
+ var envSchema = z.object({
56
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
57
+ PORT: z.coerce.number().default(8080),
58
+ LOG_LEVEL: z.enum(["error", "warn", "info", "debug"]).default("debug")
59
+ });
60
+ var parseEnv = (value) => {
61
+ const result = envSchema.safeParse(value);
62
+ if (!result.success) {
63
+ logger2.error("❌ Invalid environment variables:");
64
+ logger2.error(result.error.issues);
65
+ throw new Error("Invalid environment variables");
66
+ }
67
+ return result.data;
68
+ };
69
+ var env = parseEnv({
70
+ NODE_ENV: Bun.env.NODE_ENV,
71
+ PORT: Bun.env.PORT
72
+ });
73
+
74
+ // src/middlewares/error-handler.ts
75
+ var createErrorHandler = (translate, {
76
+ showStackInDev = true,
77
+ showStackInTest = false,
78
+ logToConsole = true
79
+ } = {}) => {
80
+ return (err, c) => {
81
+ if (logToConsole) {
82
+ logger3.error("─".repeat(60));
83
+ logger3.error("[Global Error]", err);
84
+ if (showStackInDev && env.NODE_ENV === "development" || showStackInTest && env.NODE_ENV === "test") {
85
+ logger3.error(err.stack);
86
+ }
87
+ logger3.error("─".repeat(60));
88
+ }
89
+ if (err instanceof CustomException) {
90
+ c.status(err.code);
91
+ return c.json({
92
+ status: err.code,
93
+ message: translate(err.message, err.options),
94
+ debugMessage: err.debugMessage,
95
+ timestamp: new Date
96
+ });
97
+ }
98
+ if (err instanceof HTTPException) {
99
+ c.status(err.status);
100
+ return c.json({
101
+ status: err.status,
102
+ message: err.message,
103
+ debugMessage: err.stack,
104
+ timestamp: new Date
105
+ });
106
+ }
107
+ c.status(500);
108
+ return c.json({
109
+ status: 500,
110
+ message: translate("errors.internal-server-error"),
111
+ debugMessage: err.message,
112
+ timestamp: new Date
113
+ });
114
+ };
115
+ };
116
+ // src/middlewares/openfetch-service-user.ts
117
+ import { USER_HEADERS as USER_HEADERS2 } from "@nxgt/shared/models";
118
+ import { tryGetContext } from "hono/context-storage";
119
+ function openfetchServiceUser() {
120
+ const ctx = tryGetContext();
121
+ return {
122
+ async onRequest({ request }) {
123
+ if (ctx?.get(USER_HEADERS2.ID)) {
124
+ request.headers.set(USER_HEADERS2.ID, ctx.get(USER_HEADERS2.ID) || "");
125
+ request.headers.set(USER_HEADERS2.USERNAME, ctx.get(USER_HEADERS2.USERNAME) || "");
126
+ request.headers.set(USER_HEADERS2.EMAIL, ctx.get(USER_HEADERS2.EMAIL) || "");
127
+ request.headers.set(USER_HEADERS2.FIRST_NAME, ctx.get(USER_HEADERS2.FIRST_NAME) || "");
128
+ request.headers.set(USER_HEADERS2.LAST_NAME, ctx.get(USER_HEADERS2.LAST_NAME) || "");
129
+ request.headers.set(USER_HEADERS2.BIRTH_DATE, ctx.get(USER_HEADERS2.BIRTH_DATE) || "");
130
+ request.headers.set(USER_HEADERS2.AUTHORITIES, ctx.get(USER_HEADERS2.AUTHORITIES)?.join(",") || "");
131
+ request.headers.set(USER_HEADERS2.CLIENT, ctx.get(USER_HEADERS2.CLIENT) || "");
132
+ request.headers.set(USER_HEADERS2.ROLES, ctx.get(USER_HEADERS2.ROLES)?.join(",") || "");
133
+ request.headers.set(USER_HEADERS2.SCOPES, ctx.get(USER_HEADERS2.SCOPES)?.join(",") || "");
134
+ request.headers.set("X-Service", "gateway");
135
+ }
136
+ return request;
137
+ }
138
+ };
139
+ }
140
+ // src/middlewares/ory-auth.ts
141
+ import { USER_HEADERS as USER_HEADERS4 } from "@nxgt/shared/models";
142
+ import { CustomException as CustomException2 } from "@nxgt/shared-exceptions";
143
+ import { logger as logger4 } from "@nxgt/shared-logging";
144
+ import { createMiddleware as createMiddleware3 } from "hono/factory";
145
+ import {
146
+ bearerOf,
147
+ OryUnavailable
148
+ } from "stx-sdk/ory";
149
+
150
+ // src/utils/test.utils.ts
151
+ import { USER_HEADERS as USER_HEADERS3 } from "@nxgt/shared/models";
152
+ import { mongoose } from "@nxgt/shared-mongo";
153
+ function principalFromMockHeaders(ctx) {
154
+ if (!ctx.req.header(USER_HEADERS3.ID) && !ctx.req.header(USER_HEADERS3.CLIENT)) {
155
+ return;
156
+ }
157
+ const principal = {
158
+ id: ctx.req.header(USER_HEADERS3.ID),
159
+ username: ctx.req.header(USER_HEADERS3.USERNAME),
160
+ email: ctx.req.header(USER_HEADERS3.EMAIL),
161
+ firstName: ctx.req.header(USER_HEADERS3.FIRST_NAME),
162
+ lastName: ctx.req.header(USER_HEADERS3.LAST_NAME),
163
+ birthDate: ctx.req.header(USER_HEADERS3.BIRTH_DATE) ? new Date(ctx.req.header(USER_HEADERS3.BIRTH_DATE) || "") : null,
164
+ authorities: ctx.req.header(USER_HEADERS3.AUTHORITIES)?.split(",") || [],
165
+ clientId: ctx.req.header(USER_HEADERS3.CLIENT) || null,
166
+ roles: ctx.req.header(USER_HEADERS3.ROLES)?.split(",") || [],
167
+ scopes: ctx.req.header(USER_HEADERS3.SCOPES)?.split(",") || []
168
+ };
169
+ principal.name = principal.username || principal.clientId || undefined;
170
+ return principal;
171
+ }
172
+ function mockUser(values) {
173
+ return {
174
+ id: new mongoose.mongo.ObjectId().toHexString(),
175
+ authorities: [...values.roles ?? [], ...values.permissions ?? []],
176
+ name: values.username || undefined,
177
+ ...values
178
+ };
179
+ }
180
+ function mockAuthMiddleware(user) {
181
+ return {
182
+ async onRequest({ request }) {
183
+ request.headers.set(USER_HEADERS3.ID, user.id || "");
184
+ request.headers.set(USER_HEADERS3.USERNAME, user.username || "");
185
+ request.headers.set(USER_HEADERS3.EMAIL, user.email || "");
186
+ request.headers.set(USER_HEADERS3.AUTHORITIES, user.authorities?.join(",") || "");
187
+ request.headers.set(USER_HEADERS3.BIRTH_DATE, user.birthDate?.toISOString() ?? "");
188
+ request.headers.set(USER_HEADERS3.FIRST_NAME, user.firstName ?? "");
189
+ request.headers.set(USER_HEADERS3.LAST_NAME, user.lastName ?? "");
190
+ return request;
191
+ }
192
+ };
193
+ }
194
+ var asQueryMethod = {
195
+ onRequest: async ({ request }) => {
196
+ const url = new URL(request.url);
197
+ url.pathname = url.pathname.replace(/\/search$/, "");
198
+ const body = await request.text();
199
+ return new Request(url, {
200
+ method: "QUERY",
201
+ headers: request.headers,
202
+ body: body || undefined
203
+ });
204
+ }
205
+ };
206
+
207
+ // src/middlewares/ory-auth.ts
208
+ function oryAuth(ory) {
209
+ return createMiddleware3(async (ctx, next) => {
210
+ if (env.NODE_ENV === "test") {
211
+ const mockPrincipal = principalFromMockHeaders(ctx);
212
+ if (mockPrincipal) {
213
+ ctx.set("principal", mockPrincipal);
214
+ ctx.set("ory", oryPrincipalFromMock(mockPrincipal));
215
+ ctx.set(USER_HEADERS4.CLAIMS, {
216
+ sub: mockPrincipal.id,
217
+ username: mockPrincipal.username ?? undefined,
218
+ clientId: mockPrincipal.clientId ?? undefined,
219
+ authorities: mockPrincipal.authorities ?? [],
220
+ roles: mockPrincipal.roles ?? [],
221
+ scope: mockPrincipal.scopes?.join(" ")
222
+ });
223
+ return next();
224
+ }
225
+ }
226
+ let resolved;
227
+ try {
228
+ resolved = await ory.resolve(ctx.req.raw.headers);
229
+ } catch (error) {
230
+ if (!(error instanceof OryUnavailable))
231
+ throw error;
232
+ throw serviceUnavailable(error);
233
+ }
234
+ if (!resolved) {
235
+ ctx.set("ory", null);
236
+ return next();
237
+ }
238
+ const principal = toPrincipal(resolved);
239
+ logger4.info(`Resolving principal (ory ${resolved.kind}): subject[${resolved.subject}]`);
240
+ ctx.set("principal", principal);
241
+ ctx.set("ory", resolved);
242
+ ctx.set("accessToken", bearerOf(ctx.req.raw.headers));
243
+ ctx.set(USER_HEADERS4.CLAIMS, {
244
+ sub: resolved.subject,
245
+ kind: resolved.kind,
246
+ email: resolved.identity?.email,
247
+ email_verified: resolved.identity?.verified,
248
+ clientId: resolved.clientId,
249
+ scope: resolved.scopes.join(" ") || undefined,
250
+ aud: resolved.audience,
251
+ aal: resolved.aal,
252
+ exp: resolved.expiresAt?.toISOString()
253
+ });
254
+ return next();
255
+ });
256
+ }
257
+ function serviceUnavailable(error) {
258
+ logger4.error(`Ory ${error.service} unavailable (${error.status}): ${JSON.stringify(error.body)}`);
259
+ return CustomException2.from({
260
+ message: "errors.service-unavailable",
261
+ code: 503,
262
+ debugMessage: `ory: ${error.message}`
263
+ });
264
+ }
265
+ function withOryUnavailable(handler) {
266
+ return (error, ctx) => handler(error instanceof OryUnavailable ? serviceUnavailable(error) : error, ctx);
267
+ }
268
+ function toPrincipal(ory) {
269
+ const principal = {
270
+ id: ory.subject,
271
+ username: ory.identity?.email ?? null,
272
+ email: ory.identity?.email ?? null,
273
+ firstName: ory.identity?.name?.first ?? null,
274
+ lastName: ory.identity?.name?.last ?? null,
275
+ birthDate: null,
276
+ authorities: [],
277
+ roles: [],
278
+ clientId: ory.clientId ?? null,
279
+ scopes: ory.scopes
280
+ };
281
+ principal.name = principal.username || principal.clientId || undefined;
282
+ return principal;
283
+ }
284
+ function oryPrincipalFromMock(mock) {
285
+ return {
286
+ subject: mock.id ?? mock.clientId ?? "mock",
287
+ kind: mock.clientId && !mock.id ? "token" : "session",
288
+ identity: mock.email ? {
289
+ email: mock.email,
290
+ name: {
291
+ first: mock.firstName ?? undefined,
292
+ last: mock.lastName ?? undefined
293
+ },
294
+ verified: true
295
+ } : undefined,
296
+ scopes: mock.scopes ?? [],
297
+ clientId: mock.clientId ?? undefined
298
+ };
299
+ }
300
+ // src/middlewares/rate-limiter.ts
301
+ import { logger as logger5 } from "@nxgt/shared-logging";
302
+ import { Redis as UpstashRedis } from "@upstash/redis";
303
+ import {
304
+ rateLimiter as honoRateLimiter,
305
+ RedisStore
306
+ } from "hono-rate-limiter";
307
+ import IORedis from "ioredis";
308
+ function ioredisAdapter(client) {
309
+ return {
310
+ scriptLoad: (script) => client.script("LOAD", script),
311
+ evalsha: (sha1, keys, args) => client.evalsha(sha1, keys.length, ...keys, ...args),
312
+ decr: (key) => client.decr(key),
313
+ del: (key) => client.del(key)
314
+ };
315
+ }
316
+ function redisClientFor(redisUrl, redisToken) {
317
+ if (redisUrl.startsWith("redis://") || redisUrl.startsWith("rediss://")) {
318
+ const client = new IORedis(redisUrl);
319
+ client.on("error", (err) => {
320
+ logger5.error(`[rate-limiter] Redis connection error: ${err.message}`);
321
+ });
322
+ return ioredisAdapter(client);
323
+ }
324
+ return new UpstashRedis({
325
+ url: redisUrl,
326
+ token: redisToken
327
+ });
328
+ }
329
+ function rateLimiter(options) {
330
+ return honoRateLimiter({
331
+ ...options,
332
+ windowMs: options?.windowMs ?? 1 * 60 * 1000,
333
+ limit: options?.limit ?? 10,
334
+ keyGenerator: options?.keyGenerator ?? ((c) => c.req.header("x-forwarded-for") ?? ""),
335
+ store: options?.redisUrl ? new RedisStore({
336
+ client: redisClientFor(options.redisUrl, options.redisToken),
337
+ prefix: options.prefix
338
+ }) : undefined
339
+ });
340
+ }
341
+ // src/middlewares/secured.ts
342
+ import { isScopeAuthority } from "@nxgt/shared/helpers";
343
+ import { CustomException as CustomException3 } from "@nxgt/shared-exceptions";
344
+ import { getLogger } from "@nxgt/shared-logging";
345
+ import { createMiddleware as createMiddleware4 } from "hono/factory";
346
+ function secured(authorities = []) {
347
+ return createMiddleware4(async (ctx, next) => {
348
+ const logger6 = ctx.get("logger") || getLogger();
349
+ logger6.info("Secured middleware: checking authorities");
350
+ const user = ctx.get("principal");
351
+ if (!user) {
352
+ logger6.error("Unauthenticated access attempt");
353
+ throw CustomException3.unauthorized({
354
+ message: "errors.unauthenticated"
355
+ });
356
+ }
357
+ if (user.roles?.includes("ADMIN")) {
358
+ return next();
359
+ }
360
+ logger6.info(`User authenticated: ${user.name ?? user.username ?? user.clientId}`);
361
+ if (!authorities.length) {
362
+ return next();
363
+ }
364
+ const isClient = !!user.clientId && !user.username;
365
+ const userAuthorities = user.authorities ?? [];
366
+ const effectiveUserAuthorities = isClient ? userAuthorities.filter(isScopeAuthority) : userAuthorities;
367
+ const granted = authorities.every((group) => {
368
+ if (group.length === 0)
369
+ return true;
370
+ return group.some((authority) => effectiveUserAuthorities.includes(authority));
371
+ });
372
+ if (granted) {
373
+ await next();
374
+ return;
375
+ }
376
+ throw CustomException3.forbidden({
377
+ message: "errors.forbidden"
378
+ });
379
+ });
380
+ }
381
+ export {
382
+ ACCEPT_QUERY_MEDIA_TYPE,
383
+ USER_HEADERS,
384
+ acceptQuery,
385
+ asQueryMethod,
386
+ createErrorHandler,
387
+ currentUser,
388
+ mockAuthMiddleware,
389
+ mockUser,
390
+ openfetchServiceUser,
391
+ oryAuth,
392
+ principalFromMockHeaders,
393
+ rateLimiter,
394
+ secured,
395
+ serviceUnavailable,
396
+ toPrincipal,
397
+ withOryUnavailable
398
+ };
399
+
400
+ //# debugId=EC87DAEAD6B394CA64756E2164756E21
401
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/middlewares/accept-query.ts", "../src/middlewares/current-user.ts", "../src/middlewares/error-handler.ts", "../src/env.ts", "../src/middlewares/openfetch-service-user.ts", "../src/middlewares/ory-auth.ts", "../src/utils/test.utils.ts", "../src/middlewares/rate-limiter.ts", "../src/middlewares/secured.ts"],
4
+ "sourcesContent": [
5
+ "import { createMiddleware } from 'hono/factory';\n\n/** Media types accepted in a QUERY request body, advertised by `acceptQuery()`. */\nexport const ACCEPT_QUERY_MEDIA_TYPE = 'application/json';\n\n/**\n * Advertises QUERY support on a resource.\n *\n * `Accept-Query` is how the safe-method-with-body draft says a resource\n * announces both that it answers QUERY *and* which media types it will accept\n * in the request content. It rides on the QUERY registration only: the\n * `POST …/search` route it shares its handlers with is left exactly as it was,\n * headers included, so that every existing caller sees no change at all.\n *\n * Set after `next()` so it lands on whatever the handler produced, errors\n * included.\n */\nexport function acceptQuery(mediaTypes: string = ACCEPT_QUERY_MEDIA_TYPE) {\n\treturn createMiddleware(async (ctx, next) => {\n\t\tawait next();\n\t\tctx.header('Accept-Query', mediaTypes);\n\t});\n}\n",
6
+ "import { type Principal, USER_HEADERS } from '@nxgt/shared/models';\nimport { logger } from '@nxgt/shared-logging';\nimport { createMiddleware } from 'hono/factory';\nimport type { MiddlewareHandler } from 'hono/types';\n\nexport const currentUser = (): MiddlewareHandler =>\n\tcreateMiddleware(async (ctx, next) => {\n\t\tif (\n\t\t\t!ctx.req.header(USER_HEADERS.ID) &&\n\t\t\t!ctx.req.header(USER_HEADERS.CLIENT)\n\t\t) {\n\t\t\tlogger.warn('No user information found in headers');\n\t\t\treturn next();\n\t\t}\n\n\t\tconst user: Principal = {\n\t\t\tid: ctx.req.header(USER_HEADERS.ID),\n\t\t\tusername: ctx.req.header(USER_HEADERS.USERNAME),\n\t\t\temail: ctx.req.header(USER_HEADERS.EMAIL),\n\t\t\tfirstName: ctx.req.header(USER_HEADERS.FIRST_NAME),\n\t\t\tlastName: ctx.req.header(USER_HEADERS.LAST_NAME),\n\t\t\tbirthDate: ctx.req.header(USER_HEADERS.BIRTH_DATE)\n\t\t\t\t? new Date(ctx.req.header(USER_HEADERS.BIRTH_DATE) || '')\n\t\t\t\t: null,\n\t\t\tauthorities: ctx.req.header(USER_HEADERS.AUTHORITIES)?.split(',') || [],\n\t\t\tclientId: ctx.req.header(USER_HEADERS.CLIENT) || null,\n\t\t\troles: ctx.req.header(USER_HEADERS.ROLES)?.split(',') || [],\n\t\t\tscopes: ctx.req.header(USER_HEADERS.SCOPES)?.split(',') || [],\n\t\t};\n\n\t\tuser.name = user.username || user.clientId || undefined;\n\n\t\tlogger.info(\n\t\t\t`Resolving principal : name[${user.name}] email[${user.email}] id[${user.id}] client[${user.clientId}]`,\n\t\t);\n\n\t\tctx.set(USER_HEADERS.ID, user.id);\n\t\tctx.set(USER_HEADERS.USERNAME, user.username);\n\t\tctx.set(USER_HEADERS.EMAIL, user.email);\n\t\tctx.set(USER_HEADERS.NAME, user.name);\n\t\tctx.set(USER_HEADERS.FIRST_NAME, user.firstName);\n\t\tctx.set(USER_HEADERS.LAST_NAME, user.lastName);\n\t\tctx.set(\n\t\t\tUSER_HEADERS.BIRTH_DATE,\n\t\t\tuser.birthDate ? user.birthDate.toISOString() : null,\n\t\t);\n\t\tctx.set(USER_HEADERS.AUTHORITIES, user.authorities);\n\t\tctx.set(USER_HEADERS.CLIENT, ctx.req.header(USER_HEADERS.CLIENT) || null);\n\t\tctx.set(\n\t\t\tUSER_HEADERS.ROLES,\n\t\t\tctx.req.header(USER_HEADERS.ROLES)?.split(',') || [],\n\t\t);\n\t\tctx.set(\n\t\t\tUSER_HEADERS.SCOPES,\n\t\t\tctx.req.header(USER_HEADERS.SCOPES)?.split(',') || [],\n\t\t);\n\n\t\tctx.set('principal', user);\n\n\t\treturn next();\n\t});\n\nexport { type Principal, USER_HEADERS };\n",
7
+ "import type { LocaleKey } from '@nxgt/i18n';\nimport { CustomException } from '@nxgt/shared-exceptions';\nimport { logger } from '@nxgt/shared-logging';\nimport type { ErrorHandler } from 'hono';\nimport { HTTPException } from 'hono/http-exception';\nimport { env } from '../env';\n\nexport type ErrorHandlerOptions = {\n\tshowStackInDev?: boolean;\n\tshowStackInTest?: boolean;\n\tlogToConsole?: boolean;\n};\n\nexport const createErrorHandler = <K extends LocaleKey = LocaleKey>(\n\ttranslate: (key: K, context?: Record<string, any>) => string,\n\t{\n\t\tshowStackInDev = true,\n\t\tshowStackInTest = false,\n\t\tlogToConsole = true,\n\t}: ErrorHandlerOptions = {},\n): ErrorHandler => {\n\treturn (err, c) => {\n\t\tif (logToConsole) {\n\t\t\tlogger.error('─'.repeat(60));\n\t\t\tlogger.error('[Global Error]', err);\n\t\t\tif (\n\t\t\t\t(showStackInDev && env.NODE_ENV === 'development') ||\n\t\t\t\t(showStackInTest && env.NODE_ENV === 'test')\n\t\t\t) {\n\t\t\t\tlogger.error(err.stack);\n\t\t\t}\n\t\t\tlogger.error('─'.repeat(60));\n\t\t}\n\n\t\tif (err instanceof CustomException) {\n\t\t\tc.status(err.code);\n\t\t\treturn c.json({\n\t\t\t\tstatus: err.code,\n\t\t\t\tmessage: translate(err.message as K, err.options),\n\t\t\t\tdebugMessage: err.debugMessage,\n\t\t\t\ttimestamp: new Date(),\n\t\t\t});\n\t\t}\n\t\tif (err instanceof HTTPException) {\n\t\t\tc.status(err.status);\n\t\t\treturn c.json({\n\t\t\t\tstatus: err.status,\n\t\t\t\tmessage: err.message,\n\t\t\t\tdebugMessage: err.stack,\n\t\t\t\ttimestamp: new Date(),\n\t\t\t});\n\t\t}\n\t\tc.status(500);\n\t\treturn c.json({\n\t\t\tstatus: 500,\n\t\t\tmessage: translate('errors.internal-server-error' as K),\n\t\t\tdebugMessage: err.message,\n\t\t\ttimestamp: new Date(),\n\t\t});\n\t};\n};\n",
8
+ "import { logger } from '@nxgt/shared-logging';\nimport { z } from 'zod';\n\n// Define schema\nconst envSchema = z.object({\n\t// Node environment\n\tNODE_ENV: z\n\t\t.enum(['development', 'production', 'test'])\n\t\t.default('development'),\n\n\t// Server\n\tPORT: z.coerce.number().default(8080),\n\n\t// Logging\n\tLOG_LEVEL: z.enum(['error', 'warn', 'info', 'debug']).default('debug'),\n});\n\nexport type Env = z.infer<typeof envSchema>;\n\n// Parse and validate environment variables\nconst parseEnv = (value: Record<string, unknown>): Env => {\n\tconst result = envSchema.safeParse(value);\n\n\tif (!result.success) {\n\t\tlogger.error('❌ Invalid environment variables:');\n\t\tlogger.error(result.error.issues);\n\t\tthrow new Error('Invalid environment variables');\n\t}\n\n\treturn result.data;\n};\n\n// Export validated and typed environment variables\nexport const env = parseEnv({\n\tNODE_ENV: Bun.env.NODE_ENV,\n\tPORT: Bun.env.PORT,\n});\n",
9
+ "import { USER_HEADERS } from '@nxgt/shared/models';\nimport { tryGetContext } from 'hono/context-storage';\nimport type { Middleware } from 'openapi-fetch';\n\nexport function openfetchServiceUser(): Middleware {\n\tconst ctx = tryGetContext();\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\tif (ctx?.get(USER_HEADERS.ID)) {\n\t\t\t\trequest.headers.set(USER_HEADERS.ID, ctx.get(USER_HEADERS.ID) || '');\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.USERNAME,\n\t\t\t\t\tctx.get(USER_HEADERS.USERNAME) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.EMAIL,\n\t\t\t\t\tctx.get(USER_HEADERS.EMAIL) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.FIRST_NAME,\n\t\t\t\t\tctx.get(USER_HEADERS.FIRST_NAME) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.LAST_NAME,\n\t\t\t\t\tctx.get(USER_HEADERS.LAST_NAME) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.BIRTH_DATE,\n\t\t\t\t\tctx.get(USER_HEADERS.BIRTH_DATE) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.AUTHORITIES,\n\t\t\t\t\tctx.get(USER_HEADERS.AUTHORITIES)?.join(',') || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.CLIENT,\n\t\t\t\t\tctx.get(USER_HEADERS.CLIENT) || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.ROLES,\n\t\t\t\t\tctx.get(USER_HEADERS.ROLES)?.join(',') || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set(\n\t\t\t\t\tUSER_HEADERS.SCOPES,\n\t\t\t\t\tctx.get(USER_HEADERS.SCOPES)?.join(',') || '',\n\t\t\t\t);\n\t\t\t\trequest.headers.set('X-Service', 'gateway');\n\t\t\t}\n\t\t\treturn request;\n\t\t},\n\t};\n}\n",
10
+ "import { type Principal, USER_HEADERS } from '@nxgt/shared/models';\nimport { CustomException } from '@nxgt/shared-exceptions';\nimport { logger } from '@nxgt/shared-logging';\nimport type { ErrorHandler } from 'hono';\nimport { createMiddleware } from 'hono/factory';\nimport {\n\tbearerOf,\n\ttype Ory,\n\ttype OryPrincipal,\n\tOryUnavailable,\n} from 'stx-sdk/ory';\nimport { env } from '../env';\nimport { principalFromMockHeaders } from '../utils/test.utils';\n\n/**\n * Authentication for an Ory-native API — the twin of storex-api's\n * `remoteAuth()`, with the Ory stack instead of oauth-api as the authority.\n * Same contract, so `policyGuard` and a `rules.yaml` keep answering 401 for\n * `authenticated: true` without knowing which authority signed the caller in:\n *\n * - `NODE_ENV=test` and `X-User-*` headers present ⇒ the mock principal, as\n * every route spec in this repo expects. The Ory principal is synthesised\n * from it so `<module>.access.ts` sees a `subject` either way.\n * - no credential, or one Kratos / Hydra does not honour ⇒ `next()` as an\n * anonymous caller. The rules file decides whether that is a 401.\n * - Kratos, Hydra or Keto unreachable ⇒ **503**, fail closed. Never an\n * anonymous `next()`: that turns an outage into a lockout with no error.\n * - otherwise `principal` (the repo-wide shape), `ory` (the Ory one —\n * `subject` is what Keto receives), `accessToken` (the Bearer, if that is\n * what came in) and `X-Claims`.\n *\n * Takes an `Ory` rather than URLs so the app builds one `createOry()` from\n * its own zod-validated env and shares it with its access layer.\n */\nexport function oryAuth(ory: Ory) {\n\treturn createMiddleware(async (ctx, next) => {\n\t\tif (env.NODE_ENV === 'test') {\n\t\t\tconst mockPrincipal = principalFromMockHeaders(ctx);\n\t\t\tif (mockPrincipal) {\n\t\t\t\tctx.set('principal', mockPrincipal);\n\t\t\t\tctx.set('ory', oryPrincipalFromMock(mockPrincipal));\n\t\t\t\tctx.set(USER_HEADERS.CLAIMS, {\n\t\t\t\t\tsub: mockPrincipal.id,\n\t\t\t\t\tusername: mockPrincipal.username ?? undefined,\n\t\t\t\t\tclientId: mockPrincipal.clientId ?? undefined,\n\t\t\t\t\tauthorities: mockPrincipal.authorities ?? [],\n\t\t\t\t\troles: mockPrincipal.roles ?? [],\n\t\t\t\t\tscope: mockPrincipal.scopes?.join(' '),\n\t\t\t\t});\n\t\t\t\treturn next();\n\t\t\t}\n\t\t}\n\n\t\tlet resolved: OryPrincipal | null;\n\t\ttry {\n\t\t\tresolved = await ory.resolve(ctx.req.raw.headers);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof OryUnavailable)) throw error;\n\t\t\tthrow serviceUnavailable(error);\n\t\t}\n\n\t\tif (!resolved) {\n\t\t\tctx.set('ory', null);\n\t\t\treturn next();\n\t\t}\n\n\t\tconst principal = toPrincipal(resolved);\n\t\tlogger.info(\n\t\t\t`Resolving principal (ory ${resolved.kind}): subject[${resolved.subject}]`,\n\t\t);\n\n\t\tctx.set('principal', principal);\n\t\tctx.set('ory', resolved);\n\t\tctx.set('accessToken', bearerOf(ctx.req.raw.headers));\n\t\tctx.set(USER_HEADERS.CLAIMS, {\n\t\t\tsub: resolved.subject,\n\t\t\tkind: resolved.kind,\n\t\t\temail: resolved.identity?.email,\n\t\t\temail_verified: resolved.identity?.verified,\n\t\t\tclientId: resolved.clientId,\n\t\t\tscope: resolved.scopes.join(' ') || undefined,\n\t\t\taud: resolved.audience,\n\t\t\taal: resolved.aal,\n\t\t\texp: resolved.expiresAt?.toISOString(),\n\t\t});\n\n\t\treturn next();\n\t});\n}\n\n/**\n * The 503 an `OryUnavailable` becomes — for the middleware above and for\n * `withOryUnavailable` below, so a Keto outage in a service's `isAllowed`\n * answers the same thing as a Kratos outage in `resolve`.\n */\nexport function serviceUnavailable(error: OryUnavailable): CustomException {\n\tlogger.error(\n\t\t`Ory ${error.service} unavailable (${error.status}): ${JSON.stringify(error.body)}`,\n\t);\n\treturn CustomException.from({\n\t\tmessage: 'errors.service-unavailable',\n\t\tcode: 503,\n\t\tdebugMessage: `ory: ${error.message}`,\n\t});\n}\n\n/**\n * Wraps the app's error handler so an `OryUnavailable` thrown anywhere\n * below the middleware — an access layer asking Keto, a service listing\n * tuples — is a 503 and not the generic 500. Without it the middleware's\n * own mapping only covers `resolve`, and a Keto restart would surface as\n * \"Internal server error\" from every guarded route:\n *\n * ```ts\n * app.onError(withOryUnavailable(createErrorHandler(translate)));\n * ```\n */\nexport function withOryUnavailable(handler: ErrorHandler): ErrorHandler {\n\treturn (error, ctx) =>\n\t\thandler(\n\t\t\terror instanceof OryUnavailable ? serviceUnavailable(error) : error,\n\t\t\tctx,\n\t\t);\n}\n\n/**\n * The repo-wide `Principal` from an Ory one. `id` is the subject — the\n * identity id or the client id — because `id` is what every existing\n * `ownerId` comparison reads, and a tuple written for `subject` must match\n * an ownership check written for `id`. No authorities and no roles: Keto\n * answers those questions per object, and an empty list is what stops a\n * `@policy`-style check from granting anything by accident.\n */\nexport function toPrincipal(ory: OryPrincipal): Principal {\n\tconst principal: Principal = {\n\t\tid: ory.subject,\n\t\tusername: ory.identity?.email ?? null,\n\t\temail: ory.identity?.email ?? null,\n\t\tfirstName: ory.identity?.name?.first ?? null,\n\t\tlastName: ory.identity?.name?.last ?? null,\n\t\tbirthDate: null,\n\t\tauthorities: [],\n\t\troles: [],\n\t\tclientId: ory.clientId ?? null,\n\t\tscopes: ory.scopes,\n\t};\n\tprincipal.name = principal.username || principal.clientId || undefined;\n\treturn principal;\n}\n\n/** What a route spec's `mockUser()` looks like once it has been through Ory. */\nfunction oryPrincipalFromMock(mock: Principal): OryPrincipal {\n\treturn {\n\t\tsubject: mock.id ?? mock.clientId ?? 'mock',\n\t\tkind: mock.clientId && !mock.id ? 'token' : 'session',\n\t\tidentity: mock.email\n\t\t\t? {\n\t\t\t\t\temail: mock.email,\n\t\t\t\t\tname: {\n\t\t\t\t\t\tfirst: mock.firstName ?? undefined,\n\t\t\t\t\t\tlast: mock.lastName ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t\tverified: true,\n\t\t\t\t}\n\t\t\t: undefined,\n\t\tscopes: mock.scopes ?? [],\n\t\tclientId: mock.clientId ?? undefined,\n\t};\n}\n",
11
+ "import { type Principal, USER_HEADERS } from '@nxgt/shared/models';\nimport { mongoose } from '@nxgt/shared-mongo';\nimport type { Context } from 'hono';\nimport type { Middleware } from 'openapi-fetch';\n\n/**\n * Inverse of `mockAuthMiddleware`: reads the `X-User-*` headers it injects\n * back into a `Principal`. For services that verify tokens themselves\n * (zero-trust, no gateway in front) but still want their existing route\n * specs — written against `mockAuthMiddleware` — to work unchanged in\n * NODE_ENV=test. Returns `undefined` when no mock headers are present, so\n * callers can fall back to real token verification.\n */\nexport function principalFromMockHeaders(ctx: Context): Principal | undefined {\n\tif (\n\t\t!ctx.req.header(USER_HEADERS.ID) &&\n\t\t!ctx.req.header(USER_HEADERS.CLIENT)\n\t) {\n\t\treturn undefined;\n\t}\n\n\tconst principal: Principal = {\n\t\tid: ctx.req.header(USER_HEADERS.ID),\n\t\tusername: ctx.req.header(USER_HEADERS.USERNAME),\n\t\temail: ctx.req.header(USER_HEADERS.EMAIL),\n\t\tfirstName: ctx.req.header(USER_HEADERS.FIRST_NAME),\n\t\tlastName: ctx.req.header(USER_HEADERS.LAST_NAME),\n\t\tbirthDate: ctx.req.header(USER_HEADERS.BIRTH_DATE)\n\t\t\t? new Date(ctx.req.header(USER_HEADERS.BIRTH_DATE) || '')\n\t\t\t: null,\n\t\tauthorities: ctx.req.header(USER_HEADERS.AUTHORITIES)?.split(',') || [],\n\t\tclientId: ctx.req.header(USER_HEADERS.CLIENT) || null,\n\t\troles: ctx.req.header(USER_HEADERS.ROLES)?.split(',') || [],\n\t\tscopes: ctx.req.header(USER_HEADERS.SCOPES)?.split(',') || [],\n\t};\n\tprincipal.name = principal.username || principal.clientId || undefined;\n\treturn principal;\n}\n\nexport function mockUser(\n\tvalues: Omit<Principal, 'authorities' | 'id'> & {\n\t\tpermissions?: string[];\n\t\troles?: string[];\n\t},\n): Principal {\n\treturn {\n\t\tid: new mongoose.mongo.ObjectId().toHexString(),\n\t\tauthorities: [...(values.roles ?? []), ...(values.permissions ?? [])],\n\t\tname: values.username || undefined,\n\t\t...values,\n\t};\n}\n\nexport function mockAuthMiddleware(user: Principal): Middleware {\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\trequest.headers.set(USER_HEADERS.ID, user.id || '');\n\t\t\trequest.headers.set(USER_HEADERS.USERNAME, user.username || '');\n\t\t\trequest.headers.set(USER_HEADERS.EMAIL, user.email || '');\n\t\t\trequest.headers.set(\n\t\t\t\tUSER_HEADERS.AUTHORITIES,\n\t\t\t\tuser.authorities?.join(',') || '',\n\t\t\t);\n\t\t\trequest.headers.set(\n\t\t\t\tUSER_HEADERS.BIRTH_DATE,\n\t\t\t\tuser.birthDate?.toISOString() ?? '',\n\t\t\t);\n\t\t\trequest.headers.set(USER_HEADERS.FIRST_NAME, user.firstName ?? '');\n\t\t\trequest.headers.set(USER_HEADERS.LAST_NAME, user.lastName ?? '');\n\t\t\treturn request;\n\t\t},\n\t};\n}\n\n/**\n * openapi-fetch middleware that turns a `POST <resource>/search` call into the\n * `QUERY <resource>` it mirrors — same body, same response, safe method.\n *\n * The generated client cannot express QUERY: `openapi-typescript` has no\n * `query` path-item key (its method list is the eight classic verbs), so the\n * operation is invisible to the `paths` type, and `openapi-fetch`'s own\n * `request()` is constrained to `HttpMethod`. Call the POST search operation\n * for the types — request and response are identical either way — and let this\n * rewrite the verb *and* drop the `/search` suffix on the wire:\n *\n * ```ts\n * client.use(mockAuthMiddleware(principal));\n * const { data, error } = await client.POST('/tags/search', {\n * body: {},\n * middleware: [asQueryMethod],\n * }); // actually sends: QUERY /tags\n * ```\n *\n * Per-request middleware runs after the ones registered with `client.use()`,\n * so headers set by `mockAuthMiddleware` are already on the request and are\n * carried over. The body is read to a string rather than passed as a stream:\n * a streaming body would need `duplex: 'half'`, and these are small JSON\n * payloads.\n */\nexport const asQueryMethod: Middleware = {\n\tonRequest: async ({ request }) => {\n\t\tconst url = new URL(request.url);\n\t\turl.pathname = url.pathname.replace(/\\/search$/, '');\n\t\tconst body = await request.text();\n\t\treturn new Request(url, {\n\t\t\tmethod: 'QUERY',\n\t\t\theaders: request.headers,\n\t\t\tbody: body || undefined,\n\t\t});\n\t},\n};\n",
12
+ "import { logger } from '@nxgt/shared-logging';\nimport { Redis as UpstashRedis } from '@upstash/redis';\nimport {\n\ttype HonoConfigProps,\n\trateLimiter as honoRateLimiter,\n\ttype RedisClient,\n\tRedisStore,\n} from 'hono-rate-limiter';\nimport IORedis from 'ioredis';\n\nexport type RateLimiterOptions = Omit<HonoConfigProps, 'keyGenerator'> & {\n\t/**\n\t * Either a standard `redis://`/`rediss://` connection string (backed by\n\t * `ioredis`, for a self-hosted Redis) or an Upstash REST URL\n\t * (`https://...`, backed by `@upstash/redis`, requires `redisToken`).\n\t */\n\tredisUrl?: string;\n\t/** Upstash REST token — only used when `redisUrl` is an Upstash URL. */\n\tredisToken?: string;\n\t/**\n\t * Redis key prefix for this limiter's counters. `RedisStore` defaults to\n\t * a fixed `\"hrl:\"` prefix, so two `rateLimiter()` instances pointed at\n\t * the same Redis (e.g. a global limiter and a stricter per-route one)\n\t * silently share counters unless given distinct prefixes here.\n\t */\n\tprefix?: string;\n\tkeyGenerator?: HonoConfigProps['keyGenerator'];\n};\n\n/**\n * Adapts a standard `ioredis` client to the bespoke `RedisClient` shape\n * `hono-rate-limiter`'s `RedisStore` expects (mirroring what `@upstash/redis`\n * natively implements), so a plain self-hosted Redis (`redis://...`) can back\n * the same `RedisStore` used for Upstash.\n */\nfunction ioredisAdapter(client: IORedis): RedisClient {\n\treturn {\n\t\tscriptLoad: (script) => client.script('LOAD', script) as Promise<string>,\n\t\tevalsha: (sha1, keys, args) =>\n\t\t\tclient.evalsha(sha1, keys.length, ...keys, ...(args as string[])) as any,\n\t\tdecr: (key) => client.decr(key),\n\t\tdel: (key) => client.del(key),\n\t};\n}\n\nfunction redisClientFor(redisUrl: string, redisToken?: string): RedisClient {\n\tif (redisUrl.startsWith('redis://') || redisUrl.startsWith('rediss://')) {\n\t\tconst client = new IORedis(redisUrl);\n\t\tclient.on('error', (err) => {\n\t\t\tlogger.error(`[rate-limiter] Redis connection error: ${err.message}`);\n\t\t});\n\t\treturn ioredisAdapter(client);\n\t}\n\treturn new UpstashRedis({\n\t\turl: redisUrl,\n\t\ttoken: redisToken,\n\t}) as unknown as RedisClient;\n}\n\nexport function rateLimiter(options?: RateLimiterOptions) {\n\treturn honoRateLimiter({\n\t\t...options,\n\t\twindowMs: options?.windowMs ?? 1 * 60 * 1000,\n\t\tlimit: options?.limit ?? 10,\n\t\tkeyGenerator:\n\t\t\toptions?.keyGenerator ?? ((c) => c.req.header('x-forwarded-for') ?? ''),\n\t\tstore: options?.redisUrl\n\t\t\t? new RedisStore({\n\t\t\t\t\tclient: redisClientFor(options.redisUrl, options.redisToken),\n\t\t\t\t\tprefix: options.prefix,\n\t\t\t\t})\n\t\t\t: undefined,\n\t});\n}\n",
13
+ "import { isScopeAuthority } from '@nxgt/shared/helpers';\nimport { CustomException } from '@nxgt/shared-exceptions';\nimport { getLogger } from '@nxgt/shared-logging';\nimport { createMiddleware } from 'hono/factory';\nimport type { MiddlewareHandler } from 'hono/types';\n\n/**\n * Route guard middleware following Apollo Federation requireScopes semantics.\n *\n * `authorities` is an **array of groups** (array of arrays):\n * - Outer array = AND — every group must be satisfied.\n * - Inner array = OR — at least one authority in the group must match.\n *\n * Examples:\n * secured() — authentication check only\n * secured([['ADMIN', 'users:read']]) — ADMIN or users:read\n * secured([['ADMIN'], ['users:read']]) — ADMIN and users:read\n *\n * For confidential-client principals (clientId present, no username) only\n * SCOPE_* authorities are considered — role/permission entries are ignored.\n */\nexport function secured(authorities: string[][] = []): MiddlewareHandler {\n\treturn createMiddleware(async (ctx, next) => {\n\t\tconst logger = ctx.get('logger') || getLogger();\n\n\t\tlogger.info('Secured middleware: checking authorities');\n\t\tconst user = ctx.get('principal');\n\n\t\tif (!user) {\n\t\t\tlogger.error('Unauthenticated access attempt');\n\t\t\tthrow CustomException.unauthorized({\n\t\t\t\tmessage: 'errors.unauthenticated',\n\t\t\t});\n\t\t}\n\n\t\tif (user.roles?.includes('ADMIN')) {\n\t\t\treturn next();\n\t\t}\n\n\t\tlogger.info(\n\t\t\t`User authenticated: ${user.name ?? user.username ?? user.clientId}`,\n\t\t);\n\n\t\tif (!authorities.length) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// For confidential clients, only SCOPE_* authorities are considered —\n\t\t// role/permission entries are ignored. If the client lacks the required\n\t\t// scope authority it is denied.\n\t\tconst isClient = !!user.clientId && !user.username;\n\t\tconst userAuthorities = user.authorities ?? [];\n\t\tconst effectiveUserAuthorities = isClient\n\t\t\t? userAuthorities.filter(isScopeAuthority)\n\t\t\t: userAuthorities;\n\n\t\tconst granted = authorities.every((group) => {\n\t\t\tif (group.length === 0) return true;\n\t\t\treturn group.some((authority) =>\n\t\t\t\teffectiveUserAuthorities.includes(authority),\n\t\t\t);\n\t\t});\n\n\t\tif (granted) {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\n\t\tthrow CustomException.forbidden({\n\t\t\tmessage: 'errors.forbidden',\n\t\t});\n\t});\n}\n"
14
+ ],
15
+ "mappings": "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",
16
+ "debugId": "EC87DAEAD6B394CA64756E2164756E21",
17
+ "names": []
18
+ }
@@ -0,0 +1,3 @@
1
+ import { type McpServer, type WebStandardStreamableHTTPServerTransportOptions } from '@modelcontextprotocol/server';
2
+ export declare function createMcpServerApp(server: McpServer, options?: WebStandardStreamableHTTPServerTransportOptions): import("hono").Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/mcp/helpers.ts"],"names":[],"mappings":"AACA,OAAO,EACN,KAAK,SAAS,EAEd,KAAK,+CAA+C,EACpD,MAAM,8BAA8B,CAAC;AActC,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,SAAS,EACjB,OAAO,CAAC,EAAE,+CAA+C,6FAqCzD"}
@@ -0,0 +1,4 @@
1
+ export * from '@modelcontextprotocol/hono';
2
+ export * from '@modelcontextprotocol/server';
3
+ export * from './helpers';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,WAAW,CAAC"}
@@ -0,0 +1,47 @@
1
+ // src/mcp/index.ts
2
+ export * from "@modelcontextprotocol/hono";
3
+ export * from "@modelcontextprotocol/server";
4
+
5
+ // src/mcp/helpers.ts
6
+ import { createMcpHonoApp } from "@modelcontextprotocol/hono";
7
+ import {
8
+ WebStandardStreamableHTTPServerTransport
9
+ } from "@modelcontextprotocol/server";
10
+ import { createAuthClient } from "stx-sdk/auth";
11
+ var auth = createAuthClient("http://localhost:8080/api");
12
+ function createMcpServerApp(server, options) {
13
+ const app = createMcpHonoApp();
14
+ const transport = new WebStandardStreamableHTTPServerTransport({
15
+ ...options,
16
+ sessionIdGenerator: options?.sessionIdGenerator || (() => Bun.randomUUIDv7())
17
+ });
18
+ app.all("/mcp", async (c) => {
19
+ if (!server.isConnected()) {
20
+ await server.connect(transport);
21
+ }
22
+ const authHeader = c.req.header("Authorization");
23
+ const result = await auth.POST("/oauth/introspect", {
24
+ body: { token: authHeader?.split(" ")[1] ?? "" }
25
+ });
26
+ if (!result.data?.active) {
27
+ c.status(401);
28
+ return c.json({ error: "MCP Unauthorized" });
29
+ }
30
+ return transport.handleRequest(c.req.raw, {
31
+ authInfo: {
32
+ scopes: result.data.scope?.split(" ") ?? [],
33
+ clientId: result.data.clientId ?? "",
34
+ token: authHeader?.split(" ")[1] ?? "",
35
+ extra: result.data
36
+ },
37
+ parsedBody: c.get("parsedBody")
38
+ });
39
+ });
40
+ return app;
41
+ }
42
+ export {
43
+ createMcpServerApp
44
+ };
45
+
46
+ //# debugId=4B556E93F95CB6CA64756E2164756E21
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/mcp/index.ts", "../src/mcp/helpers.ts"],
4
+ "sourcesContent": [
5
+ "// The two star re-exports live here, in the entry point, and not in\n// `helpers.ts`: Bun emits `__reExport(ns, hono)` with `hono` undeclared when a\n// star re-export of an external package sits below the entry. See AGENTS.md.\nexport * from '@modelcontextprotocol/hono';\nexport * from '@modelcontextprotocol/server';\nexport * from './helpers';\n",
6
+ "import { createMcpHonoApp } from '@modelcontextprotocol/hono';\nimport {\n\ttype McpServer,\n\tWebStandardStreamableHTTPServerTransport,\n\ttype WebStandardStreamableHTTPServerTransportOptions,\n} from '@modelcontextprotocol/server';\nimport type { Context } from 'hono';\n\nimport { createAuthClient } from 'stx-sdk/auth';\n\n// Not exported: nothing outside this module used it, and its inferred type\n// reaches openapi-fetch through stx-sdk's own node_modules — a path that does\n// not exist for anyone installing this package, which `tsc` refuses to write\n// into a declaration (TS2883).\n//\n// The base URL is hardcoded, which is wrong for a shared package; left as it\n// was rather than changed silently during the merge.\nconst auth = createAuthClient('http://localhost:8080/api');\n\nexport function createMcpServerApp(\n\tserver: McpServer,\n\toptions?: WebStandardStreamableHTTPServerTransportOptions,\n) {\n\tconst app = createMcpHonoApp();\n\n\tconst transport = new WebStandardStreamableHTTPServerTransport({\n\t\t...options,\n\t\tsessionIdGenerator:\n\t\t\toptions?.sessionIdGenerator || (() => Bun.randomUUIDv7()),\n\t});\n\n\tapp.all('/mcp', async (c) => {\n\t\tif (!server.isConnected()) {\n\t\t\tawait server.connect(transport);\n\t\t}\n\n\t\tconst authHeader = c.req.header('Authorization');\n\n\t\tconst result = await auth.POST('/oauth/introspect', {\n\t\t\tbody: { token: authHeader?.split(' ')[1] ?? '' },\n\t\t});\n\n\t\tif (!result.data?.active) {\n\t\t\tc.status(401);\n\t\t\treturn c.json({ error: 'MCP Unauthorized' });\n\t\t}\n\n\t\treturn transport.handleRequest(c.req.raw, {\n\t\t\tauthInfo: {\n\t\t\t\tscopes: result.data.scope?.split(' ') ?? [],\n\t\t\t\tclientId: result.data.clientId ?? '',\n\t\t\t\ttoken: authHeader?.split(' ')[1] ?? '',\n\t\t\t\textra: result.data,\n\t\t\t},\n\t\t\tparsedBody: (c as Context).get('parsedBody'),\n\t\t});\n\t});\n\treturn app;\n}\n"
7
+ ],
8
+ "mappings": ";AAGA;AACA;;;ACJA;AACA;AAAA;AAAA;AAOA;AASA,IAAM,OAAO,iBAAiB,2BAA2B;AAElD,SAAS,kBAAkB,CACjC,QACA,SACC;AAAA,EACD,MAAM,MAAM,iBAAiB;AAAA,EAE7B,MAAM,YAAY,IAAI,yCAAyC;AAAA,OAC3D;AAAA,IACH,oBACC,SAAS,uBAAuB,MAAM,IAAI,aAAa;AAAA,EACzD,CAAC;AAAA,EAED,IAAI,IAAI,QAAQ,OAAO,MAAM;AAAA,IAC5B,IAAI,CAAC,OAAO,YAAY,GAAG;AAAA,MAC1B,MAAM,OAAO,QAAQ,SAAS;AAAA,IAC/B;AAAA,IAEA,MAAM,aAAa,EAAE,IAAI,OAAO,eAAe;AAAA,IAE/C,MAAM,SAAS,MAAM,KAAK,KAAK,qBAAqB;AAAA,MACnD,MAAM,EAAE,OAAO,YAAY,MAAM,GAAG,EAAE,MAAM,GAAG;AAAA,IAChD,CAAC;AAAA,IAED,IAAI,CAAC,OAAO,MAAM,QAAQ;AAAA,MACzB,EAAE,OAAO,GAAG;AAAA,MACZ,OAAO,EAAE,KAAK,EAAE,OAAO,mBAAmB,CAAC;AAAA,IAC5C;AAAA,IAEA,OAAO,UAAU,cAAc,EAAE,IAAI,KAAK;AAAA,MACzC,UAAU;AAAA,QACT,QAAQ,OAAO,KAAK,OAAO,MAAM,GAAG,KAAK,CAAC;AAAA,QAC1C,UAAU,OAAO,KAAK,YAAY;AAAA,QAClC,OAAO,YAAY,MAAM,GAAG,EAAE,MAAM;AAAA,QACpC,OAAO,OAAO;AAAA,MACf;AAAA,MACA,YAAa,EAAc,IAAI,YAAY;AAAA,IAC5C,CAAC;AAAA,GACD;AAAA,EACD,OAAO;AAAA;",
9
+ "debugId": "4B556E93F95CB6CA64756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,16 @@
1
+ /** Media types accepted in a QUERY request body, advertised by `acceptQuery()`. */
2
+ export declare const ACCEPT_QUERY_MEDIA_TYPE = "application/json";
3
+ /**
4
+ * Advertises QUERY support on a resource.
5
+ *
6
+ * `Accept-Query` is how the safe-method-with-body draft says a resource
7
+ * announces both that it answers QUERY *and* which media types it will accept
8
+ * in the request content. It rides on the QUERY registration only: the
9
+ * `POST …/search` route it shares its handlers with is left exactly as it was,
10
+ * headers included, so that every existing caller sees no change at all.
11
+ *
12
+ * Set after `next()` so it lands on whatever the handler produced, errors
13
+ * included.
14
+ */
15
+ export declare function acceptQuery(mediaTypes?: string): import("hono").MiddlewareHandler<any, string, {}, Response>;
16
+ //# sourceMappingURL=accept-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accept-query.d.ts","sourceRoot":"","sources":["../../src/middlewares/accept-query.ts"],"names":[],"mappings":"AAEA,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,UAAU,GAAE,MAAgC,+DAKvE"}
@@ -0,0 +1,5 @@
1
+ import { type Principal, USER_HEADERS } from '@nxgt/shared/models';
2
+ import type { MiddlewareHandler } from 'hono/types';
3
+ export declare const currentUser: () => MiddlewareHandler;
4
+ export { type Principal, USER_HEADERS };
5
+ //# sourceMappingURL=current-user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"current-user.d.ts","sourceRoot":"","sources":["../../src/middlewares/current-user.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,eAAO,MAAM,WAAW,QAAO,iBAuD5B,CAAC;AAEJ,OAAO,EAAE,KAAK,SAAS,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { LocaleKey } from '@nxgt/i18n';
2
+ import type { ErrorHandler } from 'hono';
3
+ export type ErrorHandlerOptions = {
4
+ showStackInDev?: boolean;
5
+ showStackInTest?: boolean;
6
+ logToConsole?: boolean;
7
+ };
8
+ export declare const createErrorHandler: <K extends LocaleKey = LocaleKey>(translate: (key: K, context?: Record<string, any>) => string, { showStackInDev, showStackInTest, logToConsole, }?: ErrorHandlerOptions) => ErrorHandler;
9
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middlewares/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIzC,MAAM,MAAM,mBAAmB,GAAG;IACjC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,CAAC,SAAS,SAAS,GAAG,SAAS,EACjE,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,EAC5D,qDAIG,mBAAwB,KACzB,YAwCF,CAAC"}
@@ -0,0 +1,8 @@
1
+ export * from './accept-query';
2
+ export * from './current-user';
3
+ export * from './error-handler';
4
+ export * from './openfetch-service-user';
5
+ export * from './ory-auth';
6
+ export * from './rate-limiter';
7
+ export * from './secured';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middlewares/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Middleware } from 'openapi-fetch';
2
+ export declare function openfetchServiceUser(): Middleware;
3
+ //# sourceMappingURL=openfetch-service-user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openfetch-service-user.d.ts","sourceRoot":"","sources":["../../src/middlewares/openfetch-service-user.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,wBAAgB,oBAAoB,IAAI,UAAU,CA+CjD"}
@@ -0,0 +1,53 @@
1
+ import { type Principal } from '@nxgt/shared/models';
2
+ import { CustomException } from '@nxgt/shared-exceptions';
3
+ import type { ErrorHandler } from 'hono';
4
+ import { type Ory, type OryPrincipal, OryUnavailable } from 'stx-sdk/ory';
5
+ /**
6
+ * Authentication for an Ory-native API — the twin of storex-api's
7
+ * `remoteAuth()`, with the Ory stack instead of oauth-api as the authority.
8
+ * Same contract, so `policyGuard` and a `rules.yaml` keep answering 401 for
9
+ * `authenticated: true` without knowing which authority signed the caller in:
10
+ *
11
+ * - `NODE_ENV=test` and `X-User-*` headers present ⇒ the mock principal, as
12
+ * every route spec in this repo expects. The Ory principal is synthesised
13
+ * from it so `<module>.access.ts` sees a `subject` either way.
14
+ * - no credential, or one Kratos / Hydra does not honour ⇒ `next()` as an
15
+ * anonymous caller. The rules file decides whether that is a 401.
16
+ * - Kratos, Hydra or Keto unreachable ⇒ **503**, fail closed. Never an
17
+ * anonymous `next()`: that turns an outage into a lockout with no error.
18
+ * - otherwise `principal` (the repo-wide shape), `ory` (the Ory one —
19
+ * `subject` is what Keto receives), `accessToken` (the Bearer, if that is
20
+ * what came in) and `X-Claims`.
21
+ *
22
+ * Takes an `Ory` rather than URLs so the app builds one `createOry()` from
23
+ * its own zod-validated env and shares it with its access layer.
24
+ */
25
+ export declare function oryAuth(ory: Ory): import("hono").MiddlewareHandler<any, string, {}, Response>;
26
+ /**
27
+ * The 503 an `OryUnavailable` becomes — for the middleware above and for
28
+ * `withOryUnavailable` below, so a Keto outage in a service's `isAllowed`
29
+ * answers the same thing as a Kratos outage in `resolve`.
30
+ */
31
+ export declare function serviceUnavailable(error: OryUnavailable): CustomException;
32
+ /**
33
+ * Wraps the app's error handler so an `OryUnavailable` thrown anywhere
34
+ * below the middleware — an access layer asking Keto, a service listing
35
+ * tuples — is a 503 and not the generic 500. Without it the middleware's
36
+ * own mapping only covers `resolve`, and a Keto restart would surface as
37
+ * "Internal server error" from every guarded route:
38
+ *
39
+ * ```ts
40
+ * app.onError(withOryUnavailable(createErrorHandler(translate)));
41
+ * ```
42
+ */
43
+ export declare function withOryUnavailable(handler: ErrorHandler): ErrorHandler;
44
+ /**
45
+ * The repo-wide `Principal` from an Ory one. `id` is the subject — the
46
+ * identity id or the client id — because `id` is what every existing
47
+ * `ownerId` comparison reads, and a tuple written for `subject` must match
48
+ * an ownership check written for `id`. No authorities and no roles: Keto
49
+ * answers those questions per object, and an empty list is what stops a
50
+ * `@policy`-style check from granting anything by accident.
51
+ */
52
+ export declare function toPrincipal(ory: OryPrincipal): Principal;
53
+ //# sourceMappingURL=ory-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ory-auth.d.ts","sourceRoot":"","sources":["../../src/middlewares/ory-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,EAEN,KAAK,GAAG,EACR,KAAK,YAAY,EACjB,cAAc,EACd,MAAM,aAAa,CAAC;AAIrB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,+DAsD/B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CASzE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAMtE;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,CAexD"}
@@ -0,0 +1,21 @@
1
+ import { type HonoConfigProps } from 'hono-rate-limiter';
2
+ export type RateLimiterOptions = Omit<HonoConfigProps, 'keyGenerator'> & {
3
+ /**
4
+ * Either a standard `redis://`/`rediss://` connection string (backed by
5
+ * `ioredis`, for a self-hosted Redis) or an Upstash REST URL
6
+ * (`https://...`, backed by `@upstash/redis`, requires `redisToken`).
7
+ */
8
+ redisUrl?: string;
9
+ /** Upstash REST token — only used when `redisUrl` is an Upstash URL. */
10
+ redisToken?: string;
11
+ /**
12
+ * Redis key prefix for this limiter's counters. `RedisStore` defaults to
13
+ * a fixed `"hrl:"` prefix, so two `rateLimiter()` instances pointed at
14
+ * the same Redis (e.g. a global limiter and a stricter per-route one)
15
+ * silently share counters unless given distinct prefixes here.
16
+ */
17
+ prefix?: string;
18
+ keyGenerator?: HonoConfigProps['keyGenerator'];
19
+ };
20
+ export declare function rateLimiter(options?: RateLimiterOptions): import("hono").MiddlewareHandler<import("hono").Env, string, import("hono").Input, Response>;
21
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/middlewares/rate-limiter.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,eAAe,EAIpB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,GAAG;IACxE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,eAAe,CAAC,cAAc,CAAC,CAAC;CAC/C,CAAC;AAgCF,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,gGAcvD"}
@@ -0,0 +1,18 @@
1
+ import type { MiddlewareHandler } from 'hono/types';
2
+ /**
3
+ * Route guard middleware following Apollo Federation requireScopes semantics.
4
+ *
5
+ * `authorities` is an **array of groups** (array of arrays):
6
+ * - Outer array = AND — every group must be satisfied.
7
+ * - Inner array = OR — at least one authority in the group must match.
8
+ *
9
+ * Examples:
10
+ * secured() — authentication check only
11
+ * secured([['ADMIN', 'users:read']]) — ADMIN or users:read
12
+ * secured([['ADMIN'], ['users:read']]) — ADMIN and users:read
13
+ *
14
+ * For confidential-client principals (clientId present, no username) only
15
+ * SCOPE_* authorities are considered — role/permission entries are ignored.
16
+ */
17
+ export declare function secured(authorities?: string[][]): MiddlewareHandler;
18
+ //# sourceMappingURL=secured.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secured.d.ts","sourceRoot":"","sources":["../../src/middlewares/secured.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,WAAW,GAAE,MAAM,EAAE,EAAO,GAAG,iBAAiB,CAmDvE"}
@@ -0,0 +1,4 @@
1
+ import createClient from 'openapi-fetch';
2
+ export * from 'openapi-fetch';
3
+ export default createClient;
4
+ //# sourceMappingURL=openapi-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi-fetch.d.ts","sourceRoot":"","sources":["../src/openapi-fetch.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC,cAAc,eAAe,CAAC;AAE9B,eAAe,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ // src/openapi-fetch.ts
2
+ import createClient from "openapi-fetch";
3
+
4
+ export * from "openapi-fetch";
5
+ var openapi_fetch_default = createClient;
6
+ export {
7
+ openapi_fetch_default as default
8
+ };
9
+
10
+ //# debugId=925B8E246A2C7E0D64756E2164756E21
11
+ //# sourceMappingURL=openapi-fetch.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/openapi-fetch.ts"],
4
+ "sourcesContent": [
5
+ "import createClient from 'openapi-fetch';\n\nexport * from 'openapi-fetch';\n\nexport default createClient;\n"
6
+ ],
7
+ "mappings": ";AAAA;AAAA;AAEA;AAEA,IAAe;",
8
+ "debugId": "925B8E246A2C7E0D64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,27 @@
1
+ import type { Principal } from '@nxgt/shared/models';
2
+ import type { OryPrincipal } from 'stx-sdk/ory';
3
+
4
+ declare module 'hono' {
5
+ interface ContextVariableMap {
6
+ principal?: Principal | null;
7
+ /** The Bearer token the caller sent, when that is how they signed in. */
8
+ accessToken?: string | null;
9
+ /**
10
+ * Set by `oryAuth()`: the Ory principal, `null` for an anonymous
11
+ * caller. `ory.subject` is the string Keto receives.
12
+ */
13
+ ory?: OryPrincipal | null;
14
+ 'X-User-Id'?: string | null;
15
+ 'X-User-Name'?: string | null;
16
+ 'X-User-Email'?: string | null;
17
+ 'X-User-Firstname'?: string | null;
18
+ 'X-User-Lastname'?: string | null;
19
+ 'X-User-Birthdate'?: string | null;
20
+ 'X-User-Authorities'?: string[] | null;
21
+ 'X-Roles'?: string[] | null;
22
+ 'X-Realm'?: string | null;
23
+ 'X-Scopes'?: string[] | null;
24
+ 'X-Client-Id'?: string | null;
25
+ 'X-Claims'?: Record<string, any> | null;
26
+ }
27
+ }
@@ -0,0 +1,2 @@
1
+ import './hono.d.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './test.utils';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { type Principal } from '@nxgt/shared/models';
2
+ import type { Context } from 'hono';
3
+ import type { Middleware } from 'openapi-fetch';
4
+ /**
5
+ * Inverse of `mockAuthMiddleware`: reads the `X-User-*` headers it injects
6
+ * back into a `Principal`. For services that verify tokens themselves
7
+ * (zero-trust, no gateway in front) but still want their existing route
8
+ * specs — written against `mockAuthMiddleware` — to work unchanged in
9
+ * NODE_ENV=test. Returns `undefined` when no mock headers are present, so
10
+ * callers can fall back to real token verification.
11
+ */
12
+ export declare function principalFromMockHeaders(ctx: Context): Principal | undefined;
13
+ export declare function mockUser(values: Omit<Principal, 'authorities' | 'id'> & {
14
+ permissions?: string[];
15
+ roles?: string[];
16
+ }): Principal;
17
+ export declare function mockAuthMiddleware(user: Principal): Middleware;
18
+ /**
19
+ * openapi-fetch middleware that turns a `POST <resource>/search` call into the
20
+ * `QUERY <resource>` it mirrors — same body, same response, safe method.
21
+ *
22
+ * The generated client cannot express QUERY: `openapi-typescript` has no
23
+ * `query` path-item key (its method list is the eight classic verbs), so the
24
+ * operation is invisible to the `paths` type, and `openapi-fetch`'s own
25
+ * `request()` is constrained to `HttpMethod`. Call the POST search operation
26
+ * for the types — request and response are identical either way — and let this
27
+ * rewrite the verb *and* drop the `/search` suffix on the wire:
28
+ *
29
+ * ```ts
30
+ * client.use(mockAuthMiddleware(principal));
31
+ * const { data, error } = await client.POST('/tags/search', {
32
+ * body: {},
33
+ * middleware: [asQueryMethod],
34
+ * }); // actually sends: QUERY /tags
35
+ * ```
36
+ *
37
+ * Per-request middleware runs after the ones registered with `client.use()`,
38
+ * so headers set by `mockAuthMiddleware` are already on the request and are
39
+ * carried over. The body is read to a string rather than passed as a stream:
40
+ * a streaming body would need `duplex: 'half'`, and these are small JSON
41
+ * payloads.
42
+ */
43
+ export declare const asQueryMethod: Middleware;
44
+ //# sourceMappingURL=test.utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.utils.d.ts","sourceRoot":"","sources":["../../src/utils/test.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,qBAAqB,CAAC;AAEnE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAwB5E;AAED,wBAAgB,QAAQ,CACvB,MAAM,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC,GAAG;IAC/C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB,GACC,SAAS,CAOX;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAmB9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,EAAE,UAW3B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@nxgt/shared-hono",
3
+ "version": "1.0.0",
4
+ "license": "UNLICENSED",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "package.json"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./openapi-fetch": {
20
+ "types": "./dist/openapi-fetch.d.ts",
21
+ "import": "./dist/openapi-fetch.js",
22
+ "default": "./dist/openapi-fetch.js"
23
+ },
24
+ "./mcp": {
25
+ "types": "./dist/mcp/index.d.ts",
26
+ "import": "./dist/mcp/index.js",
27
+ "default": "./dist/mcp/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/softistx/nxgt-core.git",
34
+ "directory": "packages/shared-hono"
35
+ },
36
+ "publishConfig": {
37
+ "registry": "https://registry.npmjs.org",
38
+ "access": "public"
39
+ },
40
+ "scripts": {
41
+ "build": "bun run ../../build.ts",
42
+ "typecheck": "tsc --noEmit"
43
+ },
44
+ "nxgt": {
45
+ "entrypoints": [
46
+ "src/index.ts",
47
+ "src/openapi-fetch.ts",
48
+ "src/mcp/index.ts"
49
+ ]
50
+ },
51
+ "dependencies": {
52
+ "@hono/zod-validator": "^0.9.0",
53
+ "@modelcontextprotocol/hono": "^2.0.0",
54
+ "@modelcontextprotocol/server": "^2.0.0",
55
+ "@nxgt/i18n": "1.0.0",
56
+ "@nxgt/shared": "1.0.0",
57
+ "@nxgt/shared-exceptions": "1.0.0",
58
+ "@nxgt/shared-logging": "1.0.0",
59
+ "@nxgt/shared-mongo": "1.0.0",
60
+ "@upstash/redis": "^1.38.2",
61
+ "hono": "^4.13.4",
62
+ "hono-rate-limiter": "^0.5.3",
63
+ "ioredis": "^6.0.0",
64
+ "lodash": "^4.18.1",
65
+ "openapi-fetch": "^0.17.0",
66
+ "zod": "^4.4.3"
67
+ },
68
+ "devDependencies": {
69
+ "@types/bun": "^1.4.0",
70
+ "@types/lodash": "^4.17.25",
71
+ "jose": "^6.2.9"
72
+ },
73
+ "peerDependencies": {
74
+ "stx-sdk": ">=1.0.0",
75
+ "typescript": "^6.0.3"
76
+ }
77
+ }