@aruvili/api 0.1.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 (158) hide show
  1. package/dist/config.d.ts +22 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +34 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/context.d.ts +7 -0
  6. package/dist/context.d.ts.map +1 -0
  7. package/dist/context.js +3 -0
  8. package/dist/context.js.map +1 -0
  9. package/dist/controllers/index.d.ts +39 -0
  10. package/dist/controllers/index.d.ts.map +1 -0
  11. package/dist/controllers/index.js +39 -0
  12. package/dist/controllers/index.js.map +1 -0
  13. package/dist/db.d.ts +6 -0
  14. package/dist/db.d.ts.map +1 -0
  15. package/dist/db.js +74 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/index.d.ts +17 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +154 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/middleware/auth.d.ts +15 -0
  22. package/dist/middleware/auth.d.ts.map +1 -0
  23. package/dist/middleware/auth.js +93 -0
  24. package/dist/middleware/auth.js.map +1 -0
  25. package/dist/middleware/body-limit.d.ts +9 -0
  26. package/dist/middleware/body-limit.d.ts.map +1 -0
  27. package/dist/middleware/body-limit.js +15 -0
  28. package/dist/middleware/body-limit.js.map +1 -0
  29. package/dist/middleware/rate-limit.d.ts +6 -0
  30. package/dist/middleware/rate-limit.d.ts.map +1 -0
  31. package/dist/middleware/rate-limit.js +40 -0
  32. package/dist/middleware/rate-limit.js.map +1 -0
  33. package/dist/middleware/rbac.d.ts +10 -0
  34. package/dist/middleware/rbac.d.ts.map +1 -0
  35. package/dist/middleware/rbac.js +61 -0
  36. package/dist/middleware/rbac.js.map +1 -0
  37. package/dist/middleware/tenant.d.ts +3 -0
  38. package/dist/middleware/tenant.d.ts.map +1 -0
  39. package/dist/middleware/tenant.js +19 -0
  40. package/dist/middleware/tenant.js.map +1 -0
  41. package/dist/registry.d.ts +26 -0
  42. package/dist/registry.d.ts.map +1 -0
  43. package/dist/registry.js +112 -0
  44. package/dist/registry.js.map +1 -0
  45. package/dist/routes/auth.d.ts +3 -0
  46. package/dist/routes/auth.d.ts.map +1 -0
  47. package/dist/routes/auth.js +141 -0
  48. package/dist/routes/auth.js.map +1 -0
  49. package/dist/routes/crud.d.ts +7 -0
  50. package/dist/routes/crud.d.ts.map +1 -0
  51. package/dist/routes/crud.js +845 -0
  52. package/dist/routes/crud.js.map +1 -0
  53. package/dist/routes/files.d.ts +7 -0
  54. package/dist/routes/files.d.ts.map +1 -0
  55. package/dist/routes/files.js +123 -0
  56. package/dist/routes/files.js.map +1 -0
  57. package/dist/routes/meta.d.ts +3 -0
  58. package/dist/routes/meta.d.ts.map +1 -0
  59. package/dist/routes/meta.js +352 -0
  60. package/dist/routes/meta.js.map +1 -0
  61. package/dist/scheduler.d.ts +33 -0
  62. package/dist/scheduler.d.ts.map +1 -0
  63. package/dist/scheduler.js +97 -0
  64. package/dist/scheduler.js.map +1 -0
  65. package/dist/utils/link-validator.d.ts +7 -0
  66. package/dist/utils/link-validator.d.ts.map +1 -0
  67. package/dist/utils/link-validator.js +33 -0
  68. package/dist/utils/link-validator.js.map +1 -0
  69. package/dist/utils/resolver.d.ts +5 -0
  70. package/dist/utils/resolver.d.ts.map +1 -0
  71. package/dist/utils/resolver.js +58 -0
  72. package/dist/utils/resolver.js.map +1 -0
  73. package/package.json +24 -0
  74. package/src/api.test.ts +362 -0
  75. package/src/config.d.ts +22 -0
  76. package/src/config.d.ts.map +1 -0
  77. package/src/config.js +34 -0
  78. package/src/config.js.map +1 -0
  79. package/src/config.ts +38 -0
  80. package/src/context.d.ts +7 -0
  81. package/src/context.d.ts.map +1 -0
  82. package/src/context.js +3 -0
  83. package/src/context.js.map +1 -0
  84. package/src/context.ts +8 -0
  85. package/src/controllers/index.d.ts +39 -0
  86. package/src/controllers/index.d.ts.map +1 -0
  87. package/src/controllers/index.js +39 -0
  88. package/src/controllers/index.js.map +1 -0
  89. package/src/controllers/index.ts +51 -0
  90. package/src/db.d.ts +6 -0
  91. package/src/db.d.ts.map +1 -0
  92. package/src/db.js +74 -0
  93. package/src/db.js.map +1 -0
  94. package/src/db.ts +73 -0
  95. package/src/index.ts +178 -0
  96. package/src/integration.test.ts +453 -0
  97. package/src/middleware/auth.d.ts +15 -0
  98. package/src/middleware/auth.d.ts.map +1 -0
  99. package/src/middleware/auth.js +93 -0
  100. package/src/middleware/auth.js.map +1 -0
  101. package/src/middleware/auth.ts +109 -0
  102. package/src/middleware/body-limit.d.ts +9 -0
  103. package/src/middleware/body-limit.d.ts.map +1 -0
  104. package/src/middleware/body-limit.js +15 -0
  105. package/src/middleware/body-limit.js.map +1 -0
  106. package/src/middleware/body-limit.ts +16 -0
  107. package/src/middleware/rate-limit.d.ts +6 -0
  108. package/src/middleware/rate-limit.d.ts.map +1 -0
  109. package/src/middleware/rate-limit.js +40 -0
  110. package/src/middleware/rate-limit.js.map +1 -0
  111. package/src/middleware/rate-limit.ts +47 -0
  112. package/src/middleware/rbac.d.ts +10 -0
  113. package/src/middleware/rbac.d.ts.map +1 -0
  114. package/src/middleware/rbac.js +61 -0
  115. package/src/middleware/rbac.js.map +1 -0
  116. package/src/middleware/rbac.ts +71 -0
  117. package/src/middleware/tenant.d.ts +3 -0
  118. package/src/middleware/tenant.d.ts.map +1 -0
  119. package/src/middleware/tenant.js +19 -0
  120. package/src/middleware/tenant.js.map +1 -0
  121. package/src/middleware/tenant.ts +24 -0
  122. package/src/registry.d.ts +26 -0
  123. package/src/registry.d.ts.map +1 -0
  124. package/src/registry.js +112 -0
  125. package/src/registry.js.map +1 -0
  126. package/src/registry.ts +123 -0
  127. package/src/routes/auth.d.ts +3 -0
  128. package/src/routes/auth.d.ts.map +1 -0
  129. package/src/routes/auth.js +141 -0
  130. package/src/routes/auth.js.map +1 -0
  131. package/src/routes/auth.ts +164 -0
  132. package/src/routes/crud.d.ts +7 -0
  133. package/src/routes/crud.d.ts.map +1 -0
  134. package/src/routes/crud.js +845 -0
  135. package/src/routes/crud.js.map +1 -0
  136. package/src/routes/crud.ts +1029 -0
  137. package/src/routes/files.d.ts +7 -0
  138. package/src/routes/files.d.ts.map +1 -0
  139. package/src/routes/files.js +123 -0
  140. package/src/routes/files.js.map +1 -0
  141. package/src/routes/files.ts +143 -0
  142. package/src/routes/meta.d.ts +3 -0
  143. package/src/routes/meta.d.ts.map +1 -0
  144. package/src/routes/meta.js +352 -0
  145. package/src/routes/meta.js.map +1 -0
  146. package/src/routes/meta.ts +448 -0
  147. package/src/scheduler.ts +118 -0
  148. package/src/utils/link-validator.d.ts +7 -0
  149. package/src/utils/link-validator.d.ts.map +1 -0
  150. package/src/utils/link-validator.js +33 -0
  151. package/src/utils/link-validator.js.map +1 -0
  152. package/src/utils/link-validator.ts +45 -0
  153. package/src/utils/resolver.d.ts +5 -0
  154. package/src/utils/resolver.d.ts.map +1 -0
  155. package/src/utils/resolver.js +58 -0
  156. package/src/utils/resolver.js.map +1 -0
  157. package/src/utils/resolver.ts +65 -0
  158. package/tsconfig.json +9 -0
