@anteros/core 0.0.1-alpha.1 → 0.0.1-alpha.3
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/package.json +2 -2
- package/server/api.ts +52 -69
- package/types/file.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anteros/core",
|
|
3
3
|
"module": "index.ts",
|
|
4
|
-
"version": "0.0.1-alpha.
|
|
4
|
+
"version": "0.0.1-alpha.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Anteros Core",
|
|
7
7
|
"private": false,
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"ioredis": "^5.11.1",
|
|
25
25
|
"joi": "^18.2.1",
|
|
26
26
|
"jose": "^6.2.3",
|
|
27
|
-
"mongodb": "
|
|
27
|
+
"mongodb": "6.21.0",
|
|
28
28
|
"socket.io": "^4.8.3",
|
|
29
29
|
"ufo": "^1.6.4"
|
|
30
30
|
}
|
package/server/api.ts
CHANGED
|
@@ -42,6 +42,55 @@ const ActionsValues = [
|
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
// ─── File access helper ────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
async function checkFileAccess(
|
|
48
|
+
access: { [key: string]: boolean | ((ctx: any) => boolean | Promise<boolean>) | undefined } | undefined,
|
|
49
|
+
operation: string,
|
|
50
|
+
tenant_id: string,
|
|
51
|
+
c: any,
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
// No access config at all → deny (secure by default)
|
|
54
|
+
if (!access) {
|
|
55
|
+
throw new AppError('Access denied', { status: 401, code: 'ACCESS_DENIED' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const hasWildcard = access['*'] !== undefined;
|
|
59
|
+
const hasSpecific = access[operation] !== undefined;
|
|
60
|
+
|
|
61
|
+
// No matching rule at all → deny
|
|
62
|
+
if (!hasWildcard && !hasSpecific) {
|
|
63
|
+
throw new AppError('Access denied', { status: 401, code: 'ACCESS_DENIED' });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Specific rule takes precedence over wildcard
|
|
67
|
+
const rule = hasSpecific ? access[operation] : access['*'];
|
|
68
|
+
if (rule === undefined) {
|
|
69
|
+
throw new AppError('Access denied', { status: 401, code: 'ACCESS_DENIED' });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const t = c.get('token');
|
|
73
|
+
const accessToken = { value: t?.value ?? null, decoded: t?.decoded ?? null, provided: t?.provided ?? false, expired: t?.expired ?? false };
|
|
74
|
+
|
|
75
|
+
if (accessToken.expired) {
|
|
76
|
+
throw new AppError('Token expired', { status: 401, code: 'TOKEN_EXPIRED' });
|
|
77
|
+
}
|
|
78
|
+
if (!accessToken.value) {
|
|
79
|
+
throw new AppError('Authentication required', { status: 401, code: 'AUTH_REQUIRED' });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let allowed: boolean;
|
|
83
|
+
if (typeof rule === 'function') {
|
|
84
|
+
const rest = new useRest({ internal: false, tenant_id });
|
|
85
|
+
allowed = await rule({ rest, error: fn.error, jwt: func.jwt, token: accessToken });
|
|
86
|
+
} else {
|
|
87
|
+
allowed = rule;
|
|
88
|
+
}
|
|
89
|
+
if (!allowed) {
|
|
90
|
+
throw new AppError(`${operation} not allowed`, { status: 401, code: 'ACCESS_DENIED' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
45
94
|
function initializeApi(app: Hono<{ Variables: HonoVariables }>) {
|
|
46
95
|
|
|
47
96
|
// Crud API
|
|
@@ -567,29 +616,7 @@ function initializeApi(app: Hono<{ Variables: HonoVariables }>) {
|
|
|
567
616
|
if (!colUpload) {
|
|
568
617
|
throw new AppError('File collection `' + collection + '` not found', { status: 400, code: 'FILE_COLLECTION_NOT_FOUND' });
|
|
569
618
|
}
|
|
570
|
-
|
|
571
|
-
const t = c.get('token');
|
|
572
|
-
const accessToken = { value: t?.value ?? null, decoded: t?.decoded ?? null, provided: t?.provided ?? false, expired: t?.expired ?? false };
|
|
573
|
-
|
|
574
|
-
if (accessToken.expired) {
|
|
575
|
-
throw new AppError('Token expired', { status: 401, code: 'TOKEN_EXPIRED' });
|
|
576
|
-
}
|
|
577
|
-
if (!accessToken.value) {
|
|
578
|
-
throw new AppError('Authentication required', { status: 401, code: 'AUTH_REQUIRED' });
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const accessCheck = colUpload.api.access.upload;
|
|
582
|
-
let allowed: boolean;
|
|
583
|
-
if (typeof accessCheck === 'function') {
|
|
584
|
-
const rest = new useRest({ internal: false, tenant_id });
|
|
585
|
-
allowed = await accessCheck({ rest, error: fn.error, jwt: func.jwt, token: accessToken });
|
|
586
|
-
} else {
|
|
587
|
-
allowed = accessCheck;
|
|
588
|
-
}
|
|
589
|
-
if (!allowed) {
|
|
590
|
-
throw new AppError('Upload not allowed', { status: 401, code: 'ACCESS_DENIED' });
|
|
591
|
-
}
|
|
592
|
-
}
|
|
619
|
+
await checkFileAccess(colUpload?.api?.access as any, 'upload', tenant_id, c);
|
|
593
620
|
|
|
594
621
|
const contentType = c.req.header('Content-Type') || '';
|
|
595
622
|
if (!contentType.includes('multipart/form-data')) {
|
|
@@ -646,29 +673,7 @@ function initializeApi(app: Hono<{ Variables: HonoVariables }>) {
|
|
|
646
673
|
if (!colServe) {
|
|
647
674
|
throw new AppError('File collection `' + collection + '` not found', { status: 400, code: 'FILE_COLLECTION_NOT_FOUND' });
|
|
648
675
|
}
|
|
649
|
-
|
|
650
|
-
const t = c.get('token');
|
|
651
|
-
const accessToken = { value: t?.value ?? null, decoded: t?.decoded ?? null, provided: t?.provided ?? false, expired: t?.expired ?? false };
|
|
652
|
-
|
|
653
|
-
if (accessToken.expired) {
|
|
654
|
-
throw new AppError('Token expired', { status: 401, code: 'TOKEN_EXPIRED' });
|
|
655
|
-
}
|
|
656
|
-
if (!accessToken.value) {
|
|
657
|
-
throw new AppError('Authentication required', { status: 401, code: 'AUTH_REQUIRED' });
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
const accessCheck = colServe.api.access.read;
|
|
661
|
-
let allowed: boolean;
|
|
662
|
-
if (typeof accessCheck === 'function') {
|
|
663
|
-
const rest = new useRest({ internal: false, tenant_id });
|
|
664
|
-
allowed = await accessCheck({ rest, error: fn.error, jwt: func.jwt, token: accessToken });
|
|
665
|
-
} else {
|
|
666
|
-
allowed = accessCheck;
|
|
667
|
-
}
|
|
668
|
-
if (!allowed) {
|
|
669
|
-
throw new AppError('Access denied', { status: 401, code: 'ACCESS_DENIED' });
|
|
670
|
-
}
|
|
671
|
-
}
|
|
676
|
+
await checkFileAccess(colServe?.api?.access as any, 'read', tenant_id, c);
|
|
672
677
|
|
|
673
678
|
const filename = basename(c.req.param('file') as string);
|
|
674
679
|
if (!filename || filename.startsWith('.') || filename.includes('..') || filename.includes('/')) {
|
|
@@ -721,29 +726,7 @@ function initializeApi(app: Hono<{ Variables: HonoVariables }>) {
|
|
|
721
726
|
if (!colDelete) {
|
|
722
727
|
throw new AppError('File collection `' + collection + '` not found', { status: 400, code: 'FILE_COLLECTION_NOT_FOUND' });
|
|
723
728
|
}
|
|
724
|
-
|
|
725
|
-
const t = c.get('token');
|
|
726
|
-
const accessToken = { value: t?.value ?? null, decoded: t?.decoded ?? null, provided: t?.provided ?? false, expired: t?.expired ?? false };
|
|
727
|
-
|
|
728
|
-
if (accessToken.expired) {
|
|
729
|
-
throw new AppError('Token expired', { status: 401, code: 'TOKEN_EXPIRED' });
|
|
730
|
-
}
|
|
731
|
-
if (!accessToken.value) {
|
|
732
|
-
throw new AppError('Authentication required', { status: 401, code: 'AUTH_REQUIRED' });
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
const accessCheck = colDelete.api.access.delete;
|
|
736
|
-
let allowed: boolean;
|
|
737
|
-
if (typeof accessCheck === 'function') {
|
|
738
|
-
const rest = new useRest({ internal: false, tenant_id });
|
|
739
|
-
allowed = await accessCheck({ rest, error: fn.error, jwt: func.jwt, token: accessToken });
|
|
740
|
-
} else {
|
|
741
|
-
allowed = accessCheck;
|
|
742
|
-
}
|
|
743
|
-
if (!allowed) {
|
|
744
|
-
throw new AppError('Access denied', { status: 401, code: 'ACCESS_DENIED' });
|
|
745
|
-
}
|
|
746
|
-
}
|
|
729
|
+
await checkFileAccess(colDelete?.api?.access as any, 'delete', tenant_id, c);
|
|
747
730
|
|
|
748
731
|
const filename = basename(c.req.param('file') as string);
|
|
749
732
|
if (!filename || filename.startsWith('.') || filename.includes('..') || filename.includes('/')) {
|
package/types/file.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ type FileAccessHandler = (ctx: {
|
|
|
13
13
|
}) => boolean | Promise<boolean>;
|
|
14
14
|
|
|
15
15
|
type FileApiAccess = {
|
|
16
|
+
/** Default access rule applied when no per-operation rule is set */
|
|
17
|
+
'*'?: boolean | FileAccessHandler;
|
|
16
18
|
/** Access control for POST /upload/:tenant_id/:slug */
|
|
17
19
|
upload?: boolean | FileAccessHandler;
|
|
18
20
|
/** Access control for GET /files/:tenant_id/:slug/:file */
|