@kyro-cms/core 0.3.1 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (201) hide show
  1. package/dist/{WebhookService-BznDc2AT.d.ts → WebhookService-BCpW2dyL.d.ts} +1 -1
  2. package/dist/{WebhookService-mZZ75syh.d.cts → WebhookService-DxYSFvNg.d.cts} +1 -1
  3. package/dist/api-handler.cjs +52 -0
  4. package/dist/api-handler.cjs.map +1 -0
  5. package/dist/api-handler.d.cts +9 -0
  6. package/dist/api-handler.d.ts +9 -0
  7. package/dist/api-handler.js +46 -0
  8. package/dist/api-handler.js.map +1 -0
  9. package/dist/{base-Hu6ij8sZ.d.ts → base-DvvNqnM-.d.cts} +16 -5
  10. package/dist/{base-Db9LkB1N.d.cts → base-eVegJ_Pr.d.ts} +16 -5
  11. package/dist/bootstrap-DGJ3N7SO.js +6 -0
  12. package/dist/{bootstrap-LL6O7PWO.js.map → bootstrap-DGJ3N7SO.js.map} +1 -1
  13. package/dist/bootstrap-O5UGUTYU.cjs +31 -0
  14. package/dist/{bootstrap-BMWVB2T6.cjs.map → bootstrap-O5UGUTYU.cjs.map} +1 -1
  15. package/dist/{chunk-QKOFKITP.js → chunk-2HFJUUFZ.js} +3 -11
  16. package/dist/chunk-2HFJUUFZ.js.map +1 -0
  17. package/dist/chunk-2SJATAN4.js +5514 -0
  18. package/dist/chunk-2SJATAN4.js.map +1 -0
  19. package/dist/{chunk-DIC236EW.js → chunk-342BJNBI.js} +167 -24
  20. package/dist/chunk-342BJNBI.js.map +1 -0
  21. package/dist/{chunk-OUGKLCYF.js → chunk-3AJE4SEG.js} +4 -3
  22. package/dist/chunk-3AJE4SEG.js.map +1 -0
  23. package/dist/chunk-6LPNEC6D.js +617 -0
  24. package/dist/chunk-6LPNEC6D.js.map +1 -0
  25. package/dist/{chunk-BXMWDUED.js → chunk-A4USRVTQ.js} +2 -2
  26. package/dist/chunk-A4USRVTQ.js.map +1 -0
  27. package/dist/chunk-ADLJSJSN.cjs +13 -0
  28. package/dist/chunk-ADLJSJSN.cjs.map +1 -0
  29. package/dist/chunk-ATBOUGQP.cjs +513 -0
  30. package/dist/chunk-ATBOUGQP.cjs.map +1 -0
  31. package/dist/{chunk-KB6QF4HO.js → chunk-B76I67F3.js} +246 -141
  32. package/dist/chunk-B76I67F3.js.map +1 -0
  33. package/dist/chunk-BQ2T4WRS.js +140 -0
  34. package/dist/chunk-BQ2T4WRS.js.map +1 -0
  35. package/dist/chunk-CZ3HWX2X.cjs +622 -0
  36. package/dist/chunk-CZ3HWX2X.cjs.map +1 -0
  37. package/dist/{chunk-PNBZZ76A.cjs → chunk-DAIBBBOL.cjs} +246 -140
  38. package/dist/chunk-DAIBBBOL.cjs.map +1 -0
  39. package/dist/{chunk-U74F3YZU.js → chunk-DBUYB32X.js} +15 -3
  40. package/dist/chunk-DBUYB32X.js.map +1 -0
  41. package/dist/chunk-DLHUQO25.cjs +1746 -0
  42. package/dist/chunk-DLHUQO25.cjs.map +1 -0
  43. package/dist/{chunk-GE5DMB44.js → chunk-E3BZLMX6.js} +55 -49
  44. package/dist/chunk-E3BZLMX6.js.map +1 -0
  45. package/dist/{chunk-44BF6ALS.cjs → chunk-H4XCAPA6.cjs} +55 -49
  46. package/dist/chunk-H4XCAPA6.cjs.map +1 -0
  47. package/dist/{chunk-VIONYQ2K.cjs → chunk-IBG6V56E.cjs} +16 -32
  48. package/dist/chunk-IBG6V56E.cjs.map +1 -0
  49. package/dist/{chunk-LIJVWQKU.cjs → chunk-IX3ABYKZ.cjs} +43 -31
  50. package/dist/chunk-IX3ABYKZ.cjs.map +1 -0
  51. package/dist/chunk-JYGIFBBS.cjs +146 -0
  52. package/dist/chunk-JYGIFBBS.cjs.map +1 -0
  53. package/dist/{chunk-42JPONZU.cjs → chunk-K7JPTH3G.cjs} +17 -16
  54. package/dist/chunk-K7JPTH3G.cjs.map +1 -0
  55. package/dist/{chunk-RLTG4YZM.cjs → chunk-KOCTZKPV.cjs} +2 -2
  56. package/dist/chunk-KOCTZKPV.cjs.map +1 -0
  57. package/dist/chunk-MMYAIYHJ.cjs +5538 -0
  58. package/dist/chunk-MMYAIYHJ.cjs.map +1 -0
  59. package/dist/{chunk-EWP5AT6A.cjs → chunk-N4H37VN4.cjs} +2 -11
  60. package/dist/chunk-N4H37VN4.cjs.map +1 -0
  61. package/dist/chunk-P2YW545G.js +11 -0
  62. package/dist/chunk-P2YW545G.js.map +1 -0
  63. package/dist/chunk-Q23JB3KL.js +488 -0
  64. package/dist/chunk-Q23JB3KL.js.map +1 -0
  65. package/dist/{chunk-E5X75WNB.js → chunk-QXIQWPAP.js} +14 -30
  66. package/dist/chunk-QXIQWPAP.js.map +1 -0
  67. package/dist/chunk-R3XIBBAW.cjs +34 -0
  68. package/dist/chunk-R3XIBBAW.cjs.map +1 -0
  69. package/dist/{chunk-KWGNR4HM.js → chunk-REK7AYOC.js} +82 -9
  70. package/dist/chunk-REK7AYOC.js.map +1 -0
  71. package/dist/chunk-RGIQKTZ7.js +68 -0
  72. package/dist/chunk-RGIQKTZ7.js.map +1 -0
  73. package/dist/chunk-RYDGMBIG.js +1737 -0
  74. package/dist/chunk-RYDGMBIG.js.map +1 -0
  75. package/dist/chunk-SDMNUYVU.js +30 -0
  76. package/dist/chunk-SDMNUYVU.js.map +1 -0
  77. package/dist/chunk-VEI5KQVC.cjs +1246 -0
  78. package/dist/chunk-VEI5KQVC.cjs.map +1 -0
  79. package/dist/{chunk-FTSSDDZQ.cjs → chunk-VJT6P4N6.cjs} +82 -9
  80. package/dist/chunk-VJT6P4N6.cjs.map +1 -0
  81. package/dist/{chunk-HT6VE4NW.cjs → chunk-W3KPQX7V.cjs} +168 -25
  82. package/dist/chunk-W3KPQX7V.cjs.map +1 -0
  83. package/dist/{chunk-LTRCYJAG.js → chunk-WOWUL7ZY.js} +3 -2
  84. package/dist/chunk-WOWUL7ZY.js.map +1 -0
  85. package/dist/{chunk-7YITG2US.cjs → chunk-WQBRWOQT.cjs} +3 -2
  86. package/dist/chunk-WQBRWOQT.cjs.map +1 -0
  87. package/dist/chunk-X3CU27OO.cjs +78 -0
  88. package/dist/chunk-X3CU27OO.cjs.map +1 -0
  89. package/dist/chunk-XIXGJGQW.js +1228 -0
  90. package/dist/chunk-XIXGJGQW.js.map +1 -0
  91. package/dist/cli/index.cjs +2 -2
  92. package/dist/cli/index.js +2 -2
  93. package/dist/client.cjs +23 -13
  94. package/dist/client.d.cts +4 -2
  95. package/dist/client.d.ts +4 -2
  96. package/dist/client.js +3 -1
  97. package/dist/drizzle/index.cjs +20 -19
  98. package/dist/drizzle/index.d.cts +28 -7
  99. package/dist/drizzle/index.d.ts +28 -7
  100. package/dist/drizzle/index.js +5 -4
  101. package/dist/fields/index.cjs +105 -0
  102. package/dist/fields/index.cjs.map +1 -0
  103. package/dist/fields/index.d.cts +27 -0
  104. package/dist/fields/index.d.ts +27 -0
  105. package/dist/fields/index.js +4 -0
  106. package/dist/fields/index.js.map +1 -0
  107. package/dist/graphql/index.cjs +4 -3
  108. package/dist/graphql/index.d.cts +3 -2
  109. package/dist/graphql/index.d.ts +3 -2
  110. package/dist/graphql/index.js +2 -1
  111. package/dist/{index-Ci6r4xnN.d.ts → index-CLp-DRKA.d.ts} +2 -1
  112. package/dist/{index-11MDNKce.d.cts → index-DfO7G4kN.d.cts} +2 -1
  113. package/dist/index.cjs +2621 -6672
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.d.cts +136 -47
  116. package/dist/index.d.ts +136 -47
  117. package/dist/index.js +2333 -6546
  118. package/dist/index.js.map +1 -1
  119. package/dist/integration.cjs +68 -0
  120. package/dist/integration.cjs.map +1 -0
  121. package/dist/integration.d.cts +27 -0
  122. package/dist/integration.d.ts +27 -0
  123. package/dist/integration.js +61 -0
  124. package/dist/integration.js.map +1 -0
  125. package/dist/mongodb/index.cjs +4 -4
  126. package/dist/mongodb/index.d.cts +20 -6
  127. package/dist/mongodb/index.d.ts +20 -6
  128. package/dist/mongodb/index.js +2 -2
  129. package/dist/postgres-auth-adapter-7F3ECO7I.js +5 -0
  130. package/dist/{postgres-auth-adapter-OTRWSTT5.js.map → postgres-auth-adapter-7F3ECO7I.js.map} +1 -1
  131. package/dist/postgres-auth-adapter-Z463NYJZ.cjs +14 -0
  132. package/dist/{postgres-auth-adapter-EVRPO7BQ.cjs.map → postgres-auth-adapter-Z463NYJZ.cjs.map} +1 -1
  133. package/dist/redis-adapter-LPUWLE4Y.cjs +13 -0
  134. package/dist/{redis-adapter-E7PMN5HW.cjs.map → redis-adapter-LPUWLE4Y.cjs.map} +1 -1
  135. package/dist/redis-adapter-THYDCGQR.js +4 -0
  136. package/dist/{redis-adapter-HOO67RBQ.js.map → redis-adapter-THYDCGQR.js.map} +1 -1
  137. package/dist/rest/index.cjs +8 -5
  138. package/dist/rest/index.d.cts +6 -3
  139. package/dist/rest/index.d.ts +6 -3
  140. package/dist/rest/index.js +6 -3
  141. package/dist/{schema-CNB2DDTX.js → schema-6Q4W6AE6.js} +3 -3
  142. package/dist/{schema-CNB2DDTX.js.map → schema-6Q4W6AE6.js.map} +1 -1
  143. package/dist/{schema-Y777CQQS.cjs → schema-TIYTCIKX.cjs} +14 -14
  144. package/dist/{schema-Y777CQQS.cjs.map → schema-TIYTCIKX.cjs.map} +1 -1
  145. package/dist/templates/index.cjs +27 -23
  146. package/dist/templates/index.d.cts +8 -2
  147. package/dist/templates/index.d.ts +8 -2
  148. package/dist/templates/index.js +1 -1
  149. package/dist/trpc/index.cjs +12 -11
  150. package/dist/trpc/index.d.cts +3 -2
  151. package/dist/trpc/index.d.ts +3 -2
  152. package/dist/trpc/index.js +3 -2
  153. package/dist/{types-kGfsGdos.d.cts → types-Bs1up4yP.d.ts} +76 -244
  154. package/dist/{types-1u353OHN.d.ts → types-Da83JLDk.d.cts} +6 -2
  155. package/dist/{types-1u353OHN.d.cts → types-Da83JLDk.d.ts} +6 -2
  156. package/dist/{types-kGfsGdos.d.ts → types-J3R9nVsZ.d.cts} +76 -244
  157. package/dist/types-VtjUxIMp.d.cts +246 -0
  158. package/dist/types-VtjUxIMp.d.ts +246 -0
  159. package/package.json +16 -9
  160. package/dist/bootstrap-BMWVB2T6.cjs +0 -31
  161. package/dist/bootstrap-LL6O7PWO.js +0 -6
  162. package/dist/chunk-42JPONZU.cjs.map +0 -1
  163. package/dist/chunk-44BF6ALS.cjs.map +0 -1
  164. package/dist/chunk-4M5PHMUE.cjs +0 -947
  165. package/dist/chunk-4M5PHMUE.cjs.map +0 -1
  166. package/dist/chunk-6MSSF46R.js +0 -941
  167. package/dist/chunk-6MSSF46R.js.map +0 -1
  168. package/dist/chunk-7YITG2US.cjs.map +0 -1
  169. package/dist/chunk-BTOE3VUK.js +0 -330
  170. package/dist/chunk-BTOE3VUK.js.map +0 -1
  171. package/dist/chunk-BXMWDUED.js.map +0 -1
  172. package/dist/chunk-DIC236EW.js.map +0 -1
  173. package/dist/chunk-E5X75WNB.js.map +0 -1
  174. package/dist/chunk-E63IF3MD.cjs +0 -951
  175. package/dist/chunk-E63IF3MD.cjs.map +0 -1
  176. package/dist/chunk-EWP5AT6A.cjs.map +0 -1
  177. package/dist/chunk-FTSSDDZQ.cjs.map +0 -1
  178. package/dist/chunk-GE5DMB44.js.map +0 -1
  179. package/dist/chunk-GVFB5C6O.cjs +0 -345
  180. package/dist/chunk-GVFB5C6O.cjs.map +0 -1
  181. package/dist/chunk-HT6VE4NW.cjs.map +0 -1
  182. package/dist/chunk-HVSQDZZJ.cjs +0 -765
  183. package/dist/chunk-HVSQDZZJ.cjs.map +0 -1
  184. package/dist/chunk-HYC4GNHX.js +0 -758
  185. package/dist/chunk-HYC4GNHX.js.map +0 -1
  186. package/dist/chunk-KB6QF4HO.js.map +0 -1
  187. package/dist/chunk-KWGNR4HM.js.map +0 -1
  188. package/dist/chunk-LIJVWQKU.cjs.map +0 -1
  189. package/dist/chunk-LTRCYJAG.js.map +0 -1
  190. package/dist/chunk-OUGKLCYF.js.map +0 -1
  191. package/dist/chunk-PNBZZ76A.cjs.map +0 -1
  192. package/dist/chunk-QKOFKITP.js.map +0 -1
  193. package/dist/chunk-RLTG4YZM.cjs.map +0 -1
  194. package/dist/chunk-RRYXQMZG.js +0 -935
  195. package/dist/chunk-RRYXQMZG.js.map +0 -1
  196. package/dist/chunk-U74F3YZU.js.map +0 -1
  197. package/dist/chunk-VIONYQ2K.cjs.map +0 -1
  198. package/dist/postgres-auth-adapter-EVRPO7BQ.cjs +0 -14
  199. package/dist/postgres-auth-adapter-OTRWSTT5.js +0 -5
  200. package/dist/redis-adapter-E7PMN5HW.cjs +0 -13
  201. package/dist/redis-adapter-HOO67RBQ.js +0 -4
@@ -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