@@ -0,0 +1,109 @@
1
+ import { Context, Next } from 'hono';
2
+ import crypto from 'crypto';
3
+
4
+ export interface UserSession {
5
+ email: string;
6
+ roles: string[];
7
+ session_id: string; // Unique per-request correlation
8
+ }
9
+
10
+ /**
11
+ * Resolves request JWT signature and binds session properties to Hono request context.
12
+ * In production: validates HMAC-SHA256 signature against JWT_SECRET.
13
+ * In development: supports mock tokens for testing.
14
+ */
15
+ export async function authMiddleware(c: Context, next: Next) {
16
+ const authHeader = c.req.header('Authorization');
17
+
18
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
19
+ // If no auth token is provided, assign Guest role session context
20
+ c.set('user', {
21
+ email: 'guest@system.local',
22
+ roles: ['Guest'],
23
+ session_id: crypto.randomUUID()
24
+ } as UserSession);
25
+ await next();
26
+ return;
27
+ }
28
+
29
+ const token = authHeader.split(' ')[1];
30
+
31
+ if (!token || token.trim() === '') {
32
+ return c.json({ error: 'Unauthorized: Empty bearer token' }, 401);
33
+ }
34
+
35
+ // Enforce maximum token length to prevent abuse
36
+ if (token.length > 4096) {
37
+ return c.json({ error: 'Unauthorized: Token exceeds maximum allowed length' }, 401);
38
+ }
39
+
40
+ try {
41
+ const parts = token.split('.');
42
+
43
+ if (parts.length === 3) {
44
+ const jwtSecret = process.env.JWT_SECRET;
45
+
46
+ if (jwtSecret) {
47
+ // Production: Verify HMAC-SHA256 signature
48
+ const [headerB64, payloadB64, signatureB64] = parts;
49
+ const signatureInput = `${headerB64}.${payloadB64}`;
50
+ const expectedSignature = crypto
51
+ .createHmac('sha256', jwtSecret)
52
+ .update(signatureInput)
53
+ .digest('base64url');
54
+
55
+ if (signatureB64 !== expectedSignature) {
56
+ return c.json({ error: 'Unauthorized: Invalid token signature' }, 401);
57
+ }
58
+
59
+ // Decode and validate payload
60
+ const payloadJson = Buffer.from(payloadB64.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
61
+ const payload = JSON.parse(payloadJson);
62
+
63
+ // Check token expiry
64
+ if (payload.exp && Date.now() / 1000 > payload.exp) {
65
+ return c.json({ error: 'Unauthorized: Token has expired' }, 401);
66
+ }
67
+
68
+ // Check issued-at is not in the future (clock skew tolerance: 60s)
69
+ if (payload.iat && payload.iat > Date.now() / 1000 + 60) {
70
+ return c.json({ error: 'Unauthorized: Token issued in the future' }, 401);
71
+ }
72
+
73
+ c.set('user', {
74
+ email: payload.email || 'unknown@user.local',
75
+ roles: Array.isArray(payload.roles) ? payload.roles : ['Guest'],
76
+ session_id: crypto.randomUUID()
77
+ } as UserSession);
78
+ } else {
79
+ // Development fallback: decode without verification (JWT_SECRET not set)
80
+ console.warn('[AUTH] JWT_SECRET not configured. Running in development mode - tokens are NOT verified.');
81
+ const payloadB64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
82
+ const payloadJson = Buffer.from(payloadB64, 'base64').toString('utf-8');
83
+ const payload = JSON.parse(payloadJson);
84
+
85
+ c.set('user', {
86
+ email: payload.email || 'unknown@user.local',
87
+ roles: Array.isArray(payload.roles) ? payload.roles : ['Guest'],
88
+ session_id: crypto.randomUUID()
89
+ } as UserSession);
90
+ }
91
+ } else {
92
+ // Mock/testing token support (non-JWT format)
93
+ if (process.env.NODE_ENV === 'production') {
94
+ return c.json({ error: 'Unauthorized: Invalid token format' }, 401);
95
+ }
96
+
97
+ if (token === 'admin-token') {
98
+ c.set('user', { email: 'admin@system.local', roles: ['System Manager'], session_id: crypto.randomUUID() } as UserSession);
99
+ } else if (token === 'user-token') {
100
+ c.set('user', { email: 'user@system.local', roles: ['Employee'], session_id: crypto.randomUUID() } as UserSession);
101
+ } else {
102
+ return c.json({ error: 'Unauthorized: Invalid authentication signature' }, 401);
103
+ }
104
+ }
105
+ await next();
106
+ } catch (err) {
107
+ return c.json({ error: 'Unauthorized: Could not resolve session token' }, 401);
108
+ }
109
+ }
@@ -0,0 +1,9 @@
1
+ import { Context, Next } from 'hono';
2
+ /**
3
+ * Enforces max request body size to prevent memory exhaustion attacks.
4
+ */
5
+ export declare function bodyLimitMiddleware(c: Context, next: Next): Promise<(Response & import("hono").TypedResponse<{
6
+ error: string;
7
+ max_bytes: number;
8
+ }, 413, "json">) | undefined>;
9
+ //# sourceMappingURL=body-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-limit.d.ts","sourceRoot":"","sources":["body-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGrC;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI;;;8BAS/D"}
@@ -0,0 +1,15 @@
1
+ import { config } from '../config.js';
2
+ /**
3
+ * Enforces max request body size to prevent memory exhaustion attacks.
4
+ */
5
+ export async function bodyLimitMiddleware(c, next) {
6
+ const contentLength = c.req.header('content-length');
7
+ if (contentLength && parseInt(contentLength) > config.bodyLimitBytes) {
8
+ return c.json({
9
+ error: 'Payload Too Large',
10
+ max_bytes: config.bodyLimitBytes
11
+ }, 413);
12
+ }
13
+ await next();
14
+ }
15
+ //# sourceMappingURL=body-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body-limit.js","sourceRoot":"","sources":["body-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,CAAU,EAAE,IAAU;IAC9D,MAAM,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACrD,IAAI,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC;QACrE,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,mBAAmB;YAC1B,SAAS,EAAE,MAAM,CAAC,cAAc;SACjC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC;IACD,MAAM,IAAI,EAAE,CAAC;AACf,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { Context, Next } from 'hono';
2
+ import { config } from '../config.js';
3
+
4
+ /**
5
+ * Enforces max request body size to prevent memory exhaustion attacks.
6
+ */
7
+ export async function bodyLimitMiddleware(c: Context, next: Next) {
8
+ const contentLength = c.req.header('content-length');
9
+ if (contentLength && parseInt(contentLength) > config.bodyLimitBytes) {
10
+ return c.json({
11
+ error: 'Payload Too Large',
12
+ max_bytes: config.bodyLimitBytes
13
+ }, 413);
14
+ }
15
+ await next();
16
+ }
@@ -0,0 +1,6 @@
1
+ import { Context, Next } from 'hono';
2
+ export declare function rateLimitMiddleware(c: Context, next: Next): Promise<(Response & import("hono").TypedResponse<{
3
+ error: string;
4
+ retry_after_ms: number;
5
+ }, 429, "json">) | undefined>;
6
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAuBrC,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI;;;8BAuB/D"}
@@ -0,0 +1,40 @@
1
+ import { config } from '../config.js';
2
+ /**
3
+ * Simple in-memory sliding window rate limiter.
4
+ * Enterprise: replace with Redis-backed limiter for multi-instance deployments.
5
+ */
6
+ const windows = new Map();
7
+ // Periodic cleanup to prevent memory leak
8
+ setInterval(() => {
9
+ const now = Date.now();
10
+ for (const [key, val] of windows) {
11
+ if (now > val.resetAt)
12
+ windows.delete(key);
13
+ }
14
+ }, 60_000);
15
+ function getClientKey(c) {
16
+ const user = c.get('user');
17
+ if (user?.email && user.email !== 'guest@system.local')
18
+ return `user:${user.email}`;
19
+ return `ip:${c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'}`;
20
+ }
21
+ export async function rateLimitMiddleware(c, next) {
22
+ const key = getClientKey(c);
23
+ const now = Date.now();
24
+ const limit = config.rateLimitMax;
25
+ const windowMs = config.rateLimitWindowMs;
26
+ let entry = windows.get(key);
27
+ if (!entry || now > entry.resetAt) {
28
+ entry = { count: 0, resetAt: now + windowMs };
29
+ windows.set(key, entry);
30
+ }
31
+ entry.count++;
32
+ c.header('X-RateLimit-Limit', String(limit));
33
+ c.header('X-RateLimit-Remaining', String(Math.max(0, limit - entry.count)));
34
+ c.header('X-RateLimit-Reset', String(Math.ceil(entry.resetAt / 1000)));
35
+ if (entry.count > limit) {
36
+ return c.json({ error: 'Too Many Requests', retry_after_ms: entry.resetAt - now }, 429);
37
+ }
38
+ await next();
39
+ }
40
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;GAGG;AACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAC;AAEtE,0CAA0C;AAC1C,WAAW,CAAC,GAAG,EAAE;IACf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACjC,IAAI,GAAG,GAAG,GAAG,CAAC,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC,EAAE,MAAM,CAAC,CAAC;AAEX,SAAS,YAAY,CAAC,CAAU;IAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,oBAAoB;QAAE,OAAO,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;IACpF,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,SAAS,EAAE,CAAC;AAC3F,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,CAAU,EAAE,IAAU;IAC9D,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,iBAAiB,CAAC;IAE1C,IAAI,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAClC,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK,EAAE,CAAC;IAEd,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC5E,CAAC,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAEvE,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;QACxB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;IAC1F,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { Context, Next } from 'hono';
2
+ import { config } from '../config.js';
3
+
4
+ /**
5
+ * Simple in-memory sliding window rate limiter.
6
+ * Enterprise: replace with Redis-backed limiter for multi-instance deployments.
7
+ */
8
+ const windows = new Map<string, { count: number; resetAt: number }>();
9
+
10
+ // Periodic cleanup to prevent memory leak
11
+ setInterval(() => {
12
+ const now = Date.now();
13
+ for (const [key, val] of windows) {
14
+ if (now > val.resetAt) windows.delete(key);
15
+ }
16
+ }, 60_000);
17
+
18
+ function getClientKey(c: Context): string {
19
+ const user = c.get('user');
20
+ if (user?.email && user.email !== 'guest@system.local') return `user:${user.email}`;
21
+ return `ip:${c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'}`;
22
+ }
23
+
24
+ export async function rateLimitMiddleware(c: Context, next: Next) {
25
+ const key = getClientKey(c);
26
+ const now = Date.now();
27
+ const limit = config.rateLimitMax;
28
+ const windowMs = config.rateLimitWindowMs;
29
+
30
+ let entry = windows.get(key);
31
+ if (!entry || now > entry.resetAt) {
32
+ entry = { count: 0, resetAt: now + windowMs };
33
+ windows.set(key, entry);
34
+ }
35
+
36
+ entry.count++;
37
+
38
+ c.header('X-RateLimit-Limit', String(limit));
39
+ c.header('X-RateLimit-Remaining', String(Math.max(0, limit - entry.count)));
40
+ c.header('X-RateLimit-Reset', String(Math.ceil(entry.resetAt / 1000)));
41
+
42
+ if (entry.count > limit) {
43
+ return c.json({ error: 'Too Many Requests', retry_after_ms: entry.resetAt - now }, 429);
44
+ }
45
+
46
+ await next();
47
+ }
@@ -0,0 +1,10 @@
1
+ import { Context, Next } from 'hono';
2
+ /**
3
+ * Enforces metadata-driven Role-Based Access Control (RBAC) security boundaries on all requests.
4
+ */
5
+ export declare function rbacMiddleware(c: Context, next: Next): Promise<(Response & import("hono").TypedResponse<{
6
+ error: string;
7
+ }, 403, "json">) | (Response & import("hono").TypedResponse<{
8
+ error: string;
9
+ }, 404, "json">) | undefined>;
10
+ //# sourceMappingURL=rbac.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rbac.d.ts","sourceRoot":"","sources":["rbac.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAIrC;;GAEG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI;;;;8BA+D1D"}
@@ -0,0 +1,61 @@
1
+ import { registry } from '../registry.js';
2
+ /**
3
+ * Enforces metadata-driven Role-Based Access Control (RBAC) security boundaries on all requests.
4
+ */
5
+ export async function rbacMiddleware(c, next) {
6
+ const user = c.get('user');
7
+ const doctypeName = c.req.param('doctype');
8
+ if (!user || !user.roles) {
9
+ return c.json({ error: 'Forbidden: Missing request authentication context' }, 403);
10
+ }
11
+ if (!doctypeName) {
12
+ await next();
13
+ return;
14
+ }
15
+ // Load DocType schema definition
16
+ const doctype = await registry.get(doctypeName);
17
+ if (!doctype) {
18
+ return c.json({ error: `DocType ${doctypeName} not found` }, 404);
19
+ }
20
+ const method = c.req.method;
21
+ const path = c.req.path;
22
+ let action = 'read';
23
+ if (method === 'POST') {
24
+ if (path.endsWith('/submit')) {
25
+ action = 'submit';
26
+ }
27
+ else if (path.endsWith('/cancel')) {
28
+ action = 'cancel';
29
+ }
30
+ else {
31
+ action = 'create';
32
+ }
33
+ }
34
+ else if (method === 'PUT') {
35
+ action = 'update';
36
+ }
37
+ else if (method === 'DELETE') {
38
+ action = 'delete';
39
+ }
40
+ else if (method === 'GET') {
41
+ action = 'read';
42
+ }
43
+ // Admin bypass
44
+ if (user.roles.includes('System Manager')) {
45
+ await next();
46
+ return;
47
+ }
48
+ // Check user roles against PermissionRules
49
+ const permitted = doctype.permissions.some(rule => {
50
+ const roleMatches = user.roles.includes(rule.role);
51
+ if (!roleMatches)
52
+ return false;
53
+ // Check action boolean
54
+ return !!rule[action];
55
+ });
56
+ if (!permitted) {
57
+ return c.json({ error: `Forbidden: User does not hold ${action} permissions for DocType '${doctypeName}'` }, 403);
58
+ }
59
+ await next();
60
+ }
61
+ //# sourceMappingURL=rbac.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rbac.js","sourceRoot":"","sources":["rbac.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,CAAU,EAAE,IAAU;IACzD,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAE3C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mDAAmD,EAAE,EAAE,GAAG,CAAC,CAAC;IACrF,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,iCAAiC;IACjC,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,WAAW,YAAY,EAAE,EAAE,GAAG,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;IAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;IAExB,IAAI,MAAM,GAAyB,MAAM,CAAC;IAE1C,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;aAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACpC,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QAC5B,MAAM,GAAG,QAAQ,CAAC;IACpB,CAAC;SAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,GAAG,QAAQ,CAAC;IACpB,CAAC;SAAM,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;QAC5B,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;IAED,eAAe;IACf,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,EAAE,CAAC;QACb,OAAO;IACT,CAAC;IAED,2CAA2C;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAChD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,WAAW;YAAE,OAAO,KAAK,CAAC;QAE/B,uBAAuB;QACvB,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,CAAC,IAAI,CACX,EAAE,KAAK,EAAE,iCAAiC,MAAM,6BAA6B,WAAW,GAAG,EAAE,EAC7F,GAAG,CACJ,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { Context, Next } from 'hono';
2
+ import { registry } from '../registry.js';
3
+ import { PermissionRule } from '@aruvili/core';
4
+
5
+ /**
6
+ * Enforces metadata-driven Role-Based Access Control (RBAC) security boundaries on all requests.
7
+ */
8
+ export async function rbacMiddleware(c: Context, next: Next) {
9
+ const user = c.get('user');
10
+ const doctypeName = c.req.param('doctype');
11
+
12
+ if (!user || !user.roles) {
13
+ return c.json({ error: 'Forbidden: Missing request authentication context' }, 403);
14
+ }
15
+
16
+ if (!doctypeName) {
17
+ await next();
18
+ return;
19
+ }
20
+
21
+ // Load DocType schema definition
22
+ const doctype = await registry.get(doctypeName);
23
+ if (!doctype) {
24
+ return c.json({ error: `DocType ${doctypeName} not found` }, 404);
25
+ }
26
+
27
+ const method = c.req.method;
28
+ const path = c.req.path;
29
+
30
+ let action: keyof PermissionRule = 'read';
31
+
32
+ if (method === 'POST') {
33
+ if (path.endsWith('/submit')) {
34
+ action = 'submit';
35
+ } else if (path.endsWith('/cancel')) {
36
+ action = 'cancel';
37
+ } else {
38
+ action = 'create';
39
+ }
40
+ } else if (method === 'PUT') {
41
+ action = 'update';
42
+ } else if (method === 'DELETE') {
43
+ action = 'delete';
44
+ } else if (method === 'GET') {
45
+ action = 'read';
46
+ }
47
+
48
+ // Admin bypass
49
+ if (user.roles.includes('System Manager')) {
50
+ await next();
51
+ return;
52
+ }
53
+
54
+ // Check user roles against PermissionRules
55
+ const permitted = doctype.permissions.some(rule => {
56
+ const roleMatches = user.roles.includes(rule.role);
57
+ if (!roleMatches) return false;
58
+
59
+ // Check action boolean
60
+ return !!rule[action];
61
+ });
62
+
63
+ if (!permitted) {
64
+ return c.json(
65
+ { error: `Forbidden: User does not hold ${action} permissions for DocType '${doctypeName}'` },
66
+ 403
67
+ );
68
+ }
69
+
70
+ await next();
71
+ }
@@ -0,0 +1,3 @@
1
+ import { MiddlewareHandler } from 'hono';
2
+ export declare const tenantMiddleware: MiddlewareHandler;
3
+ //# sourceMappingURL=tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["tenant.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAKzC,eAAO,MAAM,gBAAgB,EAAE,iBAkB9B,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { requestContext } from '../context.js';
2
+ const TENANT_REGEX = /^[a-zA-Z0-9_]{1,63}$/;
3
+ export const tenantMiddleware = async (c, next) => {
4
+ const tenantHeader = c.req.header('X-Tenant-ID');
5
+ // Resolve tenant. Default to public if not provided.
6
+ let tenantId = 'public';
7
+ if (tenantHeader) {
8
+ tenantId = tenantHeader.toLowerCase().trim();
9
+ }
10
+ // Validate tenantId to block SQL Injection in search path
11
+ if (!TENANT_REGEX.test(tenantId)) {
12
+ return c.json({ error: 'Invalid Tenant ID format' }, 400);
13
+ }
14
+ // Bind tenant to context store
15
+ return requestContext.run({ tenantId, user: c.get('user') }, async () => {
16
+ await next();
17
+ });
18
+ };
19
+ //# sourceMappingURL=tenant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant.js","sourceRoot":"","sources":["tenant.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnE,MAAM,YAAY,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAEjD,qDAAqD;IACrD,IAAI,QAAQ,GAAG,QAAQ,CAAC;IACxB,IAAI,YAAY,EAAE,CAAC;QACjB,QAAQ,GAAG,YAAY,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/C,CAAC;IAED,0DAA0D;IAC1D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;IAED,+BAA+B;IAC/B,OAAO,cAAc,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { MiddlewareHandler } from 'hono';
2
+ import { requestContext } from '../context.js';
3
+
4
+ const TENANT_REGEX = /^[a-zA-Z0-9_]{1,63}$/;
5
+
6
+ export const tenantMiddleware: MiddlewareHandler = async (c, next) => {
7
+ const tenantHeader = c.req.header('X-Tenant-ID');
8
+
9
+ // Resolve tenant. Default to public if not provided.
10
+ let tenantId = 'public';
11
+ if (tenantHeader) {
12
+ tenantId = tenantHeader.toLowerCase().trim();
13
+ }
14
+
15
+ // Validate tenantId to block SQL Injection in search path
16
+ if (!TENANT_REGEX.test(tenantId)) {
17
+ return c.json({ error: 'Invalid Tenant ID format' }, 400);
18
+ }
19
+
20
+ // Bind tenant to context store
21
+ return requestContext.run({ tenantId, user: c.get('user') }, async () => {
22
+ await next();
23
+ });
24
+ };
@@ -0,0 +1,26 @@
1
+ import { DocTypeDefinition } from '@aruvili/core';
2
+ import { z } from 'zod';
3
+ declare class DocTypeRegistry {
4
+ private cache;
5
+ private zodSchemas;
6
+ /**
7
+ * Clears in-memory metadata caches.
8
+ */
9
+ invalidate(doctypeName: string): void;
10
+ /**
11
+ * Retrieves a DocTypeDefinition schema structure.
12
+ * Loads dynamically from database into cache if missing.
13
+ */
14
+ get(doctypeName: string): Promise<DocTypeDefinition | null>;
15
+ /**
16
+ * Sets definition details to registry and clear caches.
17
+ */
18
+ set(doctypeName: string, definition: DocTypeDefinition): void;
19
+ /**
20
+ * Compiles and returns a runtime Zod validator matching the fields configuration.
21
+ */
22
+ getValidator(doctypeName: string): Promise<z.ZodObject<any> | null>;
23
+ }
24
+ export declare const registry: DocTypeRegistry;
25
+ export {};
26
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,cAAM,eAAe;IACnB,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,UAAU,CAAuC;IAEzD;;OAEG;IACI,UAAU,CAAC,WAAW,EAAE,MAAM;IAKrC;;;OAGG;IACU,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IAoBxE;;OAEG;IACI,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,iBAAiB;IAK7D;;OAEG;IACU,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;CAqEjF;AAED,eAAO,MAAM,QAAQ,iBAAwB,CAAC"}
@@ -0,0 +1,112 @@
1
+ import { z } from 'zod';
2
+ import { query } from './db.js';
3
+ class DocTypeRegistry {
4
+ cache = new Map();
5
+ zodSchemas = new Map();
6
+ /**
7
+ * Clears in-memory metadata caches.
8
+ */
9
+ invalidate(doctypeName) {
10
+ this.cache.delete(doctypeName);
11
+ this.zodSchemas.delete(doctypeName);
12
+ }
13
+ /**
14
+ * Retrieves a DocTypeDefinition schema structure.
15
+ * Loads dynamically from database into cache if missing.
16
+ */
17
+ async get(doctypeName) {
18
+ if (this.cache.has(doctypeName)) {
19
+ return this.cache.get(doctypeName);
20
+ }
21
+ // Attempt to load from PostgreSQL meta configuration table
22
+ try {
23
+ const res = await query('SELECT definition FROM _doctype_meta WHERE name = $1', [doctypeName]);
24
+ if (res.rows.length === 0) {
25
+ return null;
26
+ }
27
+ const definition = res.rows[0].definition;
28
+ this.cache.set(doctypeName, definition);
29
+ return definition;
30
+ }
31
+ catch (err) {
32
+ console.error(`Failed to load DocType ${doctypeName} from database:`, err);
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * Sets definition details to registry and clear caches.
38
+ */
39
+ set(doctypeName, definition) {
40
+ this.cache.set(doctypeName, definition);
41
+ this.zodSchemas.delete(doctypeName);
42
+ }
43
+ /**
44
+ * Compiles and returns a runtime Zod validator matching the fields configuration.
45
+ */
46
+ async getValidator(doctypeName) {
47
+ if (this.zodSchemas.has(doctypeName)) {
48
+ return this.zodSchemas.get(doctypeName);
49
+ }
50
+ const definition = await this.get(doctypeName);
51
+ if (!definition)
52
+ return null;
53
+ const shape = {
54
+ name: z.string().max(255).optional(),
55
+ docstatus: z.number().int().min(0).max(2).optional()
56
+ };
57
+ for (const field of definition.fields) {
58
+ let validator;
59
+ switch (field.fieldtype) {
60
+ case 'Select':
61
+ if (field.options) {
62
+ const list = field.options.split(',').map(s => s.trim());
63
+ validator = z.enum(list);
64
+ }
65
+ else {
66
+ validator = z.string().max(255);
67
+ }
68
+ break;
69
+ case 'Text':
70
+ case 'Link':
71
+ validator = z.string().max(255);
72
+ break;
73
+ case 'Small Text':
74
+ case 'Long Text':
75
+ validator = z.string();
76
+ break;
77
+ case 'Int':
78
+ validator = z.number().int();
79
+ break;
80
+ case 'Float':
81
+ case 'Currency':
82
+ validator = z.number();
83
+ break;
84
+ case 'Check':
85
+ validator = z.boolean();
86
+ break;
87
+ case 'Date':
88
+ validator = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be in YYYY-MM-DD format');
89
+ break;
90
+ case 'Datetime':
91
+ validator = z.string().datetime();
92
+ break;
93
+ case 'Table':
94
+ // Child table fields are arrays of validations compiled from the child schema.
95
+ // Note: to prevent recursion, retrieve the child validator dynamically.
96
+ validator = z.array(z.any()); // Validated down the pipeline inside CRUD controller transaction loop
97
+ break;
98
+ default:
99
+ validator = z.any();
100
+ }
101
+ if (!field.required) {
102
+ validator = validator.optional().nullable();
103
+ }
104
+ shape[field.fieldname] = validator;
105
+ }
106
+ const compiled = z.object(shape);
107
+ this.zodSchemas.set(doctypeName, compiled);
108
+ return compiled;
109
+ }
110
+ }
111
+ export const registry = new DocTypeRegistry();
112
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["registry.ts"],"names":[],"mappings":"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"}