@kyro-cms/admin 0.3.2 → 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 (242) hide show
  1. package/dist/EditorClient-XEUOVAAC.js +466 -0
  2. package/dist/EditorClient-XEUOVAAC.js.map +1 -0
  3. package/dist/EditorClient-YLCGVDXY.cjs +468 -0
  4. package/dist/EditorClient-YLCGVDXY.cjs.map +1 -0
  5. package/dist/chunk-7KPIUCGT.js +384 -0
  6. package/dist/chunk-7KPIUCGT.js.map +1 -0
  7. package/dist/chunk-GOACG6R7.cjs +473 -0
  8. package/dist/chunk-GOACG6R7.cjs.map +1 -0
  9. package/dist/index.cjs +14861 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.css +1661 -0
  12. package/dist/index.css.map +1 -0
  13. package/dist/index.d.ts +563 -0
  14. package/dist/index.js +14784 -0
  15. package/dist/index.js.map +1 -0
  16. package/package.json +19 -19
  17. package/src/components/ActionBar.tsx +7 -43
  18. package/src/components/Admin.tsx +138 -277
  19. package/src/components/ApiKeysManager.tsx +428 -419
  20. package/src/components/AuditLogsPage.tsx +35 -39
  21. package/src/components/AuthBridge.tsx +51 -0
  22. package/src/components/AutoForm.tsx +495 -1230
  23. package/src/components/BrandingHub.tsx +18 -19
  24. package/src/components/BulkActionsBar.tsx +1 -1
  25. package/src/components/CreateView.tsx +22 -36
  26. package/src/components/Dashboard.tsx +60 -84
  27. package/src/components/DetailView.tsx +113 -91
  28. package/src/components/DeveloperCenter.tsx +200 -198
  29. package/src/components/FieldRenderer.tsx +206 -0
  30. package/src/components/GraphQLPlayground.tsx +340 -480
  31. package/src/components/ListView.tsx +828 -254
  32. package/src/components/LoginPage.tsx +3 -4
  33. package/src/components/MarketplaceManager.tsx +254 -0
  34. package/src/components/MediaGallery.tsx +856 -1192
  35. package/src/components/PluginsManager.tsx +277 -0
  36. package/src/components/RestPlayground.tsx +398 -560
  37. package/src/components/SessionsManager.tsx +211 -0
  38. package/src/components/Sidebar.astro +179 -151
  39. package/src/components/ThemeProvider.tsx +7 -161
  40. package/src/components/UserManagement.tsx +162 -146
  41. package/src/components/UserMenu.tsx +110 -0
  42. package/src/components/WebhookManager.tsx +305 -367
  43. package/src/components/blocks/AccordionBlock.tsx +4 -4
  44. package/src/components/blocks/ArrayBlock.tsx +3 -3
  45. package/src/components/blocks/BlockEditModal.tsx +8 -8
  46. package/src/components/blocks/BlockWrapper.tsx +61 -0
  47. package/src/components/blocks/ButtonBlock.tsx +4 -4
  48. package/src/components/blocks/ChildBlocksTree.tsx +23 -25
  49. package/src/components/blocks/CodeBlock.tsx +15 -15
  50. package/src/components/blocks/ColumnsBlock.tsx +6 -44
  51. package/src/components/blocks/DividerBlock.tsx +3 -3
  52. package/src/components/blocks/FileBlock.tsx +4 -4
  53. package/src/components/blocks/HeadingBlock.tsx +6 -38
  54. package/src/components/blocks/HeroBlock.tsx +4 -4
  55. package/src/components/blocks/ImageBlock.tsx +4 -4
  56. package/src/components/blocks/LinkBlock.tsx +4 -4
  57. package/src/components/blocks/ListBlock.tsx +3 -3
  58. package/src/components/blocks/ParagraphBlock.tsx +12 -42
  59. package/src/components/blocks/RelationshipBlock.tsx +4 -4
  60. package/src/components/blocks/RichTextBlock.tsx +4 -4
  61. package/src/components/blocks/VStackBlock.tsx +5 -37
  62. package/src/components/blocks/VideoBlock.tsx +4 -4
  63. package/src/components/blocks/types.ts +11 -0
  64. package/src/components/fields/AccordionField.tsx +1 -1
  65. package/src/components/fields/ArrayField.tsx +2 -2
  66. package/src/components/fields/ArrayLayout.tsx +93 -0
  67. package/src/components/fields/BlocksField.tsx +122 -111
  68. package/src/components/fields/ButtonField.tsx +1 -1
  69. package/src/components/fields/CheckboxField.tsx +14 -15
  70. package/src/components/fields/ChildrenField.tsx +2 -2
  71. package/src/components/fields/CodeField.tsx +3 -3
  72. package/src/components/fields/ColumnsField.tsx +2 -2
  73. package/src/components/fields/DateField.tsx +13 -26
  74. package/src/components/fields/EditorClient.tsx +26 -28
  75. package/src/components/fields/FieldLayout.tsx +52 -0
  76. package/src/components/fields/GroupLayout.tsx +35 -0
  77. package/src/components/fields/JSONField.tsx +7 -7
  78. package/src/components/fields/LinkField.tsx +1 -1
  79. package/src/components/fields/MarkdownField.tsx +1 -1
  80. package/src/components/fields/NumberField.tsx +13 -26
  81. package/src/components/fields/PortableTextField.tsx +4 -4
  82. package/src/components/fields/PortableTextRenderer.tsx +1 -1
  83. package/src/components/fields/RelationshipBlockField.tsx +31 -23
  84. package/src/components/fields/RelationshipField.tsx +14 -14
  85. package/src/components/fields/SelectField.tsx +17 -26
  86. package/src/components/fields/TabsLayout.tsx +69 -0
  87. package/src/components/fields/TextField.tsx +85 -38
  88. package/src/components/fields/UploadField.tsx +71 -41
  89. package/src/components/fields/VideoField.tsx +1 -1
  90. package/src/components/fields/extensions/blockComponents.tsx +2 -2
  91. package/src/components/fields/extensions/blocksStore.ts +207 -193
  92. package/src/components/fields/types.ts +22 -0
  93. package/src/components/layout/Layout.tsx +1 -1
  94. package/src/components/ui/ActionMenu.tsx +63 -0
  95. package/src/components/ui/Badge.tsx +59 -5
  96. package/src/components/ui/BlockDrawer.tsx +4 -5
  97. package/src/components/ui/CommandPalette.tsx +58 -36
  98. package/src/components/ui/CommandPaletteWrapper.tsx +18 -17
  99. package/src/components/ui/Dropdown.tsx +18 -16
  100. package/src/components/ui/EmptyState.tsx +25 -0
  101. package/src/components/ui/GlobalModal.tsx +49 -0
  102. package/src/components/ui/IconButton.tsx +44 -0
  103. package/src/components/ui/Modal.tsx +19 -20
  104. package/src/components/ui/PageHeader.tsx +158 -0
  105. package/src/components/ui/Pagination.tsx +61 -0
  106. package/src/components/ui/PromptModal.tsx +1 -1
  107. package/src/components/ui/SearchInput.tsx +57 -0
  108. package/src/components/ui/SeoPreview.tsx +31 -0
  109. package/src/components/ui/SessionModal.tsx +0 -0
  110. package/src/components/ui/SlidePanel.tsx +2 -0
  111. package/src/components/ui/Toast.tsx +65 -122
  112. package/src/components/ui/Toaster.tsx +18 -0
  113. package/src/components/ui/icons.tsx +112 -0
  114. package/src/components/users/UserDetail.tsx +290 -0
  115. package/src/components/users/UserForm.tsx +242 -0
  116. package/src/components/users/UsersList.tsx +338 -0
  117. package/src/env.d.ts +13 -13
  118. package/src/fields/index.ts +2 -1
  119. package/src/global.d.ts +7 -0
  120. package/src/hooks/data.ts +2 -9
  121. package/src/hooks/useAsyncData.ts +36 -0
  122. package/src/hooks/useAutoFormState.ts +527 -0
  123. package/src/hooks/useSelection.ts +49 -0
  124. package/src/hooks/useSession.ts +0 -0
  125. package/src/index.ts +11 -1
  126. package/src/integration.ts +86 -11
  127. package/src/kyro-cms.d.ts +209 -0
  128. package/src/layouts/AdminLayout.astro +128 -11
  129. package/src/layouts/AuthLayout.astro +21 -5
  130. package/src/lib/api.ts +175 -55
  131. package/src/lib/autoform-store.ts +435 -0
  132. package/src/lib/config.ts +82 -34
  133. package/src/lib/createRegistry.ts +29 -0
  134. package/src/lib/default-kyro-config.ts +4 -0
  135. package/src/lib/globals.ts +50 -0
  136. package/src/lib/media-utils.ts +18 -0
  137. package/src/lib/object-utils.ts +77 -0
  138. package/src/lib/paths.ts +61 -0
  139. package/src/lib/stores/index.ts +370 -0
  140. package/src/lib/types.ts +43 -0
  141. package/src/lib/useResourceManager.ts +105 -0
  142. package/src/pages/403.astro +67 -0
  143. package/src/pages/[collection]/[id].astro +14 -180
  144. package/src/pages/[collection]/index.astro +11 -6
  145. package/src/pages/api-explorer.astro +173 -0
  146. package/src/pages/audit/index.astro +2 -0
  147. package/src/pages/auth/login.astro +122 -0
  148. package/src/pages/auth/register.astro +167 -0
  149. package/src/pages/graphql-explorer.astro +59 -0
  150. package/src/pages/{admin/graphql.astro → graphql.astro} +51 -17
  151. package/src/pages/index.astro +577 -0
  152. package/src/pages/index_ALT.astro +3 -0
  153. package/src/pages/keys.astro +11 -0
  154. package/src/pages/marketplace.astro +11 -0
  155. package/src/pages/media.astro +3 -0
  156. package/src/pages/plugins.astro +8 -0
  157. package/src/pages/preview/[collection]/[id].astro +188 -123
  158. package/src/pages/rest-playground.astro +62 -0
  159. package/src/pages/roles/index.astro +183 -76
  160. package/src/pages/sessions.astro +8 -0
  161. package/src/pages/settings/[slug].astro +92 -114
  162. package/src/pages/settings/index.astro +5 -3
  163. package/src/pages/users/[id].astro +25 -154
  164. package/src/pages/users/index.astro +19 -130
  165. package/src/pages/users/new.astro +9 -86
  166. package/src/pages/webhooks.astro +11 -0
  167. package/src/routes.ts +80 -0
  168. package/src/styles/main.css +119 -79
  169. package/src/theme/tokens.ts +1 -0
  170. package/src/vite-env.d.ts +14 -0
  171. package/src/collections/auth/index.ts +0 -155
  172. package/src/collections/portfolio/index.ts +0 -343
  173. package/src/components/ApiExplorer.tsx +0 -325
  174. package/src/components/EnhancedListView.tsx +0 -889
  175. package/src/components/GraphQLExplorer.tsx +0 -675
  176. package/src/components/Icons.tsx +0 -23
  177. package/src/components/StatusBadge.tsx +0 -76
  178. package/src/lib/MediaService.ts +0 -541
  179. package/src/lib/auth/sqlite-adapter.ts +0 -319
  180. package/src/lib/dataStore.ts +0 -226
  181. package/src/lib/db/adapter.ts +0 -54
  182. package/src/lib/db/drizzle-mysql-adapter.ts +0 -194
  183. package/src/lib/db/drizzle-mysql-auth-adapter.ts +0 -327
  184. package/src/lib/db/drizzle-postgres-adapter.ts +0 -202
  185. package/src/lib/db/drizzle-postgres-auth-adapter.ts +0 -304
  186. package/src/lib/db/drizzle-sqlite-adapter.ts +0 -227
  187. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +0 -548
  188. package/src/lib/db/index.ts +0 -449
  189. package/src/lib/db/mongodb-adapter.ts +0 -207
  190. package/src/lib/db/mongodb-auth-adapter.ts +0 -305
  191. package/src/lib/db/schema/mysql-auth.ts +0 -113
  192. package/src/lib/db/schema/mysql-content.ts +0 -20
  193. package/src/lib/db/schema/postgres-auth.ts +0 -116
  194. package/src/lib/db/schema/postgres-content.ts +0 -35
  195. package/src/lib/db/schema/postgres-media.ts +0 -52
  196. package/src/lib/db/schema/postgres-settings.ts +0 -11
  197. package/src/lib/db/schema/sqlite-auth.ts +0 -112
  198. package/src/lib/db/schema/sqlite-content.ts +0 -20
  199. package/src/lib/db/version-adapter.ts +0 -248
  200. package/src/lib/graphql/index.ts +0 -1
  201. package/src/lib/graphql/schema.ts +0 -443
  202. package/src/lib/rate-limit.ts +0 -267
  203. package/src/lib/storage.ts +0 -374
  204. package/src/lib/store.ts +0 -85
  205. package/src/middleware.ts +0 -177
  206. package/src/pages/admin/api-explorer.astro +0 -98
  207. package/src/pages/admin/graphql-explorer.astro +0 -40
  208. package/src/pages/admin/index.astro +0 -286
  209. package/src/pages/admin/keys.astro +0 -8
  210. package/src/pages/admin/rest-playground.astro +0 -44
  211. package/src/pages/admin/webhooks.astro +0 -8
  212. package/src/pages/api/[collection]/[id]/publish.ts +0 -52
  213. package/src/pages/api/[collection]/[id]/unpublish.ts +0 -42
  214. package/src/pages/api/[collection]/[id]/versions.ts +0 -66
  215. package/src/pages/api/[collection]/[id].ts +0 -213
  216. package/src/pages/api/[collection]/index.ts +0 -209
  217. package/src/pages/api/auth/[id].ts +0 -121
  218. package/src/pages/api/auth/audit-logs.ts +0 -57
  219. package/src/pages/api/auth/login.ts +0 -211
  220. package/src/pages/api/auth/logout.ts +0 -66
  221. package/src/pages/api/auth/me.ts +0 -36
  222. package/src/pages/api/auth/refresh.ts +0 -119
  223. package/src/pages/api/auth/register.ts +0 -188
  224. package/src/pages/api/auth/users.ts +0 -97
  225. package/src/pages/api/collections.ts +0 -59
  226. package/src/pages/api/globals/[slug].ts +0 -42
  227. package/src/pages/api/graphql.ts +0 -90
  228. package/src/pages/api/health.ts +0 -426
  229. package/src/pages/api/keys/[id].ts +0 -26
  230. package/src/pages/api/keys/index.ts +0 -75
  231. package/src/pages/api/media/[id].ts +0 -309
  232. package/src/pages/api/media/folders.ts +0 -609
  233. package/src/pages/api/media/index.ts +0 -146
  234. package/src/pages/api/media/resize.ts +0 -267
  235. package/src/pages/api/search.ts +0 -82
  236. package/src/pages/api/slug-availability.ts +0 -70
  237. package/src/pages/api/storage-config.ts +0 -20
  238. package/src/pages/api/storage-status.ts +0 -206
  239. package/src/pages/api/upload.ts +0 -334
  240. package/src/pages/api/webhooks/index.ts +0 -71
  241. package/src/pages/login.astro +0 -82
  242. package/src/pages/register.astro +0 -102
