@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.
- package/dist/config.d.ts +22 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +3 -0
- package/dist/context.js.map +1 -0
- package/dist/controllers/index.d.ts +39 -0
- package/dist/controllers/index.d.ts.map +1 -0
- package/dist/controllers/index.js +39 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/db.d.ts +6 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +74 -0
- package/dist/db.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +15 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +93 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/body-limit.d.ts +9 -0
- package/dist/middleware/body-limit.d.ts.map +1 -0
- package/dist/middleware/body-limit.js +15 -0
- package/dist/middleware/body-limit.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +6 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +40 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/rbac.d.ts +10 -0
- package/dist/middleware/rbac.d.ts.map +1 -0
- package/dist/middleware/rbac.js +61 -0
- package/dist/middleware/rbac.js.map +1 -0
- package/dist/middleware/tenant.d.ts +3 -0
- package/dist/middleware/tenant.d.ts.map +1 -0
- package/dist/middleware/tenant.js +19 -0
- package/dist/middleware/tenant.js.map +1 -0
- package/dist/registry.d.ts +26 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +112 -0
- package/dist/registry.js.map +1 -0
- package/dist/routes/auth.d.ts +3 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +141 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/crud.d.ts +7 -0
- package/dist/routes/crud.d.ts.map +1 -0
- package/dist/routes/crud.js +845 -0
- package/dist/routes/crud.js.map +1 -0
- package/dist/routes/files.d.ts +7 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +123 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/meta.d.ts +3 -0
- package/dist/routes/meta.d.ts.map +1 -0
- package/dist/routes/meta.js +352 -0
- package/dist/routes/meta.js.map +1 -0
- package/dist/scheduler.d.ts +33 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +97 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/utils/link-validator.d.ts +7 -0
- package/dist/utils/link-validator.d.ts.map +1 -0
- package/dist/utils/link-validator.js +33 -0
- package/dist/utils/link-validator.js.map +1 -0
- package/dist/utils/resolver.d.ts +5 -0
- package/dist/utils/resolver.d.ts.map +1 -0
- package/dist/utils/resolver.js +58 -0
- package/dist/utils/resolver.js.map +1 -0
- package/package.json +24 -0
- package/src/api.test.ts +362 -0
- package/src/config.d.ts +22 -0
- package/src/config.d.ts.map +1 -0
- package/src/config.js +34 -0
- package/src/config.js.map +1 -0
- package/src/config.ts +38 -0
- package/src/context.d.ts +7 -0
- package/src/context.d.ts.map +1 -0
- package/src/context.js +3 -0
- package/src/context.js.map +1 -0
- package/src/context.ts +8 -0
- package/src/controllers/index.d.ts +39 -0
- package/src/controllers/index.d.ts.map +1 -0
- package/src/controllers/index.js +39 -0
- package/src/controllers/index.js.map +1 -0
- package/src/controllers/index.ts +51 -0
- package/src/db.d.ts +6 -0
- package/src/db.d.ts.map +1 -0
- package/src/db.js +74 -0
- package/src/db.js.map +1 -0
- package/src/db.ts +73 -0
- package/src/index.ts +178 -0
- package/src/integration.test.ts +453 -0
- package/src/middleware/auth.d.ts +15 -0
- package/src/middleware/auth.d.ts.map +1 -0
- package/src/middleware/auth.js +93 -0
- package/src/middleware/auth.js.map +1 -0
- package/src/middleware/auth.ts +109 -0
- package/src/middleware/body-limit.d.ts +9 -0
- package/src/middleware/body-limit.d.ts.map +1 -0
- package/src/middleware/body-limit.js +15 -0
- package/src/middleware/body-limit.js.map +1 -0
- package/src/middleware/body-limit.ts +16 -0
- package/src/middleware/rate-limit.d.ts +6 -0
- package/src/middleware/rate-limit.d.ts.map +1 -0
- package/src/middleware/rate-limit.js +40 -0
- package/src/middleware/rate-limit.js.map +1 -0
- package/src/middleware/rate-limit.ts +47 -0
- package/src/middleware/rbac.d.ts +10 -0
- package/src/middleware/rbac.d.ts.map +1 -0
- package/src/middleware/rbac.js +61 -0
- package/src/middleware/rbac.js.map +1 -0
- package/src/middleware/rbac.ts +71 -0
- package/src/middleware/tenant.d.ts +3 -0
- package/src/middleware/tenant.d.ts.map +1 -0
- package/src/middleware/tenant.js +19 -0
- package/src/middleware/tenant.js.map +1 -0
- package/src/middleware/tenant.ts +24 -0
- package/src/registry.d.ts +26 -0
- package/src/registry.d.ts.map +1 -0
- package/src/registry.js +112 -0
- package/src/registry.js.map +1 -0
- package/src/registry.ts +123 -0
- package/src/routes/auth.d.ts +3 -0
- package/src/routes/auth.d.ts.map +1 -0
- package/src/routes/auth.js +141 -0
- package/src/routes/auth.js.map +1 -0
- package/src/routes/auth.ts +164 -0
- package/src/routes/crud.d.ts +7 -0
- package/src/routes/crud.d.ts.map +1 -0
- package/src/routes/crud.js +845 -0
- package/src/routes/crud.js.map +1 -0
- package/src/routes/crud.ts +1029 -0
- package/src/routes/files.d.ts +7 -0
- package/src/routes/files.d.ts.map +1 -0
- package/src/routes/files.js +123 -0
- package/src/routes/files.js.map +1 -0
- package/src/routes/files.ts +143 -0
- package/src/routes/meta.d.ts +3 -0
- package/src/routes/meta.d.ts.map +1 -0
- package/src/routes/meta.js +352 -0
- package/src/routes/meta.js.map +1 -0
- package/src/routes/meta.ts +448 -0
- package/src/scheduler.ts +118 -0
- package/src/utils/link-validator.d.ts +7 -0
- package/src/utils/link-validator.d.ts.map +1 -0
- package/src/utils/link-validator.js +33 -0
- package/src/utils/link-validator.js.map +1 -0
- package/src/utils/link-validator.ts +45 -0
- package/src/utils/resolver.d.ts +5 -0
- package/src/utils/resolver.d.ts.map +1 -0
- package/src/utils/resolver.js +58 -0
- package/src/utils/resolver.js.map +1 -0
- package/src/utils/resolver.ts +65 -0
- package/tsconfig.json +9 -0
|
@@ -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":["../../src/middleware/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,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":["../../src/middleware/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":["../../src/middleware/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 @@
|
|
|
1
|
+
{"version":3,"file":"tenant.d.ts","sourceRoot":"","sources":["../../src/middleware/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":["../../src/middleware/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,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":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,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"}
|
package/dist/registry.js
ADDED
|
@@ -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":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAK5B,eAAO,MAAM,UAAU,4EAAa,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
import { query, withTransaction } from '../db.js';
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
export const authRouter = new Hono();
|
|
6
|
+
/**
|
|
7
|
+
* Hash password using native crypto (no bcrypt dependency needed in Bun).
|
|
8
|
+
*/
|
|
9
|
+
async function hashPassword(password) {
|
|
10
|
+
const salt = crypto.randomBytes(32).toString('hex');
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
crypto.scrypt(password, salt, 64, (err, derived) => {
|
|
13
|
+
if (err)
|
|
14
|
+
reject(err);
|
|
15
|
+
resolve(`${salt}:${derived.toString('hex')}`);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function verifyPassword(password, hash) {
|
|
20
|
+
const [salt, key] = hash.split(':');
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
crypto.scrypt(password, salt, 64, (err, derived) => {
|
|
23
|
+
if (err)
|
|
24
|
+
reject(err);
|
|
25
|
+
resolve(derived.toString('hex') === key);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate a signed JWT token.
|
|
31
|
+
*/
|
|
32
|
+
function signJWT(payload) {
|
|
33
|
+
const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
|
|
34
|
+
const now = Math.floor(Date.now() / 1000);
|
|
35
|
+
const body = Buffer.from(JSON.stringify({
|
|
36
|
+
...payload,
|
|
37
|
+
iat: now,
|
|
38
|
+
exp: now + config.jwtExpiresIn
|
|
39
|
+
})).toString('base64url');
|
|
40
|
+
const signature = crypto
|
|
41
|
+
.createHmac('sha256', config.jwtSecret)
|
|
42
|
+
.update(`${header}.${body}`)
|
|
43
|
+
.digest('base64url');
|
|
44
|
+
return `${header}.${body}.${signature}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POST /api/auth/signup
|
|
48
|
+
*/
|
|
49
|
+
authRouter.post('/signup', async (c) => {
|
|
50
|
+
const { email, password, full_name } = await c.req.json();
|
|
51
|
+
if (!email || !password) {
|
|
52
|
+
return c.json({ error: 'Email and password are required' }, 400);
|
|
53
|
+
}
|
|
54
|
+
if (typeof password !== 'string' || password.length < 8) {
|
|
55
|
+
return c.json({ error: 'Password must be at least 8 characters' }, 400);
|
|
56
|
+
}
|
|
57
|
+
if (typeof email !== 'string' || !email.includes('@')) {
|
|
58
|
+
return c.json({ error: 'Invalid email address' }, 400);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const existing = await query('SELECT name FROM _users WHERE email = $1', [email.toLowerCase()]);
|
|
62
|
+
if (existing.rows.length > 0) {
|
|
63
|
+
return c.json({ error: 'An account with this email already exists' }, 409);
|
|
64
|
+
}
|
|
65
|
+
const hashed = await hashPassword(password);
|
|
66
|
+
const userId = crypto.randomUUID();
|
|
67
|
+
await withTransaction(async (client) => {
|
|
68
|
+
await client.query(`INSERT INTO _users (name, uuid, email, full_name, hashed_password, roles, enabled)
|
|
69
|
+
VALUES ($1, gen_random_uuid(), $2, $3, $4, $5, true)`, [userId, email.toLowerCase(), full_name || '', hashed, JSON.stringify(['Guest'])]);
|
|
70
|
+
});
|
|
71
|
+
const token = signJWT({ email: email.toLowerCase(), roles: ['Guest'], user_id: userId });
|
|
72
|
+
return c.json({ token, user: { name: userId, email: email.toLowerCase(), roles: ['Guest'] } }, 201);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error('[AUTH] Signup failed:', err.message);
|
|
76
|
+
return c.json({ error: 'Registration failed' }, 500);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* POST /api/auth/login
|
|
81
|
+
*/
|
|
82
|
+
authRouter.post('/login', async (c) => {
|
|
83
|
+
const { email, password } = await c.req.json();
|
|
84
|
+
if (!email || !password) {
|
|
85
|
+
return c.json({ error: 'Email and password are required' }, 400);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const res = await query('SELECT name, email, hashed_password, roles, enabled FROM _users WHERE email = $1', [email.toLowerCase()]);
|
|
89
|
+
if (res.rows.length === 0) {
|
|
90
|
+
return c.json({ error: 'Invalid email or password' }, 401);
|
|
91
|
+
}
|
|
92
|
+
const user = res.rows[0];
|
|
93
|
+
if (!user.enabled) {
|
|
94
|
+
return c.json({ error: 'Account is disabled' }, 403);
|
|
95
|
+
}
|
|
96
|
+
const valid = await verifyPassword(password, user.hashed_password);
|
|
97
|
+
if (!valid) {
|
|
98
|
+
return c.json({ error: 'Invalid email or password' }, 401);
|
|
99
|
+
}
|
|
100
|
+
const roles = Array.isArray(user.roles) ? user.roles : JSON.parse(user.roles || '["Guest"]');
|
|
101
|
+
const token = signJWT({ email: user.email, roles, user_id: user.name });
|
|
102
|
+
// Update last login
|
|
103
|
+
await query('UPDATE _users SET last_login = NOW() WHERE name = $1', [user.name]);
|
|
104
|
+
return c.json({ token, user: { name: user.name, email: user.email, roles } });
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
console.error('[AUTH] Login failed:', err.message);
|
|
108
|
+
return c.json({ error: 'Authentication failed' }, 500);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* GET /api/auth/me — returns current session user info
|
|
113
|
+
*/
|
|
114
|
+
authRouter.get('/me', async (c) => {
|
|
115
|
+
const authHeader = c.req.header('Authorization');
|
|
116
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
117
|
+
return c.json({ error: 'Not authenticated' }, 401);
|
|
118
|
+
}
|
|
119
|
+
const token = authHeader.split(' ')[1];
|
|
120
|
+
try {
|
|
121
|
+
const parts = token.split('.');
|
|
122
|
+
if (parts.length !== 3)
|
|
123
|
+
return c.json({ error: 'Invalid token' }, 401);
|
|
124
|
+
const sig = crypto.createHmac('sha256', config.jwtSecret).update(`${parts[0]}.${parts[1]}`).digest('base64url');
|
|
125
|
+
if (sig !== parts[2])
|
|
126
|
+
return c.json({ error: 'Invalid token' }, 401);
|
|
127
|
+
const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString());
|
|
128
|
+
if (payload.exp && Date.now() / 1000 > payload.exp)
|
|
129
|
+
return c.json({ error: 'Token expired' }, 401);
|
|
130
|
+
const res = await query('SELECT name, email, full_name, roles, enabled, last_login FROM _users WHERE email = $1', [payload.email]);
|
|
131
|
+
if (res.rows.length === 0)
|
|
132
|
+
return c.json({ error: 'User not found' }, 404);
|
|
133
|
+
const user = res.rows[0];
|
|
134
|
+
user.roles = Array.isArray(user.roles) ? user.roles : JSON.parse(user.roles || '[]');
|
|
135
|
+
return c.json(user);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return c.json({ error: 'Invalid token' }, 401);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crud.d.ts","sourceRoot":"","sources":["../../src/routes/crud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,eAAO,MAAM,UAAU;eAAyB;QAAE,IAAI,EAAE,GAAG,CAAA;KAAE;yCAAK,CAAC"}
|