@dnax/core 0.3.4 → 0.3.5

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/app/hono.ts CHANGED
@@ -18,6 +18,8 @@ import { MediaDrive } from "../lib/media";
18
18
  import { isStudio } from "../lib/studio";
19
19
  import { pick } from "radash";
20
20
  import { secureHeaders } from "hono/secure-headers";
21
+ import { getTenant } from "../lib/tenant";
22
+ import { checkPermission, getPermission } from "../lib/permissions";
21
23
  const app = new Hono();
22
24
 
23
25
  const API_PATH = "/api";
@@ -31,7 +33,7 @@ function HonoInstance(): typeof app {
31
33
  })
32
34
  );
33
35
  //eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoibm9tIiwiaWF0IjoxNzE3Nzc0MDQzLCJleHAiOjE3MTc3NzQxMDN9.Ud4-0y8pa4SMIcSn8PU1A-sjC-hT4ZVe_u3AdChyIJU
34
- // Middleware
36
+ // Middlewares Injection
35
37
  app.use(async (c, next) => {
36
38
  return asyncLocalStorage.run(new Map(), async () => {
37
39
  let cookie = getCookie(c);
@@ -53,20 +55,20 @@ function HonoInstance(): typeof app {
53
55
  };
54
56
  c.set("_v", _v);
55
57
  c.set("tenant-id", c.req.header()["tenant-id"]);
58
+
56
59
  if (token && valid) {
57
60
  session.set({
61
+ // token: token,
58
62
  state: decode?.state || {},
59
- access: decode?.access || {},
63
+ role: decode?.role || null,
60
64
  _v: { ...(_v || {}) },
61
- token: token,
62
65
  });
63
66
  } else {
64
67
  // no valid token
65
68
  session.set({
66
69
  state: {},
67
- access: {},
70
+ role: undefined,
68
71
  _v: { ...(_v || {}) },
69
- token: null,
70
72
  });
71
73
  }
72
74
 
@@ -80,58 +82,7 @@ function HonoInstance(): typeof app {
80
82
  });
81
83
  });
82
84
 
83
- // Public assets
84
- app.get(
85
- "/assets/*",
86
- serveStatic({
87
- root: "uploads",
88
- //rewriteRequestPath: (path) => path?.replace(/^\/assets/, ""),
89
- })
90
- );
91
-
92
- app.post("/api/studio", (c, next) => {
93
- let cookie = getCookie(c);
94
- let secretKeyStudio = cookie["_STUDIO_SECRET_KEY_"];
95
- let canPerform = isStudio(secretKeyStudio);
96
- let cf = {
97
- collections: Cfg.collections?.map((c) =>
98
- pick(c, ["fields", "slug", "tenant_id", "type"])
99
- ),
100
- tenants: Cfg.tenants.map((t) => t?.id),
101
- };
102
-
103
- if (canPerform) {
104
- return c.json({
105
- auth: true,
106
- data: cf,
107
- });
108
- } else {
109
- c.status(403);
110
- return c.json({ message: "Unauthorized" });
111
- }
112
- });
113
-
114
- Cfg.collections?.map((c) => {
115
- if (c?.type == "media" && c.media?.enabled) {
116
- let mediaPath = cleanPath(
117
- "/files/" + (c?.media?.overwriteFolderName || c?.slug) + "/public/*"
118
- );
119
-
120
- //console.log("Media Path :", mediaPath);
121
- app.get(
122
- mediaPath,
123
- serveStatic({
124
- root: cleanPath("uploads"),
125
- rewriteRequestPath: (path) => path?.replace(/^\/files/, ""),
126
- onNotFound: (path, c) => {
127
- console.log(`${path} is not found, you access ${c.req.path}`);
128
- },
129
- })
130
- );
131
- }
132
- });
133
-
134
- // access controle for api
85
+ // Access controle for api
135
86
  app.use(cleanPath(API_PATH), async (c, next) => {
136
87
  let session = sessionStorage();
137
88
 
@@ -141,9 +92,8 @@ function HonoInstance(): typeof app {
141
92
  let col = getCollection(collection, tenant_id);
142
93
  let colAccess =
143
94
  col?.access?.hasOwnProperty(action) ||
144
- session.get()?.access?.hasOwnProperty(action) ||
145
- session.get()?.access?.hasOwnProperty("allAction");
146
- null;
95
+ getPermission(session.get()?.role, tenant_id) ||
96
+ null;
147
97
  let nextLifecyle: any = false;
148
98
 
149
99
  // Middleware for apis
@@ -195,23 +145,30 @@ function HonoInstance(): typeof app {
195
145
  if (col && action && colAccess) {
196
146
  let basicNextAccess = false;
197
147
 
198
- if (session.get().access.hasOwnProperty(action)) {
199
- basicNextAccess = session.get().access[action];
148
+ if (getPermission(session.get()?.role, tenant_id)) {
149
+ basicNextAccess = checkPermission(
150
+ session.get()?.role,
151
+ tenant_id,
152
+ collection,
153
+ action
154
+ );
200
155
  }
201
156
 
202
- if (session.get().access.hasOwnProperty("allAction")) {
157
+ /* if (session.get().access.hasOwnProperty("allAction")) {
203
158
  basicNextAccess = session.get().access["allAction"];
159
+ } */
160
+
161
+ nextLifecyle = basicNextAccess;
162
+
163
+ if (col?.access?.hasOwnProperty(action)) {
164
+ nextLifecyle = await col?.access[action]({
165
+ session: sessionStorage(),
166
+ action: action,
167
+ c: c,
168
+ isAuth: c.var?._v?.isAuth || false,
169
+ rest: new useRest({ tenant_id: tenant_id }),
170
+ });
204
171
  }
205
-
206
- nextLifecyle = basicNextAccess
207
- ? basicNextAccess
208
- : await col?.access[action]({
209
- session: sessionStorage(),
210
- action: action,
211
- c: c,
212
- isAuth: c.var?._v?.isAuth || false,
213
- rest: new useRest({ tenant_id: tenant_id }),
214
- });
215
172
  }
216
173
  }
217
174
 
@@ -228,6 +185,11 @@ function HonoInstance(): typeof app {
228
185
  // API REST
229
186
  app.post(cleanPath(API_PATH), async (c) => {
230
187
  try {
188
+ // control tenant
189
+ if (!c.var["tenant-id"] || !getTenant(c.var["tenant-id"])) {
190
+ throw new contextError(`Tenant Id missiong or not found`, 404);
191
+ }
192
+
231
193
  var response;
232
194
  var parseBody;
233
195
  const { action, collection, cleanDeep, useCache, name } =
@@ -272,12 +234,7 @@ function HonoInstance(): typeof app {
272
234
  });
273
235
  }
274
236
 
275
- // Controler on collection
276
- if (
277
- !getCollection(collection, c.var["tenant-id"]) &&
278
- getAction(action) &&
279
- collection
280
- ) {
237
+ if (!getAction(action) || !collection) {
281
238
  throw new contextError(`Collection ${collection} not found`, 404);
282
239
  }
283
240
 
@@ -431,6 +388,49 @@ function HonoInstance(): typeof app {
431
388
  }
432
389
  });
433
390
 
391
+ app.post("/api/studio", (c, next) => {
392
+ let cookie = getCookie(c);
393
+ let secretKeyStudio = cookie["_STUDIO_SECRET_KEY_"];
394
+ let canPerform = isStudio(secretKeyStudio);
395
+ let cf = {
396
+ collections: Cfg.collections?.map((c) =>
397
+ pick(c, ["fields", "slug", "tenant_id", "type"])
398
+ ),
399
+ tenants: Cfg.tenants.map((t) => t?.id),
400
+ };
401
+
402
+ if (canPerform) {
403
+ return c.json({
404
+ auth: true,
405
+ data: cf,
406
+ });
407
+ } else {
408
+ c.status(403);
409
+ return c.json({ message: "Unauthorized" });
410
+ }
411
+ });
412
+
413
+ // media endpoints
414
+ Cfg.collections?.map((c) => {
415
+ if (c?.type == "media" && c.media?.enabled) {
416
+ let mediaPath = cleanPath(
417
+ "/files/" + (c?.media?.overwriteFolderName || c?.slug) + "/public/*"
418
+ );
419
+
420
+ //console.log("Media Path :", mediaPath);
421
+ app.get(
422
+ mediaPath,
423
+ serveStatic({
424
+ root: cleanPath("uploads"),
425
+ rewriteRequestPath: (path) => path?.replace(/^\/files/, ""),
426
+ onNotFound: (path, c) => {
427
+ console.log(`${path} is not found, you access ${c.req.path}`);
428
+ },
429
+ })
430
+ );
431
+ }
432
+ });
433
+
434
434
  // Endpoint
435
435
  Cfg?.endpoints?.map((e) => {
436
436
  if (e?.enabled) {
@@ -443,4 +443,13 @@ function HonoInstance(): typeof app {
443
443
  return app;
444
444
  }
445
445
 
446
+ // Public assets
447
+ app.get(
448
+ "/assets/*",
449
+ serveStatic({
450
+ root: "uploads",
451
+ //rewriteRequestPath: (path) => path?.replace(/^\/assets/, ""),
452
+ })
453
+ );
454
+
446
455
  export { HonoInstance };
package/define/index.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  cronConfig,
8
8
  cronCtx,
9
9
  middlewareCtx,
10
+ permissionSchema,
10
11
  } from "../types";
11
12
  import { Cfg } from "../config/";
12
13
  import { deepMerge, freeze } from "../utils";
@@ -40,6 +41,10 @@ function Task(config: cronConfig) {
40
41
  return config;
41
42
  }
42
43
 
44
+ function Permission(config: permissionSchema): permissionSchema {
45
+ return config;
46
+ }
47
+
43
48
  const define = {
44
49
  Service,
45
50
  Config,
@@ -50,6 +55,7 @@ const define = {
50
55
  Middleware,
51
56
  Task,
52
57
  Cron: Task,
58
+ Permission,
53
59
  };
54
60
 
55
61
  export default define;
@@ -8,40 +8,15 @@ const sessionStorage = () => ({
8
8
  get(): {
9
9
  state: object;
10
10
  _v: object;
11
- token: string;
12
- access: {
13
- allAction?: boolean;
14
- aggreate?: boolean;
15
- find?: boolean;
16
- findOne?: boolean;
17
- insertOne?: boolean;
18
- insertMany?: boolean;
19
- updateOne?: boolean;
20
- updateMany?: boolean;
21
- deleteOne?: boolean;
22
- deleteMany?: boolean;
23
- upload?: boolean;
24
- };
11
+
12
+ role: string | null | undefined;
25
13
  } {
26
14
  let store = asyncLocalStorage.getStore() as InstanceType<typeof Map>;
27
15
  return (
28
16
  (store.get(key) as {
29
17
  state: object;
30
18
  _v: object;
31
- token: string;
32
- access: {
33
- allAction?: boolean;
34
- aggreate?: boolean;
35
- find?: boolean;
36
- findOne?: boolean;
37
- insertOne?: boolean;
38
- insertMany?: boolean;
39
- updateOne?: boolean;
40
- updateMany?: boolean;
41
- deleteOne?: boolean;
42
- deleteMany?: boolean;
43
- upload?: boolean;
44
- };
19
+ role: string;
45
20
  }) || {
46
21
  state: {},
47
22
  _v: {},
@@ -53,30 +28,18 @@ const sessionStorage = () => ({
53
28
  input: {
54
29
  state: object;
55
30
  _v?: object;
56
- token?: string | undefined | null;
57
- access?: {
58
- allAction?: boolean;
59
- aggreate?: boolean;
60
- find?: boolean;
61
- findOne?: boolean;
62
- insertOne?: boolean;
63
- insertMany?: boolean;
64
- updateOne?: boolean;
65
- updateMany?: boolean;
66
- deleteOne?: boolean;
67
- deleteMany?: boolean;
68
- upload?: boolean;
69
- };
31
+ role?: string;
32
+ token?: string | null | undefined;
70
33
  } = {
71
34
  state: {},
72
35
  _v: {},
73
- // token: null,
36
+ token: null,
74
37
  }
75
38
  ) {
76
39
  let store = asyncLocalStorage.getStore() as InstanceType<typeof Map>;
77
40
  let generateToken = jwt.sign({
78
41
  state: input.state,
79
- access: input?.access || {},
42
+ role: input?.role || {},
80
43
  });
81
44
 
82
45
  store.set(key, {
@@ -89,7 +52,7 @@ const sessionStorage = () => ({
89
52
  ...(input?._v || {}),
90
53
  setAt: new Date().toString(),
91
54
  },
92
- access: input?.access || {},
55
+ role: input?.role || null,
93
56
  token: generateToken,
94
57
  });
95
58
  },
package/lib/index.ts CHANGED
@@ -3,6 +3,7 @@ import { connectTenantsDatabase } from "../driver";
3
3
  import { loadEndpoints } from "./endpoint";
4
4
  import { loadServices } from "./service";
5
5
  import { initCron } from "./cron";
6
+ import { loadPermissions } from "./permissions";
6
7
  import { loadSocket } from "./socket";
7
8
  // load all ressource
8
9
  async function init(cf = { app: null }) {
@@ -11,6 +12,9 @@ async function init(cf = { app: null }) {
11
12
  // load all collections
12
13
  await loadAllCollections();
13
14
 
15
+ // load all permissions
16
+ await loadPermissions();
17
+
14
18
  // sync all collections database ( Indexes)
15
19
  syncCollectionDatabase();
16
20
 
@@ -0,0 +1,85 @@
1
+ import { omit } from "radash";
2
+ import type {
3
+ Collection,
4
+ Endpoint,
5
+ Field,
6
+ permissionSchema,
7
+ } from "../types/index";
8
+ import { Glob } from "bun";
9
+ import { Cfg } from "../config";
10
+ import { cleanPath, resolvePath } from "../utils";
11
+ import path from "path";
12
+ import { useRest } from "../driver/mongo/rest";
13
+
14
+ async function loadPermissions() {
15
+ let permissions: permissionSchema[] = [];
16
+ if (Cfg.tenants) {
17
+ for await (let t of Cfg.tenants) {
18
+ let tenantPath = `${t.dir}/permissions/**/**.access.{ts,js}`;
19
+ const glob = new Glob(tenantPath);
20
+ for await (let file of glob.scan({
21
+ cwd: Cfg.cwd,
22
+ })) {
23
+ let fullPathFile = path.join(Cfg.cwd || "", file);
24
+ await import(fullPathFile)
25
+ .then((inject) => {
26
+ permissions.push({
27
+ ...inject?.default,
28
+ tenant_id: t.id,
29
+ });
30
+ })
31
+ .catch((err) => {
32
+ console.error(err);
33
+ });
34
+ }
35
+ }
36
+ }
37
+
38
+ Cfg.permissions = permissions;
39
+ }
40
+
41
+ function getPermission(
42
+ role: string,
43
+ tenant_id: string | number
44
+ ): permissionSchema | undefined | null {
45
+ if (Cfg?.permissions?.length) {
46
+ return Cfg.permissions.find((col: permissionSchema) => {
47
+ return col.role === role && col.tenant_id === tenant_id;
48
+ });
49
+ }
50
+ return null;
51
+ }
52
+
53
+ function checkPermission(
54
+ role: string,
55
+ tenant_id: string | number,
56
+ collection: string,
57
+ action: string
58
+ ): boolean {
59
+ let perm = getPermission(role, tenant_id);
60
+ let approved = false;
61
+
62
+ // 1- Check if all connection
63
+ let findAsterixCollection = perm?.access?.find((c) =>
64
+ c.collections.includes("*")
65
+ );
66
+ if (findAsterixCollection) {
67
+ let actionsOnAll = findAsterixCollection.actions.find((a) => a === action);
68
+ actionsOnAll ? (approved = true) : (approved = false);
69
+ }
70
+
71
+ // 2- Check if collection and action
72
+ let findCollection = perm?.access?.find((c) =>
73
+ c.collections.includes(collection)
74
+ );
75
+ if (findCollection) {
76
+ let actionsOnCollection = findCollection.actions.find((a) => a === action);
77
+ actionsOnCollection ? (approved = true) : (approved = false);
78
+ }
79
+
80
+ // console.log("approved", approved);
81
+
82
+ return approved;
83
+ }
84
+
85
+ export { loadPermissions, getPermission, checkPermission };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dnax/core",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "bin": {
package/types/index.ts CHANGED
@@ -162,37 +162,13 @@ export type sessionCtx = {
162
162
  state: object | any;
163
163
  token?: string;
164
164
  _v?: object;
165
- access?: {
166
- allAction?: boolean;
167
- aggreate?: boolean;
168
- find?: boolean;
169
- findOne?: boolean;
170
- insertOne?: boolean;
171
- insertMany?: boolean;
172
- updateOne?: boolean;
173
- updateMany?: boolean;
174
- deleteOne?: boolean;
175
- deleteMany?: boolean;
176
- upload?: boolean;
177
- };
165
+ role?: string | null | undefined;
178
166
  }) => void;
179
167
  get: () => {
180
168
  state: object;
181
169
  _v: object;
182
- access: {
183
- allAction?: boolean;
184
- aggreate?: boolean;
185
- find?: boolean;
186
- findOne?: boolean;
187
- insertOne?: boolean;
188
- insertMany?: boolean;
189
- updateOne?: boolean;
190
- updateMany?: boolean;
191
- deleteOne?: boolean;
192
- deleteMany?: boolean;
193
- upload?: boolean;
194
- };
195
- token: string;
170
+ role: string | null | undefined;
171
+ token: string | null | undefined;
196
172
  };
197
173
  };
198
174
 
@@ -379,7 +355,7 @@ export type Config = {
379
355
  * Tenants database for API
380
356
  */
381
357
  tenants: Tenant[];
382
-
358
+ permissions?: permissionSchema[];
383
359
  /**
384
360
  * Dont touch it automatically generated
385
361
  */
@@ -495,3 +471,38 @@ export type smtpConfigType = {
495
471
  pass: string;
496
472
  };
497
473
  };
474
+
475
+ export type AccessFunctionType = (ctx: {
476
+ isAuth: boolean;
477
+ c?: Context;
478
+ session: sessionCtx;
479
+ rest: InstanceType<typeof useRest>;
480
+ }) => boolean | Promise<any>;
481
+
482
+ export type accessType = {
483
+ collections: Array<string> | ["*"];
484
+ actions: Array<
485
+ | {
486
+ action: Actions;
487
+ handler: AccessFunctionType;
488
+ }
489
+ | "*"
490
+ | "allAction"
491
+ | "aggregate"
492
+ | "find"
493
+ | "findOne"
494
+ | "insertOne"
495
+ | "insertMany"
496
+ | "updateOne"
497
+ | "updateMany"
498
+ | "deleteOne"
499
+ | "deleteMany"
500
+ | "upload"
501
+ >;
502
+ };
503
+
504
+ export type permissionSchema = {
505
+ role: string;
506
+ description?: string;
507
+ access: Array<accessType> | [];
508
+ };