@kyro-cms/core 0.3.2 → 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/dist/{WebhookService-BznDc2AT.d.ts → WebhookService-118ZTFis.d.ts} +2 -2
- package/dist/{WebhookService-mZZ75syh.d.cts → WebhookService-AefJfqX0.d.cts} +2 -2
- package/dist/api-handler.cjs +52 -0
- package/dist/api-handler.cjs.map +1 -0
- package/dist/api-handler.d.cts +9 -0
- package/dist/api-handler.d.ts +9 -0
- package/dist/api-handler.js +46 -0
- package/dist/api-handler.js.map +1 -0
- package/dist/{base-Hu6ij8sZ.d.ts → base-DvvNqnM-.d.cts} +16 -5
- package/dist/{base-Db9LkB1N.d.cts → base-eVegJ_Pr.d.ts} +16 -5
- package/dist/bootstrap-DGJ3N7SO.js +6 -0
- package/dist/{bootstrap-LL6O7PWO.js.map → bootstrap-DGJ3N7SO.js.map} +1 -1
- package/dist/bootstrap-O5UGUTYU.cjs +31 -0
- package/dist/{bootstrap-BMWVB2T6.cjs.map → bootstrap-O5UGUTYU.cjs.map} +1 -1
- package/dist/{chunk-QKOFKITP.js → chunk-2HFJUUFZ.js} +3 -11
- package/dist/chunk-2HFJUUFZ.js.map +1 -0
- package/dist/chunk-2UOI5MUC.cjs +1276 -0
- package/dist/chunk-2UOI5MUC.cjs.map +1 -0
- package/dist/{chunk-DIC236EW.js → chunk-342BJNBI.js} +167 -24
- package/dist/chunk-342BJNBI.js.map +1 -0
- package/dist/{chunk-OUGKLCYF.js → chunk-3AJE4SEG.js} +4 -3
- package/dist/chunk-3AJE4SEG.js.map +1 -0
- package/dist/chunk-4UD44U4Z.js +5818 -0
- package/dist/chunk-4UD44U4Z.js.map +1 -0
- package/dist/chunk-5FTY2DLG.js +1258 -0
- package/dist/chunk-5FTY2DLG.js.map +1 -0
- package/dist/{chunk-BXMWDUED.js → chunk-A4USRVTQ.js} +2 -2
- package/dist/chunk-A4USRVTQ.js.map +1 -0
- package/dist/chunk-ADLJSJSN.cjs +13 -0
- package/dist/chunk-ADLJSJSN.cjs.map +1 -0
- package/dist/chunk-ATBOUGQP.cjs +513 -0
- package/dist/chunk-ATBOUGQP.cjs.map +1 -0
- package/dist/chunk-BQ2T4WRS.js +140 -0
- package/dist/chunk-BQ2T4WRS.js.map +1 -0
- package/dist/{chunk-U74F3YZU.js → chunk-DBUYB32X.js} +15 -3
- package/dist/chunk-DBUYB32X.js.map +1 -0
- package/dist/chunk-DE7OQOMD.cjs +5842 -0
- package/dist/chunk-DE7OQOMD.cjs.map +1 -0
- package/dist/chunk-DLHUQO25.cjs +1746 -0
- package/dist/chunk-DLHUQO25.cjs.map +1 -0
- package/dist/{chunk-GE5DMB44.js → chunk-E3BZLMX6.js} +55 -49
- package/dist/chunk-E3BZLMX6.js.map +1 -0
- package/dist/{chunk-44BF6ALS.cjs → chunk-H4XCAPA6.cjs} +55 -49
- package/dist/chunk-H4XCAPA6.cjs.map +1 -0
- package/dist/{chunk-VIONYQ2K.cjs → chunk-IBG6V56E.cjs} +16 -32
- package/dist/chunk-IBG6V56E.cjs.map +1 -0
- package/dist/{chunk-LIJVWQKU.cjs → chunk-IX3ABYKZ.cjs} +43 -31
- package/dist/chunk-IX3ABYKZ.cjs.map +1 -0
- package/dist/chunk-JYGIFBBS.cjs +146 -0
- package/dist/chunk-JYGIFBBS.cjs.map +1 -0
- package/dist/{chunk-42JPONZU.cjs → chunk-K7JPTH3G.cjs} +17 -16
- package/dist/chunk-K7JPTH3G.cjs.map +1 -0
- package/dist/{chunk-RLTG4YZM.cjs → chunk-KOCTZKPV.cjs} +2 -2
- package/dist/chunk-KOCTZKPV.cjs.map +1 -0
- package/dist/{chunk-EWP5AT6A.cjs → chunk-N4H37VN4.cjs} +2 -11
- package/dist/chunk-N4H37VN4.cjs.map +1 -0
- package/dist/chunk-P2YW545G.js +11 -0
- package/dist/chunk-P2YW545G.js.map +1 -0
- package/dist/chunk-Q23JB3KL.js +488 -0
- package/dist/chunk-Q23JB3KL.js.map +1 -0
- package/dist/{chunk-E5X75WNB.js → chunk-QXIQWPAP.js} +14 -30
- package/dist/chunk-QXIQWPAP.js.map +1 -0
- package/dist/chunk-R3XIBBAW.cjs +34 -0
- package/dist/chunk-R3XIBBAW.cjs.map +1 -0
- package/dist/chunk-R4C4O4SE.cjs +622 -0
- package/dist/chunk-R4C4O4SE.cjs.map +1 -0
- package/dist/{chunk-KWGNR4HM.js → chunk-REK7AYOC.js} +82 -9
- package/dist/chunk-REK7AYOC.js.map +1 -0
- package/dist/chunk-RGIQKTZ7.js +68 -0
- package/dist/chunk-RGIQKTZ7.js.map +1 -0
- package/dist/chunk-RYDGMBIG.js +1737 -0
- package/dist/chunk-RYDGMBIG.js.map +1 -0
- package/dist/chunk-SDMNUYVU.js +30 -0
- package/dist/chunk-SDMNUYVU.js.map +1 -0
- package/dist/{chunk-FTSSDDZQ.cjs → chunk-VJT6P4N6.cjs} +82 -9
- package/dist/chunk-VJT6P4N6.cjs.map +1 -0
- package/dist/{chunk-HT6VE4NW.cjs → chunk-W3KPQX7V.cjs} +168 -25
- package/dist/chunk-W3KPQX7V.cjs.map +1 -0
- package/dist/{chunk-LTRCYJAG.js → chunk-WOWUL7ZY.js} +3 -2
- package/dist/chunk-WOWUL7ZY.js.map +1 -0
- package/dist/{chunk-7YITG2US.cjs → chunk-WQBRWOQT.cjs} +3 -2
- package/dist/chunk-WQBRWOQT.cjs.map +1 -0
- package/dist/{chunk-KB6QF4HO.js → chunk-WSCJQI2B.js} +305 -152
- package/dist/chunk-WSCJQI2B.js.map +1 -0
- package/dist/chunk-X3CU27OO.cjs +78 -0
- package/dist/chunk-X3CU27OO.cjs.map +1 -0
- package/dist/chunk-Y3TM7WH7.js +617 -0
- package/dist/chunk-Y3TM7WH7.js.map +1 -0
- package/dist/{chunk-PNBZZ76A.cjs → chunk-Z2OVHWHB.cjs} +305 -151
- package/dist/chunk-Z2OVHWHB.cjs.map +1 -0
- package/dist/cli/index.cjs +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/client.cjs +23 -13
- package/dist/client.d.cts +4 -2
- package/dist/client.d.ts +4 -2
- package/dist/client.js +3 -1
- package/dist/drizzle/index.cjs +20 -19
- package/dist/drizzle/index.d.cts +28 -7
- package/dist/drizzle/index.d.ts +28 -7
- package/dist/drizzle/index.js +5 -4
- package/dist/fields/index.cjs +105 -0
- package/dist/fields/index.cjs.map +1 -0
- package/dist/fields/index.d.cts +27 -0
- package/dist/fields/index.d.ts +27 -0
- package/dist/fields/index.js +4 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/graphql/index.cjs +4 -3
- package/dist/graphql/index.d.cts +3 -2
- package/dist/graphql/index.d.ts +3 -2
- package/dist/graphql/index.js +2 -1
- package/dist/{index-Ci6r4xnN.d.ts → index-CLp-DRKA.d.ts} +2 -1
- package/dist/{index-11MDNKce.d.cts → index-DfO7G4kN.d.cts} +2 -1
- package/dist/index.cjs +2659 -6670
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -47
- package/dist/index.d.ts +138 -47
- package/dist/index.js +2356 -6529
- package/dist/index.js.map +1 -1
- package/dist/integration.cjs +68 -0
- package/dist/integration.cjs.map +1 -0
- package/dist/integration.d.cts +27 -0
- package/dist/integration.d.ts +27 -0
- package/dist/integration.js +61 -0
- package/dist/integration.js.map +1 -0
- package/dist/mongodb/index.cjs +4 -4
- package/dist/mongodb/index.d.cts +20 -6
- package/dist/mongodb/index.d.ts +20 -6
- package/dist/mongodb/index.js +2 -2
- package/dist/postgres-auth-adapter-7F3ECO7I.js +5 -0
- package/dist/{postgres-auth-adapter-OTRWSTT5.js.map → postgres-auth-adapter-7F3ECO7I.js.map} +1 -1
- package/dist/postgres-auth-adapter-Z463NYJZ.cjs +14 -0
- package/dist/{postgres-auth-adapter-EVRPO7BQ.cjs.map → postgres-auth-adapter-Z463NYJZ.cjs.map} +1 -1
- package/dist/redis-adapter-LPUWLE4Y.cjs +13 -0
- package/dist/{redis-adapter-E7PMN5HW.cjs.map → redis-adapter-LPUWLE4Y.cjs.map} +1 -1
- package/dist/redis-adapter-THYDCGQR.js +4 -0
- package/dist/{redis-adapter-HOO67RBQ.js.map → redis-adapter-THYDCGQR.js.map} +1 -1
- package/dist/rest/index.cjs +9 -5
- package/dist/rest/index.d.cts +6 -3
- package/dist/rest/index.d.ts +6 -3
- package/dist/rest/index.js +7 -3
- package/dist/{schema-CNB2DDTX.js → schema-6Q4W6AE6.js} +3 -3
- package/dist/{schema-CNB2DDTX.js.map → schema-6Q4W6AE6.js.map} +1 -1
- package/dist/{schema-Y777CQQS.cjs → schema-TIYTCIKX.cjs} +14 -14
- package/dist/{schema-Y777CQQS.cjs.map → schema-TIYTCIKX.cjs.map} +1 -1
- package/dist/templates/index.cjs +27 -23
- package/dist/templates/index.d.cts +8 -2
- package/dist/templates/index.d.ts +8 -2
- package/dist/templates/index.js +1 -1
- package/dist/trpc/index.cjs +12 -11
- package/dist/trpc/index.d.cts +3 -2
- package/dist/trpc/index.d.ts +3 -2
- package/dist/trpc/index.js +3 -2
- package/dist/{types-1u353OHN.d.ts → types-BnTm7oJG.d.cts} +7 -3
- package/dist/{types-1u353OHN.d.cts → types-BnTm7oJG.d.ts} +7 -3
- package/dist/{types-kGfsGdos.d.cts → types-Bs1up4yP.d.ts} +76 -244
- package/dist/{types-kGfsGdos.d.ts → types-J3R9nVsZ.d.cts} +76 -244
- package/dist/types-VtjUxIMp.d.cts +246 -0
- package/dist/types-VtjUxIMp.d.ts +246 -0
- package/package.json +16 -9
- package/dist/bootstrap-BMWVB2T6.cjs +0 -31
- package/dist/bootstrap-LL6O7PWO.js +0 -6
- package/dist/chunk-42JPONZU.cjs.map +0 -1
- package/dist/chunk-44BF6ALS.cjs.map +0 -1
- package/dist/chunk-4M5PHMUE.cjs +0 -947
- package/dist/chunk-4M5PHMUE.cjs.map +0 -1
- package/dist/chunk-6MSSF46R.js +0 -941
- package/dist/chunk-6MSSF46R.js.map +0 -1
- package/dist/chunk-7YITG2US.cjs.map +0 -1
- package/dist/chunk-BTOE3VUK.js +0 -330
- package/dist/chunk-BTOE3VUK.js.map +0 -1
- package/dist/chunk-BXMWDUED.js.map +0 -1
- package/dist/chunk-DIC236EW.js.map +0 -1
- package/dist/chunk-E5X75WNB.js.map +0 -1
- package/dist/chunk-E63IF3MD.cjs +0 -951
- package/dist/chunk-E63IF3MD.cjs.map +0 -1
- package/dist/chunk-EWP5AT6A.cjs.map +0 -1
- package/dist/chunk-FTSSDDZQ.cjs.map +0 -1
- package/dist/chunk-GE5DMB44.js.map +0 -1
- package/dist/chunk-GVFB5C6O.cjs +0 -345
- package/dist/chunk-GVFB5C6O.cjs.map +0 -1
- package/dist/chunk-HT6VE4NW.cjs.map +0 -1
- package/dist/chunk-HVSQDZZJ.cjs +0 -765
- package/dist/chunk-HVSQDZZJ.cjs.map +0 -1
- package/dist/chunk-HYC4GNHX.js +0 -758
- package/dist/chunk-HYC4GNHX.js.map +0 -1
- package/dist/chunk-KB6QF4HO.js.map +0 -1
- package/dist/chunk-KWGNR4HM.js.map +0 -1
- package/dist/chunk-LIJVWQKU.cjs.map +0 -1
- package/dist/chunk-LTRCYJAG.js.map +0 -1
- package/dist/chunk-OUGKLCYF.js.map +0 -1
- package/dist/chunk-PNBZZ76A.cjs.map +0 -1
- package/dist/chunk-QKOFKITP.js.map +0 -1
- package/dist/chunk-RLTG4YZM.cjs.map +0 -1
- package/dist/chunk-RRYXQMZG.js +0 -935
- package/dist/chunk-RRYXQMZG.js.map +0 -1
- package/dist/chunk-U74F3YZU.js.map +0 -1
- package/dist/chunk-VIONYQ2K.cjs.map +0 -1
- package/dist/postgres-auth-adapter-EVRPO7BQ.cjs +0 -14
- package/dist/postgres-auth-adapter-OTRWSTT5.js +0 -5
- package/dist/redis-adapter-E7PMN5HW.cjs +0 -13
- package/dist/redis-adapter-HOO67RBQ.js +0 -4
package/dist/chunk-6MSSF46R.js
DELETED
|
@@ -1,941 +0,0 @@
|
|
|
1
|
-
import { EmailTransport } from './chunk-HYC4GNHX.js';
|
|
2
|
-
import { WEBHOOK_EVENTS, evaluateAccess, hasApiKeyPermission, extractApiKeyFromRequest, validateApiKey, createApiKeyContext } from './chunk-E5X75WNB.js';
|
|
3
|
-
import { Hono } from 'hono';
|
|
4
|
-
import jwt from 'jsonwebtoken';
|
|
5
|
-
|
|
6
|
-
function createAuthMiddleware(config) {
|
|
7
|
-
const {
|
|
8
|
-
secret,
|
|
9
|
-
issuer,
|
|
10
|
-
audience,
|
|
11
|
-
db,
|
|
12
|
-
userLookup,
|
|
13
|
-
extractToken = defaultExtractToken
|
|
14
|
-
} = config;
|
|
15
|
-
return async function authMiddleware(req) {
|
|
16
|
-
const apiKeyRaw = extractApiKeyFromRequest(req);
|
|
17
|
-
if (apiKeyRaw && db) {
|
|
18
|
-
const result = await validateApiKey(apiKeyRaw, db, userLookup);
|
|
19
|
-
if (result.valid && result.user) {
|
|
20
|
-
return {
|
|
21
|
-
user: result.user,
|
|
22
|
-
tenantContext: createTenantContextFromUser(result.user),
|
|
23
|
-
apiKeyContext: createApiKeyContext(result),
|
|
24
|
-
status: 200,
|
|
25
|
-
authType: "apikey"
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
if (result.error) {
|
|
29
|
-
return {
|
|
30
|
-
status: 401,
|
|
31
|
-
error: result.error
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const token = extractToken(req);
|
|
36
|
-
if (!token) {
|
|
37
|
-
return {
|
|
38
|
-
status: 401,
|
|
39
|
-
error: "No authentication token provided"
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const payload = jwt.verify(token, secret, {
|
|
44
|
-
issuer,
|
|
45
|
-
audience
|
|
46
|
-
});
|
|
47
|
-
const user = {
|
|
48
|
-
id: payload.sub,
|
|
49
|
-
email: payload.email,
|
|
50
|
-
role: payload.role,
|
|
51
|
-
tenantId: payload.tenantId
|
|
52
|
-
};
|
|
53
|
-
return {
|
|
54
|
-
user,
|
|
55
|
-
token,
|
|
56
|
-
tenantContext: createTenantContextFromUser(user),
|
|
57
|
-
status: 200,
|
|
58
|
-
authType: "jwt"
|
|
59
|
-
};
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof jwt.TokenExpiredError) {
|
|
62
|
-
return {
|
|
63
|
-
status: 401,
|
|
64
|
-
error: "Token has expired"
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
if (error instanceof jwt.JsonWebTokenError) {
|
|
68
|
-
return {
|
|
69
|
-
status: 401,
|
|
70
|
-
error: "Invalid token"
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
return {
|
|
74
|
-
status: 401,
|
|
75
|
-
error: "Authentication failed"
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
function defaultExtractToken(req) {
|
|
81
|
-
const authHeader = req.headers.get("Authorization");
|
|
82
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
83
|
-
return authHeader.slice(7);
|
|
84
|
-
}
|
|
85
|
-
const cookieHeader = req.headers.get("Cookie");
|
|
86
|
-
if (cookieHeader) {
|
|
87
|
-
const cookies = Object.fromEntries(
|
|
88
|
-
cookieHeader.split("; ").map((c) => {
|
|
89
|
-
const [key, ...val] = c.split("=");
|
|
90
|
-
return [key.trim(), val.join("=")];
|
|
91
|
-
})
|
|
92
|
-
);
|
|
93
|
-
return cookies["auth_token"] || null;
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
function createTenantContextFromUser(user) {
|
|
98
|
-
return {
|
|
99
|
-
tenantId: user.tenantId || "default",
|
|
100
|
-
userId: user.id || "anonymous",
|
|
101
|
-
role: user.role || "guest",
|
|
102
|
-
roles: [user.role || "guest"],
|
|
103
|
-
permissions: [],
|
|
104
|
-
isSuperAdmin: user.role === "super_admin"
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function generateToken(payload, secret, options = {}) {
|
|
108
|
-
return jwt.sign(payload, secret, {
|
|
109
|
-
expiresIn: options.expiresIn || "24h",
|
|
110
|
-
issuer: options.issuer,
|
|
111
|
-
audience: options.audience
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// src/auth/security/in-memory-rate-limit.ts
|
|
116
|
-
var InMemoryRateLimiter = class {
|
|
117
|
-
storage = /* @__PURE__ */ new Map();
|
|
118
|
-
userStorage = /* @__PURE__ */ new Map();
|
|
119
|
-
limits;
|
|
120
|
-
userLimits;
|
|
121
|
-
constructor(limits, userLimits) {
|
|
122
|
-
this.limits = { ...DEFAULT_RATE_LIMITS, ...limits };
|
|
123
|
-
this.userLimits = userLimits || {
|
|
124
|
-
"user:api": { window: 6e4, max: 500 },
|
|
125
|
-
"user:write": { window: 36e5, max: 100 }
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
getKey(type, identifier) {
|
|
129
|
-
return `${type}:${identifier}`;
|
|
130
|
-
}
|
|
131
|
-
getUserKey(type, userId, identifier) {
|
|
132
|
-
return `user:${type}:${userId}:${identifier}`;
|
|
133
|
-
}
|
|
134
|
-
cleanupOldEntries(entries, window) {
|
|
135
|
-
const now = Date.now();
|
|
136
|
-
const windowStart = now - window;
|
|
137
|
-
while (entries.length > 0 && entries[0].timestamp < windowStart) {
|
|
138
|
-
entries.shift();
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
async check(type, identifier) {
|
|
142
|
-
const config = this.limits[type] || this.limits["api:general"];
|
|
143
|
-
const key = this.getKey(type, identifier);
|
|
144
|
-
let entries = this.storage.get(key);
|
|
145
|
-
if (!entries) {
|
|
146
|
-
entries = [];
|
|
147
|
-
this.storage.set(key, entries);
|
|
148
|
-
}
|
|
149
|
-
this.cleanupOldEntries(entries, config.window);
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
const count = entries.reduce((sum, entry) => sum + entry.count, 0);
|
|
152
|
-
entries.push({ timestamp: now, count: 1 });
|
|
153
|
-
if (count >= config.max) {
|
|
154
|
-
const oldestEntry = entries.reduce(
|
|
155
|
-
(oldest, current) => oldest.timestamp < current.timestamp ? oldest : current,
|
|
156
|
-
entries[0]
|
|
157
|
-
);
|
|
158
|
-
const resetAt = oldestEntry.timestamp + config.window;
|
|
159
|
-
return {
|
|
160
|
-
allowed: false,
|
|
161
|
-
remaining: 0,
|
|
162
|
-
resetAt,
|
|
163
|
-
retryAfter: Math.ceil((resetAt - now) / 1e3)
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
allowed: true,
|
|
168
|
-
remaining: config.max - count - 1,
|
|
169
|
-
resetAt: now + config.window
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
async checkUser(type, userId, identifier) {
|
|
173
|
-
const config = this.userLimits[type] || this.userLimits["user:api"];
|
|
174
|
-
const userMap = this.userStorage.get(userId);
|
|
175
|
-
let entries = [];
|
|
176
|
-
if (userMap) {
|
|
177
|
-
entries = userMap.get(this.getKey(type, identifier)) || [];
|
|
178
|
-
} else {
|
|
179
|
-
if (!this.userStorage.has(userId)) {
|
|
180
|
-
this.userStorage.set(userId, /* @__PURE__ */ new Map());
|
|
181
|
-
}
|
|
182
|
-
this.userStorage.get(userId).set(this.getKey(type, identifier), entries);
|
|
183
|
-
}
|
|
184
|
-
this.cleanupOldEntries(entries, config.window);
|
|
185
|
-
const now = Date.now();
|
|
186
|
-
const count = entries.reduce((sum, entry) => sum + entry.count, 0);
|
|
187
|
-
entries.push({ timestamp: now, count: 1 });
|
|
188
|
-
if (count >= config.max) {
|
|
189
|
-
const oldestEntry = entries.reduce(
|
|
190
|
-
(oldest, current) => oldest.timestamp < current.timestamp ? oldest : current,
|
|
191
|
-
entries[0]
|
|
192
|
-
);
|
|
193
|
-
const resetAt = oldestEntry.timestamp + config.window;
|
|
194
|
-
return {
|
|
195
|
-
allowed: false,
|
|
196
|
-
remaining: 0,
|
|
197
|
-
resetAt,
|
|
198
|
-
retryAfter: Math.ceil((resetAt - now) / 1e3)
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
return {
|
|
202
|
-
allowed: true,
|
|
203
|
-
remaining: config.max - count - 1,
|
|
204
|
-
resetAt: now + config.window
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
async reset(type, identifier) {
|
|
208
|
-
const key = this.getKey(type, identifier);
|
|
209
|
-
this.storage.delete(key);
|
|
210
|
-
}
|
|
211
|
-
async resetUser(type, userId, identifier) {
|
|
212
|
-
const userMap = this.userStorage.get(userId);
|
|
213
|
-
if (userMap) {
|
|
214
|
-
const key = this.getKey(type, identifier);
|
|
215
|
-
userMap.delete(key);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
async getStatus(type, identifier) {
|
|
219
|
-
const config = this.limits[type] || this.limits["api:general"];
|
|
220
|
-
const key = this.getKey(type, identifier);
|
|
221
|
-
let entries = this.storage.get(key);
|
|
222
|
-
if (!entries) {
|
|
223
|
-
entries = [];
|
|
224
|
-
this.storage.set(key, entries);
|
|
225
|
-
}
|
|
226
|
-
this.cleanupOldEntries(entries, config.window);
|
|
227
|
-
const now = Date.now();
|
|
228
|
-
const count = entries.reduce((sum, entry) => sum + entry.count, 0);
|
|
229
|
-
return {
|
|
230
|
-
count,
|
|
231
|
-
limit: config.max,
|
|
232
|
-
remaining: Math.max(0, config.max - count),
|
|
233
|
-
resetAt: now + config.window
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
setLimit(type, config) {
|
|
237
|
-
this.limits[type] = config;
|
|
238
|
-
}
|
|
239
|
-
setUserLimit(type, config) {
|
|
240
|
-
this.userLimits[type] = config;
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
|
-
var DEFAULT_RATE_LIMITS = {
|
|
244
|
-
"auth:login": { window: 9e5, max: 5 },
|
|
245
|
-
"auth:register": { window: 36e5, max: 3 },
|
|
246
|
-
"auth:forgot": { window: 36e5, max: 3 },
|
|
247
|
-
"auth:reset": { window: 36e5, max: 5 },
|
|
248
|
-
"auth:verify": { window: 36e5, max: 5 },
|
|
249
|
-
"api:general": { window: 6e4, max: 100 },
|
|
250
|
-
"api:authenticated": { window: 6e4, max: 200 }
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
// src/api/rest/hono-app.ts
|
|
254
|
-
var COLLECTION_EVENT_MAP = {
|
|
255
|
-
_media: {
|
|
256
|
-
create: WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
257
|
-
update: WEBHOOK_EVENTS.MEDIA_UPLOAD,
|
|
258
|
-
delete: WEBHOOK_EVENTS.MEDIA_DELETE
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
function getWebhookEvent(collection, operation) {
|
|
262
|
-
const mapped = COLLECTION_EVENT_MAP[collection];
|
|
263
|
-
if (mapped) return mapped[operation];
|
|
264
|
-
return `collection.${operation}`;
|
|
265
|
-
}
|
|
266
|
-
async function checkCollectionAccess(collection, operation, req, ctxUser, ctxTenantID, apiKeyContext, enablePublicAccess = true, defaultCollectionAccess = "read") {
|
|
267
|
-
const accessRule = collection.access?.[operation];
|
|
268
|
-
if (accessRule) {
|
|
269
|
-
const allowed = await evaluateAccess(accessRule, {
|
|
270
|
-
req,
|
|
271
|
-
user: ctxUser,
|
|
272
|
-
tenantID: ctxTenantID
|
|
273
|
-
});
|
|
274
|
-
if (allowed === false) {
|
|
275
|
-
return { allowed: false, error: "Access denied", status: 403 };
|
|
276
|
-
}
|
|
277
|
-
} else if (!ctxUser) {
|
|
278
|
-
const accessLevels = {
|
|
279
|
-
none: false,
|
|
280
|
-
read: operation === "read",
|
|
281
|
-
create: operation === "read" || operation === "create",
|
|
282
|
-
update: operation === "read" || operation === "create" || operation === "update",
|
|
283
|
-
admin: true
|
|
284
|
-
};
|
|
285
|
-
const allowed = enablePublicAccess && accessLevels[defaultCollectionAccess];
|
|
286
|
-
if (!allowed) {
|
|
287
|
-
return {
|
|
288
|
-
allowed: false,
|
|
289
|
-
error: "Authentication required",
|
|
290
|
-
status: 401
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (apiKeyContext?.permissions?.length > 0) {
|
|
295
|
-
const resource = collection.slug;
|
|
296
|
-
const action = operation === "read" ? "read" : operation === "create" ? "create" : "update";
|
|
297
|
-
const permission = `${resource}:${action}`;
|
|
298
|
-
if (!hasApiKeyPermission(apiKeyContext.permissions, permission) && !hasApiKeyPermission(apiKeyContext.permissions, `${resource}:admin`)) {
|
|
299
|
-
return {
|
|
300
|
-
allowed: false,
|
|
301
|
-
error: `Missing permission: ${permission}`,
|
|
302
|
-
status: 403
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return { allowed: true };
|
|
307
|
-
}
|
|
308
|
-
async function checkGlobalAccess(global, operation, req, ctxUser, ctxTenantID, enablePublicAccess = true) {
|
|
309
|
-
const accessRule = global.access?.[operation];
|
|
310
|
-
if (accessRule) {
|
|
311
|
-
const allowed = await evaluateAccess(accessRule, {
|
|
312
|
-
req,
|
|
313
|
-
user: ctxUser,
|
|
314
|
-
tenantID: ctxTenantID
|
|
315
|
-
});
|
|
316
|
-
if (allowed === false) {
|
|
317
|
-
return { allowed: false, error: "Access denied", status: 403 };
|
|
318
|
-
}
|
|
319
|
-
} else if (!ctxUser) {
|
|
320
|
-
const accessLevels = {
|
|
321
|
-
none: false,
|
|
322
|
-
read: operation === "read",
|
|
323
|
-
update: operation === "read" || operation === "update"
|
|
324
|
-
};
|
|
325
|
-
const allowed = enablePublicAccess && accessLevels[operation === "read" ? "read" : "admin"];
|
|
326
|
-
if (!allowed) {
|
|
327
|
-
return {
|
|
328
|
-
allowed: false,
|
|
329
|
-
error: "Authentication required",
|
|
330
|
-
status: 401
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return { allowed: true };
|
|
335
|
-
}
|
|
336
|
-
async function resolveAuthContext(req, authMw, staticUser, staticTenantID) {
|
|
337
|
-
if (!authMw) {
|
|
338
|
-
return {
|
|
339
|
-
user: staticUser,
|
|
340
|
-
tenantID: staticTenantID,
|
|
341
|
-
apiKeyContext: void 0
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
const result = await authMw(req);
|
|
345
|
-
if (result.status === 401) {
|
|
346
|
-
return { user: void 0, tenantID: void 0, apiKeyContext: void 0 };
|
|
347
|
-
}
|
|
348
|
-
return {
|
|
349
|
-
user: result.user || staticUser,
|
|
350
|
-
tenantID: result.tenantContext?.tenantId || staticTenantID,
|
|
351
|
-
apiKeyContext: result.apiKeyContext
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function createHonoApp(options) {
|
|
355
|
-
const {
|
|
356
|
-
registry,
|
|
357
|
-
db,
|
|
358
|
-
authSecret,
|
|
359
|
-
user,
|
|
360
|
-
tenantID,
|
|
361
|
-
cors,
|
|
362
|
-
webhookService,
|
|
363
|
-
settings
|
|
364
|
-
} = options;
|
|
365
|
-
const app = new Hono();
|
|
366
|
-
const apiAccess = settings?.access?.apiAccess;
|
|
367
|
-
if (apiAccess?.restEnabled === false) {
|
|
368
|
-
app.all("/api/*", (c) => {
|
|
369
|
-
return c.json({ error: "REST API is disabled" }, 503);
|
|
370
|
-
});
|
|
371
|
-
return app;
|
|
372
|
-
}
|
|
373
|
-
const enablePublicAccess = settings?.access?.enablePublicAccess ?? true;
|
|
374
|
-
const defaultCollectionAccess = settings?.access?.defaultCollectionAccess ?? "read";
|
|
375
|
-
const requireAuth = apiAccess?.requireAuth;
|
|
376
|
-
if (requireAuth && !authSecret) {
|
|
377
|
-
throw new Error(
|
|
378
|
-
"authSecret is required when requireAuth is enabled in access settings"
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
const authMw = authSecret ? createAuthMiddleware({ secret: authSecret, db }) : null;
|
|
382
|
-
const settingsCorsRaw = apiAccess?.cors?.allowedOrigins;
|
|
383
|
-
const optionsCors = cors?.origins;
|
|
384
|
-
const settingsCors = Array.isArray(settingsCorsRaw) ? settingsCorsRaw : typeof settingsCorsRaw === "string" && settingsCorsRaw ? settingsCorsRaw.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
385
|
-
const allowedOrigins = settingsCors.length > 0 ? settingsCors : optionsCors || [];
|
|
386
|
-
const corsEnabled = allowedOrigins.length > 0 || !!cors;
|
|
387
|
-
if (corsEnabled) {
|
|
388
|
-
app.use("*", async (c, next) => {
|
|
389
|
-
const origin = c.req.header("Origin") || "*";
|
|
390
|
-
if (allowedOrigins.length > 0 && !allowedOrigins.includes(origin)) {
|
|
391
|
-
return c.json({ error: "Origin not allowed" }, 403);
|
|
392
|
-
}
|
|
393
|
-
const allowOrigin = allowedOrigins.length > 0 ? origin : "*";
|
|
394
|
-
c.header("Access-Control-Allow-Origin", allowOrigin);
|
|
395
|
-
c.header(
|
|
396
|
-
"Access-Control-Allow-Methods",
|
|
397
|
-
"GET, POST, PATCH, DELETE, OPTIONS"
|
|
398
|
-
);
|
|
399
|
-
c.header(
|
|
400
|
-
"Access-Control-Allow-Headers",
|
|
401
|
-
"Content-Type, Authorization, X-API-Key"
|
|
402
|
-
);
|
|
403
|
-
if (cors?.credentials) {
|
|
404
|
-
c.header("Access-Control-Allow-Credentials", "true");
|
|
405
|
-
}
|
|
406
|
-
if (c.req.method === "OPTIONS") {
|
|
407
|
-
return c.text("");
|
|
408
|
-
}
|
|
409
|
-
await next();
|
|
410
|
-
});
|
|
411
|
-
}
|
|
412
|
-
const rateLimiting = settings?.access?.rateLimiting;
|
|
413
|
-
let rateLimiter;
|
|
414
|
-
if (rateLimiting?.enabled) {
|
|
415
|
-
const maxRequests = rateLimiting.maxRequests || 100;
|
|
416
|
-
const windowMs = rateLimiting.windowMs || 6e4;
|
|
417
|
-
rateLimiter = new InMemoryRateLimiter({
|
|
418
|
-
"api:general": { window: windowMs, max: maxRequests }
|
|
419
|
-
});
|
|
420
|
-
app.use("/api/*", async (c, next) => {
|
|
421
|
-
if (!rateLimiter) {
|
|
422
|
-
return next();
|
|
423
|
-
}
|
|
424
|
-
const ip = c.req.header("CF-Connecting-IP") || c.req.header("X-Forwarded-For")?.split(",")[0]?.trim() || c.req.header("X-Real-IP") || "unknown";
|
|
425
|
-
const result = await rateLimiter.check("api:general", ip);
|
|
426
|
-
c.header("X-RateLimit-Limit", String(maxRequests));
|
|
427
|
-
c.header("X-RateLimit-Remaining", String(result.remaining));
|
|
428
|
-
c.header("X-RateLimit-Reset", String(result.resetAt));
|
|
429
|
-
if (!result.allowed) {
|
|
430
|
-
return c.json(
|
|
431
|
-
{
|
|
432
|
-
error: "Too many requests",
|
|
433
|
-
retryAfter: result.retryAfter
|
|
434
|
-
},
|
|
435
|
-
429
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
await next();
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
app.get("/api/health", (c) => {
|
|
442
|
-
return c.json({
|
|
443
|
-
status: "ok",
|
|
444
|
-
version: "0.1.0",
|
|
445
|
-
collections: registry.getCollectionSlugs(),
|
|
446
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
app.get("/api/collections", (c) => {
|
|
450
|
-
const collections2 = registry.getCollections().map((col) => ({
|
|
451
|
-
slug: col.slug,
|
|
452
|
-
label: col.label || col.slug,
|
|
453
|
-
fields: col.fields.filter((f) => f.name).map((f) => ({
|
|
454
|
-
name: f.name,
|
|
455
|
-
type: f.type,
|
|
456
|
-
required: f.required,
|
|
457
|
-
label: f.label
|
|
458
|
-
}))
|
|
459
|
-
}));
|
|
460
|
-
return c.json(collections2);
|
|
461
|
-
});
|
|
462
|
-
app.get("/api/search", async (c) => {
|
|
463
|
-
try {
|
|
464
|
-
const query = c.req.query("q") || "";
|
|
465
|
-
const collectionsParam = c.req.query("collections") || "";
|
|
466
|
-
const limit = Math.min(parseInt(c.req.query("limit") || "10"), 50);
|
|
467
|
-
if (!query || query.length < 2) {
|
|
468
|
-
return c.json({ results: [], message: "Query too short" });
|
|
469
|
-
}
|
|
470
|
-
console.log("[API /api/search] Query:", query);
|
|
471
|
-
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(
|
|
472
|
-
c.req.raw,
|
|
473
|
-
authMw,
|
|
474
|
-
user,
|
|
475
|
-
tenantID
|
|
476
|
-
);
|
|
477
|
-
const targetCollections = collectionsParam ? collectionsParam.split(",").filter(Boolean) : registry.getCollectionSlugs();
|
|
478
|
-
const results = [];
|
|
479
|
-
const regex = new RegExp(
|
|
480
|
-
query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
|
|
481
|
-
"i"
|
|
482
|
-
);
|
|
483
|
-
for (const collection of registry.getCollections()) {
|
|
484
|
-
if (!targetCollections.includes(collection.slug)) continue;
|
|
485
|
-
if (collection.slug === "users") continue;
|
|
486
|
-
const access = await checkCollectionAccess(
|
|
487
|
-
collection,
|
|
488
|
-
"read",
|
|
489
|
-
c.req.raw,
|
|
490
|
-
ctxUser,
|
|
491
|
-
ctxTenantID,
|
|
492
|
-
void 0,
|
|
493
|
-
enablePublicAccess,
|
|
494
|
-
defaultCollectionAccess
|
|
495
|
-
);
|
|
496
|
-
if (!access.allowed) continue;
|
|
497
|
-
const searchableFields = collection.fields.filter(
|
|
498
|
-
(f) => f.name && f.name !== "id" && (f.type === "text" || f.type === "email" || f.type === "textarea" || f.type === "richtext" || f.indexed) && !f.admin?.hidden
|
|
499
|
-
).map((f) => f.name);
|
|
500
|
-
if (searchableFields.length === 0) continue;
|
|
501
|
-
try {
|
|
502
|
-
const orConditions = searchableFields.map(
|
|
503
|
-
(field) => {
|
|
504
|
-
const condition = {};
|
|
505
|
-
condition[field] = { like: `%${query}%` };
|
|
506
|
-
return condition;
|
|
507
|
-
}
|
|
508
|
-
);
|
|
509
|
-
const searchResult = await db.find({
|
|
510
|
-
collection: collection.slug,
|
|
511
|
-
where: { OR: orConditions },
|
|
512
|
-
limit,
|
|
513
|
-
tenantID: ctxTenantID
|
|
514
|
-
});
|
|
515
|
-
for (const doc of searchResult.docs) {
|
|
516
|
-
const titleField = collection.admin?.useAsTitle || searchableFields.find(
|
|
517
|
-
(f) => f === "title" || f === "name" || f === "heading" || f === "slug"
|
|
518
|
-
);
|
|
519
|
-
const title = titleField ? doc[titleField] : doc.id;
|
|
520
|
-
results.push({
|
|
521
|
-
collection: collection.slug,
|
|
522
|
-
label: collection.label || collection.slug,
|
|
523
|
-
id: doc.id,
|
|
524
|
-
title: String(title || "Untitled"),
|
|
525
|
-
doc
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
} catch (err) {
|
|
529
|
-
console.error(`Search error for ${collection.slug}:`, err);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
results.sort((a, b) => a.label.localeCompare(b.label));
|
|
533
|
-
return c.json({ results });
|
|
534
|
-
} catch (error) {
|
|
535
|
-
return c.json({ error: error.message, results: [] }, 500);
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
const collections = registry.getCollections();
|
|
539
|
-
for (const collection of collections) {
|
|
540
|
-
const slug = collection.slug;
|
|
541
|
-
const basePath = `/api/${slug}`;
|
|
542
|
-
app.get(basePath, async (c) => {
|
|
543
|
-
try {
|
|
544
|
-
const {
|
|
545
|
-
user: ctxUser,
|
|
546
|
-
tenantID: ctxTenantID,
|
|
547
|
-
apiKeyContext
|
|
548
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
549
|
-
const access = await checkCollectionAccess(
|
|
550
|
-
collection,
|
|
551
|
-
"read",
|
|
552
|
-
c.req.raw,
|
|
553
|
-
ctxUser,
|
|
554
|
-
ctxTenantID,
|
|
555
|
-
apiKeyContext
|
|
556
|
-
);
|
|
557
|
-
if (!access.allowed) {
|
|
558
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
559
|
-
}
|
|
560
|
-
const url = new URL(c.req.url);
|
|
561
|
-
const page = parseInt(url.searchParams.get("page") || "1");
|
|
562
|
-
const limit = Math.min(
|
|
563
|
-
parseInt(url.searchParams.get("limit") || "10"),
|
|
564
|
-
100
|
|
565
|
-
);
|
|
566
|
-
const sort = url.searchParams.get("sort") || void 0;
|
|
567
|
-
const depth = parseInt(url.searchParams.get("depth") || "0");
|
|
568
|
-
const select = url.searchParams.get("select")?.split(",") || void 0;
|
|
569
|
-
let where = {};
|
|
570
|
-
const whereParam = url.searchParams.get("where");
|
|
571
|
-
if (whereParam) {
|
|
572
|
-
try {
|
|
573
|
-
where = JSON.parse(whereParam);
|
|
574
|
-
} catch {
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
const result = await db.find({
|
|
578
|
-
collection: slug,
|
|
579
|
-
where,
|
|
580
|
-
sort,
|
|
581
|
-
limit,
|
|
582
|
-
page,
|
|
583
|
-
depth,
|
|
584
|
-
tenantID: ctxTenantID,
|
|
585
|
-
select
|
|
586
|
-
});
|
|
587
|
-
return c.json(result);
|
|
588
|
-
} catch (error) {
|
|
589
|
-
return c.json({ error: error.message }, 500);
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
app.get(`${basePath}/:id`, async (c) => {
|
|
593
|
-
try {
|
|
594
|
-
const {
|
|
595
|
-
user: ctxUser,
|
|
596
|
-
tenantID: ctxTenantID,
|
|
597
|
-
apiKeyContext
|
|
598
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
599
|
-
const access = await checkCollectionAccess(
|
|
600
|
-
collection,
|
|
601
|
-
"read",
|
|
602
|
-
c.req.raw,
|
|
603
|
-
ctxUser,
|
|
604
|
-
ctxTenantID,
|
|
605
|
-
apiKeyContext
|
|
606
|
-
);
|
|
607
|
-
if (!access.allowed) {
|
|
608
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
609
|
-
}
|
|
610
|
-
const id = c.req.param("id");
|
|
611
|
-
const url = new URL(c.req.url);
|
|
612
|
-
const depth = parseInt(url.searchParams.get("depth") || "0");
|
|
613
|
-
const select = url.searchParams.get("select")?.split(",") || void 0;
|
|
614
|
-
const doc = await db.findByID({
|
|
615
|
-
collection: slug,
|
|
616
|
-
id,
|
|
617
|
-
depth,
|
|
618
|
-
tenantID: ctxTenantID,
|
|
619
|
-
select
|
|
620
|
-
});
|
|
621
|
-
if (!doc) {
|
|
622
|
-
return c.json({ error: "Document not found" }, 404);
|
|
623
|
-
}
|
|
624
|
-
return c.json(doc);
|
|
625
|
-
} catch (error) {
|
|
626
|
-
return c.json({ error: error.message }, 500);
|
|
627
|
-
}
|
|
628
|
-
});
|
|
629
|
-
app.post(basePath, async (c) => {
|
|
630
|
-
try {
|
|
631
|
-
const {
|
|
632
|
-
user: ctxUser,
|
|
633
|
-
tenantID: ctxTenantID,
|
|
634
|
-
apiKeyContext
|
|
635
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
636
|
-
const access = await checkCollectionAccess(
|
|
637
|
-
collection,
|
|
638
|
-
"read",
|
|
639
|
-
c.req.raw,
|
|
640
|
-
ctxUser,
|
|
641
|
-
ctxTenantID,
|
|
642
|
-
apiKeyContext,
|
|
643
|
-
enablePublicAccess,
|
|
644
|
-
defaultCollectionAccess
|
|
645
|
-
);
|
|
646
|
-
if (!access.allowed) {
|
|
647
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
648
|
-
}
|
|
649
|
-
const body = await c.req.json();
|
|
650
|
-
const schema = registry.getCreateZodSchema(slug);
|
|
651
|
-
const validated = schema.parse(body);
|
|
652
|
-
if (collection.tenantScoped && ctxTenantID) {
|
|
653
|
-
validated.tenantID = ctxTenantID;
|
|
654
|
-
}
|
|
655
|
-
const doc = await db.create({
|
|
656
|
-
collection: slug,
|
|
657
|
-
data: validated,
|
|
658
|
-
tenantID: ctxTenantID
|
|
659
|
-
});
|
|
660
|
-
if (webhookService) {
|
|
661
|
-
webhookService.trigger(getWebhookEvent(slug, "create"), {
|
|
662
|
-
collection: slug,
|
|
663
|
-
operation: "create",
|
|
664
|
-
data: doc,
|
|
665
|
-
user: ctxUser ? { id: ctxUser.id, email: ctxUser.email, role: ctxUser.role } : void 0,
|
|
666
|
-
tenantId: ctxTenantID
|
|
667
|
-
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
668
|
-
}
|
|
669
|
-
return c.json({ doc, message: "Created successfully" }, 201);
|
|
670
|
-
} catch (error) {
|
|
671
|
-
if (error.name === "ZodError") {
|
|
672
|
-
return c.json(
|
|
673
|
-
{ error: "Validation failed", details: error.errors },
|
|
674
|
-
400
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
return c.json({ error: error.message }, 500);
|
|
678
|
-
}
|
|
679
|
-
});
|
|
680
|
-
app.patch(`${basePath}/:id`, async (c) => {
|
|
681
|
-
try {
|
|
682
|
-
const {
|
|
683
|
-
user: ctxUser,
|
|
684
|
-
tenantID: ctxTenantID,
|
|
685
|
-
apiKeyContext
|
|
686
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
687
|
-
const access = await checkCollectionAccess(
|
|
688
|
-
collection,
|
|
689
|
-
"update",
|
|
690
|
-
c.req.raw,
|
|
691
|
-
ctxUser,
|
|
692
|
-
ctxTenantID,
|
|
693
|
-
apiKeyContext,
|
|
694
|
-
enablePublicAccess,
|
|
695
|
-
defaultCollectionAccess
|
|
696
|
-
);
|
|
697
|
-
if (!access.allowed) {
|
|
698
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
699
|
-
}
|
|
700
|
-
const id = c.req.param("id");
|
|
701
|
-
const body = await c.req.json();
|
|
702
|
-
const schema = registry.getUpdateZodSchema(slug);
|
|
703
|
-
const validated = schema.parse(body);
|
|
704
|
-
const originalDoc = await db.findByID({
|
|
705
|
-
collection: slug,
|
|
706
|
-
id,
|
|
707
|
-
tenantID: ctxTenantID
|
|
708
|
-
});
|
|
709
|
-
const doc = await db.update({
|
|
710
|
-
collection: slug,
|
|
711
|
-
id,
|
|
712
|
-
data: validated,
|
|
713
|
-
tenantID: ctxTenantID
|
|
714
|
-
});
|
|
715
|
-
if (webhookService) {
|
|
716
|
-
webhookService.trigger(getWebhookEvent(slug, "update"), {
|
|
717
|
-
collection: slug,
|
|
718
|
-
operation: "update",
|
|
719
|
-
data: doc,
|
|
720
|
-
previousData: originalDoc,
|
|
721
|
-
user: ctxUser ? { id: ctxUser.id, email: ctxUser.email, role: ctxUser.role } : void 0,
|
|
722
|
-
tenantId: ctxTenantID
|
|
723
|
-
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
724
|
-
}
|
|
725
|
-
return c.json({ doc, message: "Updated successfully" });
|
|
726
|
-
} catch (error) {
|
|
727
|
-
if (error.name === "ZodError") {
|
|
728
|
-
return c.json(
|
|
729
|
-
{ error: "Validation failed", details: error.errors },
|
|
730
|
-
400
|
|
731
|
-
);
|
|
732
|
-
}
|
|
733
|
-
return c.json({ error: error.message }, 500);
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
app.delete(`${basePath}/:id`, async (c) => {
|
|
737
|
-
try {
|
|
738
|
-
const {
|
|
739
|
-
user: ctxUser,
|
|
740
|
-
tenantID: ctxTenantID,
|
|
741
|
-
apiKeyContext
|
|
742
|
-
} = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
743
|
-
const access = await checkCollectionAccess(
|
|
744
|
-
collection,
|
|
745
|
-
"delete",
|
|
746
|
-
c.req.raw,
|
|
747
|
-
ctxUser,
|
|
748
|
-
ctxTenantID,
|
|
749
|
-
apiKeyContext,
|
|
750
|
-
enablePublicAccess,
|
|
751
|
-
defaultCollectionAccess
|
|
752
|
-
);
|
|
753
|
-
if (!access.allowed) {
|
|
754
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
755
|
-
}
|
|
756
|
-
const id = c.req.param("id");
|
|
757
|
-
const originalDoc = await db.findByID({
|
|
758
|
-
collection: slug,
|
|
759
|
-
id,
|
|
760
|
-
tenantID: ctxTenantID
|
|
761
|
-
});
|
|
762
|
-
const doc = await db.delete({
|
|
763
|
-
collection: slug,
|
|
764
|
-
id,
|
|
765
|
-
tenantID: ctxTenantID
|
|
766
|
-
});
|
|
767
|
-
if (webhookService) {
|
|
768
|
-
webhookService.trigger(getWebhookEvent(slug, "delete"), {
|
|
769
|
-
collection: slug,
|
|
770
|
-
operation: "delete",
|
|
771
|
-
data: doc,
|
|
772
|
-
previousData: originalDoc,
|
|
773
|
-
user: ctxUser ? { id: ctxUser.id, email: ctxUser.email, role: ctxUser.role } : void 0,
|
|
774
|
-
tenantId: ctxTenantID
|
|
775
|
-
}).catch((err) => console.error(`[Webhook] Failed to trigger:`, err));
|
|
776
|
-
}
|
|
777
|
-
return c.json({ doc, message: "Deleted successfully" });
|
|
778
|
-
} catch (error) {
|
|
779
|
-
return c.json({ error: error.message }, 500);
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
for (const globalConfig of registry.getGlobals()) {
|
|
784
|
-
const slug = globalConfig.slug;
|
|
785
|
-
const basePath = `/api/globals/${slug}`;
|
|
786
|
-
app.get(basePath, async (c) => {
|
|
787
|
-
try {
|
|
788
|
-
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
789
|
-
const access = await checkGlobalAccess(
|
|
790
|
-
globalConfig,
|
|
791
|
-
"read",
|
|
792
|
-
c.req.raw,
|
|
793
|
-
ctxUser,
|
|
794
|
-
ctxTenantID,
|
|
795
|
-
enablePublicAccess
|
|
796
|
-
);
|
|
797
|
-
if (!access.allowed) {
|
|
798
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
799
|
-
}
|
|
800
|
-
const doc = await db.findOne({
|
|
801
|
-
collection: `_globals_${slug}`,
|
|
802
|
-
where: {},
|
|
803
|
-
tenantID: ctxTenantID
|
|
804
|
-
});
|
|
805
|
-
return c.json(doc || {});
|
|
806
|
-
} catch (error) {
|
|
807
|
-
return c.json({ error: error.message }, 500);
|
|
808
|
-
}
|
|
809
|
-
});
|
|
810
|
-
app.post(basePath, async (c) => {
|
|
811
|
-
try {
|
|
812
|
-
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
813
|
-
const access = await checkGlobalAccess(
|
|
814
|
-
globalConfig,
|
|
815
|
-
"update",
|
|
816
|
-
c.req.raw,
|
|
817
|
-
ctxUser,
|
|
818
|
-
ctxTenantID,
|
|
819
|
-
enablePublicAccess
|
|
820
|
-
);
|
|
821
|
-
if (!access.allowed) {
|
|
822
|
-
return c.json({ error: access.error }, access.status || 403);
|
|
823
|
-
}
|
|
824
|
-
const body = await c.req.json();
|
|
825
|
-
const schema = registry.getZodSchema(slug);
|
|
826
|
-
const validated = schema.parse(body);
|
|
827
|
-
const doc = await db.create({
|
|
828
|
-
collection: `_globals_${slug}`,
|
|
829
|
-
data: { ...validated, id: slug },
|
|
830
|
-
tenantID: ctxTenantID
|
|
831
|
-
});
|
|
832
|
-
return c.json({ doc, message: "Updated successfully" });
|
|
833
|
-
} catch (error) {
|
|
834
|
-
if (error.name === "ZodError") {
|
|
835
|
-
return c.json(
|
|
836
|
-
{ error: "Validation failed", details: error.errors },
|
|
837
|
-
400
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
|
-
return c.json({ error: error.message }, 500);
|
|
841
|
-
}
|
|
842
|
-
});
|
|
843
|
-
if (slug === "email-settings") {
|
|
844
|
-
app.post(`${basePath}/test`, async (c) => {
|
|
845
|
-
try {
|
|
846
|
-
const { user: ctxUser, tenantID: ctxTenantID } = await resolveAuthContext(c.req.raw, authMw, user, tenantID);
|
|
847
|
-
const access = await checkGlobalAccess(
|
|
848
|
-
globalConfig,
|
|
849
|
-
"update",
|
|
850
|
-
c.req.raw,
|
|
851
|
-
ctxUser,
|
|
852
|
-
ctxTenantID,
|
|
853
|
-
enablePublicAccess
|
|
854
|
-
);
|
|
855
|
-
if (!access.allowed) {
|
|
856
|
-
return c.json(
|
|
857
|
-
{ error: access.error },
|
|
858
|
-
access.status || 403
|
|
859
|
-
);
|
|
860
|
-
}
|
|
861
|
-
const body = await c.req.json();
|
|
862
|
-
const transportConfig = {
|
|
863
|
-
provider: body.provider,
|
|
864
|
-
from: body.fromEmail || body.from,
|
|
865
|
-
fromName: body.fromName,
|
|
866
|
-
replyTo: body.replyTo,
|
|
867
|
-
smtp: body.smtp ? {
|
|
868
|
-
host: body.smtp.host,
|
|
869
|
-
port: body.smtp.port,
|
|
870
|
-
secure: body.smtp.secure,
|
|
871
|
-
auth: {
|
|
872
|
-
user: body.smtp.username || body.smtp.user,
|
|
873
|
-
pass: body.smtp.password || body.smtp.pass
|
|
874
|
-
}
|
|
875
|
-
} : void 0,
|
|
876
|
-
resend: body.resend,
|
|
877
|
-
sendgrid: body.sendgrid,
|
|
878
|
-
mailgun: body.mailgun,
|
|
879
|
-
ses: body.ses
|
|
880
|
-
};
|
|
881
|
-
const transport = new EmailTransport(transportConfig);
|
|
882
|
-
const recipient = body.testEmail || body.testEmailSection && body.testEmailSection.testEmail;
|
|
883
|
-
if (!recipient) {
|
|
884
|
-
return c.json({ error: "No test recipient email provided" }, 400);
|
|
885
|
-
}
|
|
886
|
-
await transport.send({
|
|
887
|
-
to: recipient,
|
|
888
|
-
subject: "Kyro CMS - Test Email",
|
|
889
|
-
html: `
|
|
890
|
-
<div style="font-family: sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #e2e8f0; border-radius: 8px;">
|
|
891
|
-
<h1 style="color: #0b1222; margin-bottom: 16px;">Success! \u{1F680}</h1>
|
|
892
|
-
<p style="font-size: 16px; color: #334155; line-height: 1.6;">
|
|
893
|
-
Your email settings in <b>Kyro CMS</b> are working correctly.
|
|
894
|
-
</p>
|
|
895
|
-
<div style="background: #f8fafc; padding: 16px; border-radius: 6px; margin: 24px 0;">
|
|
896
|
-
<p style="margin: 0; font-size: 14px; color: #64748b;">
|
|
897
|
-
<b>Provider:</b> ${body.provider.toUpperCase()}
|
|
898
|
-
</p>
|
|
899
|
-
<p style="margin: 8px 0 0; font-size: 14px; color: #64748b;">
|
|
900
|
-
<b>Sent at:</b> ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
901
|
-
</p>
|
|
902
|
-
</div>
|
|
903
|
-
<p style="font-size: 12px; color: #94a3b8; margin-top: 32px; border-top: 1px solid #f1f5f9; padding-top: 16px;">
|
|
904
|
-
This is a test email sent from the Kyro CMS Admin Panel.
|
|
905
|
-
</p>
|
|
906
|
-
</div>
|
|
907
|
-
`,
|
|
908
|
-
text: `Success! Your email settings in Kyro CMS are working correctly.
|
|
909
|
-
|
|
910
|
-
Provider: ${body.provider}
|
|
911
|
-
Sent at: ${(/* @__PURE__ */ new Date()).toLocaleString()}`
|
|
912
|
-
});
|
|
913
|
-
return c.json({ message: "Test email sent successfully!" });
|
|
914
|
-
} catch (error) {
|
|
915
|
-
console.error("[Email Test] Failed:", error);
|
|
916
|
-
return c.json(
|
|
917
|
-
{ error: error.message || "Failed to send test email" },
|
|
918
|
-
500
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
return app;
|
|
925
|
-
}
|
|
926
|
-
function createRESTAPI(registry, db, options) {
|
|
927
|
-
return createHonoApp({
|
|
928
|
-
registry,
|
|
929
|
-
db,
|
|
930
|
-
authSecret: options?.authSecret,
|
|
931
|
-
user: options?.user,
|
|
932
|
-
req: options?.req,
|
|
933
|
-
tenantID: options?.tenantID,
|
|
934
|
-
cors: options?.cors,
|
|
935
|
-
webhookService: options?.webhookService
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
export { InMemoryRateLimiter, createHonoApp, createRESTAPI, defaultExtractToken, generateToken };
|
|
940
|
-
//# sourceMappingURL=chunk-6MSSF46R.js.map
|
|
941
|
-
//# sourceMappingURL=chunk-6MSSF46R.js.map
|