@@ -1,443 +0,0 @@
1
- import { buildSchema, graphql } from "graphql";
2
- import { dataStore } from "../dataStore";
3
- import { collections, globals } from "../config";
4
- import type { CollectionConfig, GlobalConfig } from "@kyro-cms/core";
5
- import jwt from "jsonwebtoken";
6
-
7
- const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production";
8
-
9
- function generateSchemaTypes() {
10
- const queryLines: string[] = [
11
- `_schema: SchemaInfo!`,
12
- `ping: String!`,
13
- `collections: [CollectionInfo!]!`,
14
- `globals: [GlobalInfo!]!`,
15
- ];
16
-
17
- for (const [slug, collection] of Object.entries(collections)) {
18
- if (slug === "roles") continue;
19
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
20
- queryLines.push(`${safeSlug}(page: Int, limit: Int): CollectionResult!`);
21
- queryLines.push(`${safeSlug}ById(id: ID!): JSON`);
22
- queryLines.push(`${safeSlug}BySlug(slug: String!): JSON`);
23
- }
24
-
25
- for (const slug of Object.keys(globals)) {
26
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
27
- queryLines.push(`${safeSlug}: GlobalResult!`);
28
- }
29
-
30
- const mutationLines: string[] = [];
31
- mutationLines.push(`login(email: String!, password: String!): AuthPayload!`);
32
- mutationLines.push(
33
- `register(email: String!, password: String!): AuthPayload!`,
34
- );
35
-
36
- for (const slug of Object.keys(collections)) {
37
- if (slug === "roles" || slug === "users") continue;
38
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
39
- const name = capitalize(safeSlug);
40
- mutationLines.push(`create${name}(input: JSON!): JSON!`);
41
- mutationLines.push(`update${name}(id: ID!, input: JSON!): JSON!`);
42
- mutationLines.push(`delete${name}(id: ID!): Boolean!`);
43
- }
44
-
45
- for (const slug of Object.keys(globals)) {
46
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
47
- const name = capitalize(safeSlug);
48
- mutationLines.push(`update${name}(input: JSON!): GlobalResult!`);
49
- }
50
-
51
- return `
52
- scalar JSON
53
-
54
- type Query {
55
- ${queryLines.join("\n ")}
56
- }
57
-
58
- type Mutation {
59
- ${mutationLines.join("\n ")}
60
- }
61
-
62
- type Meta {
63
- page: Int!
64
- limit: Int!
65
- totalDocs: Int!
66
- totalPages: Int!
67
- }
68
-
69
- type CollectionResult {
70
- docs: [JSON!]!
71
- totalDocs: Int!
72
- totalPages: Int!
73
- page: Int!
74
- }
75
-
76
- type GlobalResult {
77
- data: JSON
78
- success: Boolean!
79
- }
80
-
81
- type AuthPayload {
82
- token: String
83
- user: JSON
84
- error: String
85
- }
86
-
87
- type SchemaInfo {
88
- types: [TypeInfo!]!
89
- }
90
-
91
- type TypeInfo {
92
- name: String!
93
- fields: [FieldInfo!]!
94
- }
95
-
96
- type FieldInfo {
97
- name: String!
98
- type: String!
99
- required: Boolean!
100
- }
101
-
102
- type CollectionInfo {
103
- slug: String!
104
- name: String
105
- fields: [String!]!
106
- }
107
-
108
- type GlobalInfo {
109
- slug: String!
110
- name: String
111
- }
112
- `;
113
- }
114
-
115
- const schemaString = generateSchemaTypes();
116
- const typeDefs = buildSchema(schemaString);
117
-
118
- function capitalize(str: string): string {
119
- return str.charAt(0).toUpperCase() + str.slice(1);
120
- }
121
-
122
- interface Context {
123
- user?: {
124
- id: string;
125
- email: string;
126
- role: string;
127
- };
128
- apiKeyId?: string;
129
- permissions?: string[];
130
- }
131
-
132
- const rootValue = {
133
- _schema: () => {
134
- const types = Object.entries(collections).map(([slug, collection]) => ({
135
- name: capitalize(slug),
136
- fields: collection.fields.map((f: any) => ({
137
- name: f.name,
138
- type: getGraphQLType(f.type),
139
- required: f.required || false,
140
- })),
141
- }));
142
- return { types };
143
- },
144
-
145
- ping: () => "pong",
146
-
147
- collections: () => {
148
- return Object.entries(collections).map(([slug, collection]) => ({
149
- slug,
150
- name: (collection as CollectionConfig).label || slug,
151
- fields: (collection as CollectionConfig).fields.map((f: any) => f.name),
152
- }));
153
- },
154
-
155
- globals: () => {
156
- return Object.entries(globals).map(([slug, global]) => ({
157
- slug,
158
- name: (global as GlobalConfig).label || slug,
159
- }));
160
- },
161
-
162
- ...generateQueryResolvers(),
163
-
164
- ...generateMutationResolvers(),
165
- };
166
-
167
- function getGraphQLType(fieldType: string): string {
168
- const typeMap: Record<string, string> = {
169
- text: "String",
170
- richtext: "String",
171
- email: "String",
172
- password: "String",
173
- url: "String",
174
- number: "Float",
175
- integer: "Int",
176
- boolean: "Boolean",
177
- select: "String",
178
- multiselect: "[String!]!",
179
- date: "String",
180
- datetime: "String",
181
- media: "String",
182
- reference: "String",
183
- array: "[String!]!",
184
- json: "JSON",
185
- code: "String",
186
- markdown: "String",
187
- slug: "String",
188
- };
189
- return typeMap[fieldType] || "String";
190
- }
191
-
192
- function generateQueryResolvers() {
193
- const resolvers: Record<string, any> = {};
194
-
195
- for (const slug of Object.keys(collections)) {
196
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
197
- resolvers[safeSlug] = async ({
198
- page,
199
- limit,
200
- }: {
201
- page?: number;
202
- limit?: number;
203
- }) => {
204
- return await dataStore.find(slug, {
205
- page: page || 1,
206
- limit: limit || 25,
207
- });
208
- };
209
-
210
- resolvers[`${safeSlug}ById`] = async ({ id }: { id: string }) => {
211
- return await dataStore.findById(slug, id);
212
- };
213
-
214
- resolvers[`${safeSlug}BySlug`] = async ({
215
- slug: itemSlug,
216
- }: {
217
- slug: string;
218
- }) => {
219
- const docs = await dataStore.find(slug, { limit: 100 });
220
- return docs.docs.find((d: any) => d.slug === itemSlug) || null;
221
- };
222
- }
223
-
224
- for (const slug of Object.keys(globals)) {
225
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
226
- resolvers[safeSlug] = async () => {
227
- return { data: await dataStore.findGlobal(slug), success: true };
228
- };
229
- }
230
-
231
- return resolvers;
232
- }
233
-
234
- function generateMutationResolvers() {
235
- return {
236
- login: async ({ email, password }: { email: string; password: string }) => {
237
- try {
238
- const { SQLiteAuthAdapter } = await import("../auth/sqlite-adapter");
239
- const adapter = new SQLiteAuthAdapter();
240
-
241
- const user = await adapter.findUserByEmail(email);
242
- if (!user) {
243
- return { error: "Invalid credentials" };
244
- }
245
-
246
- const valid = await adapter.verifyPassword(
247
- password,
248
- user.passwordHash || "",
249
- );
250
- if (!valid) {
251
- return { error: "Invalid credentials" };
252
- }
253
-
254
- const { passwordHash, ...safeUser } = user;
255
- const token = jwt.sign(
256
- {
257
- sub: safeUser.id,
258
- email: safeUser.email,
259
- role: safeUser.role,
260
- tenantId: safeUser.tenantId,
261
- },
262
- JWT_SECRET,
263
- { expiresIn: "7d" },
264
- );
265
-
266
- return { token, user: safeUser };
267
- } catch {
268
- return { error: "Authentication failed" };
269
- }
270
- },
271
-
272
- register: async ({
273
- email,
274
- password,
275
- }: {
276
- email: string;
277
- password: string;
278
- }) => {
279
- try {
280
- const { SQLiteAuthAdapter } = await import("../auth/sqlite-adapter");
281
- const adapter = new SQLiteAuthAdapter();
282
-
283
- const existing = await adapter.findUserByEmail(email);
284
- if (existing) {
285
- return { error: "Email already exists" };
286
- }
287
-
288
- const passwordHash = await adapter.hashPassword(password);
289
- const user = await adapter.createUser({
290
- email,
291
- passwordHash,
292
- role: "customer",
293
- });
294
-
295
- const { passwordHash: _, ...safeUser } = user;
296
- const token = jwt.sign(
297
- {
298
- sub: safeUser.id,
299
- email: safeUser.email,
300
- role: safeUser.role,
301
- tenantId: safeUser.tenantId,
302
- },
303
- JWT_SECRET,
304
- { expiresIn: "7d" },
305
- );
306
-
307
- return { token, user: safeUser };
308
- } catch {
309
- return { error: "Registration failed" };
310
- }
311
- },
312
- };
313
- }
314
-
315
- function generateCollectionMutations() {
316
- const resolvers: Record<string, any> = {};
317
-
318
- for (const slug of Object.keys(collections)) {
319
- if (slug === "roles" || slug === "users") continue;
320
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
321
- const name = capitalize(safeSlug);
322
-
323
- resolvers[`create${name}`] = async ({ input }: { input: any }) => {
324
- return await dataStore.create(slug, input);
325
- };
326
-
327
- resolvers[`update${name}`] = async ({
328
- id,
329
- input,
330
- }: {
331
- id: string;
332
- input: any;
333
- }) => {
334
- return await dataStore.update(slug, id, input);
335
- };
336
-
337
- resolvers[`delete${name}`] = async ({ id }: { id: string }) => {
338
- return await dataStore.delete(slug, id);
339
- };
340
- }
341
-
342
- for (const slug of Object.keys(globals)) {
343
- const safeSlug = slug.replace(/[^a-zA-Z0-9_]/g, "_");
344
- const name = capitalize(safeSlug);
345
-
346
- resolvers[`update${name}`] = async ({ input }: { input: any }) => {
347
- await dataStore.updateGlobal(slug, input);
348
- return { data: await dataStore.findGlobal(slug), success: true };
349
- };
350
- }
351
-
352
- return resolvers;
353
- }
354
-
355
- export async function executeGraphQL(
356
- query: string,
357
- variables?: Record<string, any>,
358
- authToken?: string,
359
- apiKey?: string,
360
- ) {
361
- let context: Context = {};
362
-
363
- if (apiKey) {
364
- try {
365
- const result = await validateApiKeyFromDataStore(apiKey);
366
- if (result.valid && result.user) {
367
- context.user = {
368
- id: result.userId as string,
369
- email: result.user.email,
370
- role: result.user.role,
371
- };
372
- context.apiKeyId = result.apiKeyId;
373
- context.permissions = result.permissions;
374
- }
375
- } catch {
376
- // Invalid API key, continue without auth
377
- }
378
- } else if (authToken) {
379
- try {
380
- const payload = jwt.verify(authToken, JWT_SECRET) as jwt.JwtPayload;
381
- context.user = {
382
- id: payload.sub as string,
383
- email: (payload as any).email,
384
- role: (payload as any).role,
385
- };
386
- } catch {
387
- // Invalid token, continue without auth
388
- }
389
- }
390
-
391
- const result = await graphql({
392
- schema: typeDefs,
393
- source: query,
394
- rootValue,
395
- contextValue: context,
396
- variableValues: variables,
397
- } as any);
398
-
399
- return result;
400
- }
401
-
402
- async function validateApiKeyFromDataStore(apiKey: string) {
403
- if (!apiKey.startsWith("kyro_")) {
404
- return { valid: false };
405
- }
406
-
407
- const keyPrefix = apiKey.substring(0, 8);
408
- const result = await dataStore.find("_api_keys", { limit: 100 });
409
- const keys = (result.docs || []).filter(
410
- (k: any) => k.keyPrefix === keyPrefix,
411
- );
412
-
413
- for (const record of keys) {
414
- if (record.key === apiKey) {
415
- if (record.expiresAt && new Date(record.expiresAt) < new Date()) {
416
- return { valid: false, error: "API key expired" };
417
- }
418
-
419
- try {
420
- await dataStore.update("_api_keys", record.id, {
421
- lastUsedAt: new Date().toISOString(),
422
- });
423
- } catch {}
424
-
425
- return {
426
- valid: true,
427
- userId: record.userId,
428
- user: {
429
- id: record.userId,
430
- email: record.email,
431
- role: record.role || "author",
432
- tenantId: record.tenantId,
433
- },
434
- permissions: record.permissions || [],
435
- apiKeyId: record.id,
436
- };
437
- }
438
- }
439
-
440
- return { valid: false };
441
- }
442
-
443
- export { schemaString };
@@ -1,267 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import { randomBytes } from "crypto";
3
-
4
- const DB_PATH =
5
- process.env.KYRO_AUTH_DB_PATH || process.env.KYRO_DB_PATH || "./data/auth.db";
6
-
7
- interface RateLimitConfig {
8
- maxAttempts: number;
9
- windowMs: number;
10
- lockoutMs: number;
11
- }
12
-
13
- interface RateLimitResult {
14
- allowed: boolean;
15
- remaining: number;
16
- resetAt: Date | null;
17
- lockedUntil: Date | null;
18
- }
19
-
20
- const DEFAULT_CONFIG: RateLimitConfig = {
21
- maxAttempts: 5,
22
- windowMs: 15 * 60 * 1000, // 15 minutes
23
- lockoutMs: 15 * 60 * 1000, // 15 minutes lockout
24
- };
25
-
26
- function getDb() {
27
- return new Database(DB_PATH);
28
- }
29
-
30
- export async function checkRateLimit(
31
- identifier: string,
32
- action: string = "login",
33
- config: RateLimitConfig = DEFAULT_CONFIG,
34
- ): Promise<RateLimitResult> {
35
- const db = getDb();
36
- const now = new Date();
37
-
38
- try {
39
- // Clean up expired records first
40
- db.prepare(
41
- `
42
- DELETE FROM rate_limits
43
- WHERE expires_at IS NOT NULL AND expires_at < ?
44
- `,
45
- ).run(now.toISOString());
46
-
47
- // Get existing record
48
- const existing = db
49
- .prepare(
50
- `
51
- SELECT * FROM rate_limits
52
- WHERE identifier = ? AND action = ?
53
- `,
54
- )
55
- .get(identifier, action) as any;
56
-
57
- if (!existing) {
58
- // First attempt - create record
59
- const id = randomBytes(16).toString("hex");
60
- db.prepare(
61
- `
62
- INSERT INTO rate_limits (id, identifier, action, attempts, first_attempt, last_attempt, created_at)
63
- VALUES (?, ?, ?, 1, ?, ?, ?)
64
- `,
65
- ).run(
66
- id,
67
- identifier,
68
- action,
69
- now.toISOString(),
70
- now.toISOString(),
71
- now.toISOString(),
72
- );
73
-
74
- return {
75
- allowed: true,
76
- remaining: config.maxAttempts - 1,
77
- resetAt: new Date(now.getTime() + config.windowMs),
78
- lockedUntil: null,
79
- };
80
- }
81
-
82
- // Check if currently locked
83
- if (existing.expires_at && new Date(existing.expires_at) > now) {
84
- return {
85
- allowed: false,
86
- remaining: 0,
87
- resetAt: null,
88
- lockedUntil: new Date(existing.expires_at),
89
- };
90
- }
91
-
92
- // Check if within window
93
- const firstAttempt = existing.first_attempt
94
- ? new Date(existing.first_attempt)
95
- : now;
96
- const windowEnd = new Date(firstAttempt.getTime() + config.windowMs);
97
-
98
- if (now > windowEnd) {
99
- // Window expired - reset attempts
100
- db.prepare(
101
- `
102
- UPDATE rate_limits
103
- SET attempts = 1, first_attempt = ?, last_attempt = ?, expires_at = NULL
104
- WHERE identifier = ? AND action = ?
105
- `,
106
- ).run(now.toISOString(), now.toISOString(), identifier, action);
107
-
108
- return {
109
- allowed: true,
110
- remaining: config.maxAttempts - 1,
111
- resetAt: new Date(now.getTime() + config.windowMs),
112
- lockedUntil: null,
113
- };
114
- }
115
-
116
- // Within window - check attempts
117
- const attempts = existing.attempts || 0;
118
- const remaining = config.maxAttempts - attempts;
119
-
120
- if (attempts >= config.maxAttempts) {
121
- // Lock out the user
122
- const lockUntil = new Date(now.getTime() + config.lockoutMs);
123
- db.prepare(
124
- `
125
- UPDATE rate_limits
126
- SET last_attempt = ?, expires_at = ?
127
- WHERE identifier = ? AND action = ?
128
- `,
129
- ).run(now.toISOString(), lockUntil.toISOString(), identifier, action);
130
-
131
- return {
132
- allowed: false,
133
- remaining: 0,
134
- resetAt: windowEnd,
135
- lockedUntil: lockUntil,
136
- };
137
- }
138
-
139
- // Increment attempts
140
- db.prepare(
141
- `
142
- UPDATE rate_limits
143
- SET attempts = attempts + 1, last_attempt = ?
144
- WHERE identifier = ? AND action = ?
145
- `,
146
- ).run(now.toISOString(), identifier, action);
147
-
148
- return {
149
- allowed: true,
150
- remaining: remaining - 1,
151
- resetAt: windowEnd,
152
- lockedUntil: null,
153
- };
154
- } finally {
155
- db.close();
156
- }
157
- }
158
-
159
- export async function resetRateLimit(
160
- identifier: string,
161
- action: string = "login",
162
- ): Promise<void> {
163
- const db = getDb();
164
-
165
- try {
166
- db.prepare(
167
- `
168
- DELETE FROM rate_limits WHERE identifier = ? AND action = ?
169
- `,
170
- ).run(identifier, action);
171
- } finally {
172
- db.close();
173
- }
174
- }
175
-
176
- export async function getAccountLockStatus(email: string): Promise<{
177
- isLocked: boolean;
178
- lockedUntil: Date | null;
179
- failedAttempts: number;
180
- }> {
181
- const db = getDb();
182
-
183
- try {
184
- const user = db
185
- .prepare(
186
- `
187
- SELECT locked, locked_until, failed_login_attempts
188
- FROM users
189
- WHERE LOWER(email) = LOWER(?)
190
- `,
191
- )
192
- .get(email) as any;
193
-
194
- if (!user) {
195
- return { isLocked: false, lockedUntil: null, failedAttempts: 0 };
196
- }
197
-
198
- const now = new Date();
199
- const lockedUntil = user.locked_until ? new Date(user.locked_until) : null;
200
- const isLocked = user.locked === 1 && (!lockedUntil || lockedUntil > now);
201
-
202
- return {
203
- isLocked,
204
- lockedUntil: isLocked ? lockedUntil : null,
205
- failedAttempts: user.failed_login_attempts || 0,
206
- };
207
- } finally {
208
- db.close();
209
- }
210
- }
211
-
212
- export async function recordFailedLogin(email: string): Promise<void> {
213
- const db = getDb();
214
-
215
- try {
216
- const now = new Date();
217
- const user = db
218
- .prepare(
219
- `
220
- SELECT id FROM users WHERE LOWER(email) = LOWER(?)
221
- `,
222
- )
223
- .get(email) as any;
224
-
225
- if (!user) return;
226
-
227
- // Increment failed attempts
228
- db.prepare(
229
- `
230
- UPDATE users
231
- SET failed_login_attempts = COALESCE(failed_login_attempts, 0) + 1,
232
- last_login = ?,
233
- locked = CASE
234
- WHEN COALESCE(failed_login_attempts, 0) >= 4 THEN 1
235
- ELSE 0
236
- END,
237
- locked_until = CASE
238
- WHEN COALESCE(failed_login_attempts, 0) >= 4 THEN ?
239
- ELSE NULL
240
- END
241
- WHERE id = ?
242
- `,
243
- ).run(
244
- now.toISOString(),
245
- new Date(now.getTime() + 15 * 60 * 1000).toISOString(),
246
- user.id,
247
- );
248
- } finally {
249
- db.close();
250
- }
251
- }
252
-
253
- export async function unlockAccount(email: string): Promise<void> {
254
- const db = getDb();
255
-
256
- try {
257
- db.prepare(
258
- `
259
- UPDATE users
260
- SET locked = 0, locked_until = NULL, failed_login_attempts = 0
261
- WHERE LOWER(email) = LOWER(?)
262
- `,
263
- ).run(email);
264
- } finally {
265
- db.close();
266
- }
267
- }