@kyro-cms/core 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +202 -113
  2. package/dist/bootstrap-2WJK6PG7.cjs +29 -0
  3. package/dist/bootstrap-2WJK6PG7.cjs.map +1 -0
  4. package/dist/bootstrap-Q2TWUQF3.js +4 -0
  5. package/dist/bootstrap-Q2TWUQF3.js.map +1 -0
  6. package/dist/chunk-3QX6KG2S.js +2125 -0
  7. package/dist/chunk-3QX6KG2S.js.map +1 -0
  8. package/dist/chunk-5AOILNGY.cjs +212 -0
  9. package/dist/chunk-5AOILNGY.cjs.map +1 -0
  10. package/dist/{chunk-DKSMFC3L.js → chunk-EINVJPFM.js} +2 -2
  11. package/dist/{chunk-DKSMFC3L.js.map → chunk-EINVJPFM.js.map} +1 -1
  12. package/dist/chunk-F5B64H5S.cjs +2149 -0
  13. package/dist/chunk-F5B64H5S.cjs.map +1 -0
  14. package/dist/chunk-I4BORBXT.cjs +914 -0
  15. package/dist/chunk-I4BORBXT.cjs.map +1 -0
  16. package/dist/chunk-KA3UOIFC.js +206 -0
  17. package/dist/chunk-KA3UOIFC.js.map +1 -0
  18. package/dist/chunk-KWTKEBHM.cjs +176 -0
  19. package/dist/chunk-KWTKEBHM.cjs.map +1 -0
  20. package/dist/chunk-M4JFHQ5J.js +170 -0
  21. package/dist/chunk-M4JFHQ5J.js.map +1 -0
  22. package/dist/chunk-PZ5AY32C.js +9 -0
  23. package/dist/chunk-PZ5AY32C.js.map +1 -0
  24. package/dist/chunk-Q7SFCCGT.cjs +11 -0
  25. package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
  26. package/dist/chunk-U4CHJTWX.cjs +94 -0
  27. package/dist/chunk-U4CHJTWX.cjs.map +1 -0
  28. package/dist/{chunk-3Q3FS5J4.cjs → chunk-V3B25QOK.cjs} +2 -2
  29. package/dist/{chunk-3Q3FS5J4.cjs.map → chunk-V3B25QOK.cjs.map} +1 -1
  30. package/dist/chunk-V67YXRBT.js +899 -0
  31. package/dist/chunk-V67YXRBT.js.map +1 -0
  32. package/dist/chunk-XLMVCGXA.js +86 -0
  33. package/dist/chunk-XLMVCGXA.js.map +1 -0
  34. package/dist/cli/index.cjs +106 -14
  35. package/dist/cli/index.cjs.map +1 -1
  36. package/dist/cli/index.js +106 -14
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/database-37KXWUER.js +5 -0
  39. package/dist/database-37KXWUER.js.map +1 -0
  40. package/dist/database-LJKD3HE4.cjs +22 -0
  41. package/dist/database-LJKD3HE4.cjs.map +1 -0
  42. package/dist/drizzle/index.cjs +25 -5
  43. package/dist/drizzle/index.d.cts +5 -49
  44. package/dist/drizzle/index.d.ts +5 -49
  45. package/dist/drizzle/index.js +5 -1
  46. package/dist/graphql/index.cjs +1 -0
  47. package/dist/graphql/index.js +1 -0
  48. package/dist/index-BVFlb7uU.d.ts +192 -0
  49. package/dist/index-CzkEHKqu.d.cts +192 -0
  50. package/dist/index.cjs +1479 -25
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +421 -65
  53. package/dist/index.d.ts +421 -65
  54. package/dist/index.js +1384 -22
  55. package/dist/index.js.map +1 -1
  56. package/dist/mongodb/index.cjs +1 -0
  57. package/dist/mongodb/index.js +1 -0
  58. package/dist/postgres-auth-adapter-CYZAVPPP.cjs +14 -0
  59. package/dist/postgres-auth-adapter-CYZAVPPP.cjs.map +1 -0
  60. package/dist/postgres-auth-adapter-LTDUGBMB.js +5 -0
  61. package/dist/postgres-auth-adapter-LTDUGBMB.js.map +1 -0
  62. package/dist/rest/index.cjs +1 -0
  63. package/dist/rest/index.js +1 -0
  64. package/dist/templates/index.cjs +94 -536
  65. package/dist/templates/index.cjs.map +1 -1
  66. package/dist/templates/index.d.cts +45 -1
  67. package/dist/templates/index.d.ts +45 -1
  68. package/dist/templates/index.js +2 -535
  69. package/dist/templates/index.js.map +1 -1
  70. package/dist/trpc/index.cjs +1 -0
  71. package/dist/trpc/index.js +1 -0
  72. package/dist/ws/index.cjs +1 -0
  73. package/dist/ws/index.js +1 -0
  74. package/package.json +23 -8
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ export { allSettingsGlobals, blogCollections, blogGlobals, coreSettingsGlobals, createTemplateConfig, ecommerceCollections, ecommerceGlobals, ecommerceSettingsGlobals, kitchenSinkCollections, mediaCollections, minimalCollections } from './chunk-3QX6KG2S.js';
2
+ import { RedisAuthAdapter, EmailTransport, PasswordPolicy } from './chunk-V67YXRBT.js';
3
+ export { EmailTransport, PasswordPolicy, RedisAuthAdapter, autoBootstrap, bootstrapAdmin, getBootstrapFromEnv } from './chunk-V67YXRBT.js';
1
4
  import { createKyroServer } from './chunk-UEYC46RL.js';
2
5
  export { createContext, createCountProcedure, createCreateProcedure, createDeleteProcedure, createDynamicRouter, createFindByIDProcedure, createFindProcedure, createKyroServer, createUpdateProcedure } from './chunk-UEYC46RL.js';
3
6
  import { buildGraphQLSchema } from './chunk-OG3KX56O.js';
@@ -7,15 +10,20 @@ export { createHonoApp, createRESTAPI } from './chunk-YPAFJ7EV.js';
7
10
  export { evaluateAccess, getWhereClause, mergeWhereClauses } from './chunk-SDMNUYVU.js';
8
11
  import { KyroPubSub, createWSServer } from './chunk-3TPQ2BU6.js';
9
12
  export { KyroPubSub, KyroWSServer, PubSub, createWSServer } from './chunk-3TPQ2BU6.js';
10
- export { DrizzleAdapter, collectionToDrizzleSchema, createDrizzleAdapter, fieldToDrizzleType } from './chunk-DKSMFC3L.js';
13
+ export { DrizzleAdapter, collectionToDrizzleSchema, createDrizzleAdapter, fieldToDrizzleType } from './chunk-EINVJPFM.js';
14
+ export { PostgresAuthAdapter } from './chunk-M4JFHQ5J.js';
15
+ export { createDatabase, runMigrations, seedDefaultRoles } from './chunk-XLMVCGXA.js';
16
+ import './chunk-KA3UOIFC.js';
11
17
  export { MongoDBAdapter, createMongoDBAdapter } from './chunk-DIC236EW.js';
12
18
  import { AbstractBaseAdapter } from './chunk-BXMWDUED.js';
13
19
  export { AbstractBaseAdapter } from './chunk-BXMWDUED.js';
20
+ import './chunk-PZ5AY32C.js';
14
21
  import { z } from 'zod';
15
22
  export { z } from 'zod';
16
- import bcrypt from 'bcrypt';
17
- import jwt from 'jsonwebtoken';
18
- import { randomUUID } from 'crypto';
23
+ import bcrypt2 from 'bcrypt';
24
+ import jwt2 from 'jsonwebtoken';
25
+ import bcrypt from 'bcryptjs';
26
+ import { randomBytes } from 'crypto';
19
27
 
20
28
  // src/registry/validator.ts
21
29
  var ConfigValidationError = class extends Error {
@@ -645,10 +653,6 @@ var Registry = class {
645
653
  if (this.initialized) {
646
654
  throw new Error("Cannot add collections after Registry has been initialized");
647
655
  }
648
- const errors = validateCollection(config);
649
- if (errors.length > 0) {
650
- throw new Error(`Invalid collection config: ${errors.join(", ")}`);
651
- }
652
656
  let finalConfig = { ...config };
653
657
  for (const plugin of this.plugins) {
654
658
  if (plugin.extendCollection) {
@@ -656,6 +660,10 @@ var Registry = class {
656
660
  }
657
661
  }
658
662
  finalConfig.fields = this.applyFieldDefaults(finalConfig);
663
+ const errors = validateCollection(finalConfig);
664
+ if (errors.length > 0) {
665
+ throw new Error(`Invalid collection config: ${errors.join(", ")}`);
666
+ }
659
667
  this.collections.set(finalConfig.slug, finalConfig);
660
668
  this.clearSchemaCache(finalConfig.slug);
661
669
  }
@@ -1143,13 +1151,13 @@ async function runFieldHooks(hooks, args) {
1143
1151
  // src/database/local/adapter.ts
1144
1152
  var LocalAdapter = class extends AbstractBaseAdapter {
1145
1153
  db;
1154
+ path;
1146
1155
  migrations = /* @__PURE__ */ new Map();
1147
1156
  constructor(options) {
1148
1157
  super();
1158
+ this.path = options.path;
1149
1159
  if (options.db) {
1150
1160
  this.db = options.db;
1151
- } else if (options.path) {
1152
- this.db = null;
1153
1161
  } else {
1154
1162
  this.db = null;
1155
1163
  }
@@ -1157,12 +1165,12 @@ var LocalAdapter = class extends AbstractBaseAdapter {
1157
1165
  async connect() {
1158
1166
  if (!this.db) {
1159
1167
  const Database = (await import('better-sqlite3')).default;
1160
- this.db = new Database(":memory:");
1168
+ this.db = new Database(this.path || ":memory:");
1161
1169
  }
1162
1170
  this.db.pragma("journal_mode = WAL");
1163
1171
  this.db.pragma("foreign_keys = ON");
1164
1172
  this.connected = true;
1165
- console.log("[LocalAdapter] Connected to SQLite");
1173
+ console.log(`[LocalAdapter] Connected to SQLite (${this.path || "memory"})`);
1166
1174
  }
1167
1175
  async disconnect() {
1168
1176
  if (this.db) {
@@ -1992,6 +2000,1333 @@ var defaultFieldStyling = {
1992
2000
  }
1993
2001
  }
1994
2002
  };
2003
+ var SQLiteAuthAdapter = class {
2004
+ db = null;
2005
+ path;
2006
+ saltRounds;
2007
+ externalDb;
2008
+ constructor(options = {}) {
2009
+ this.path = options.path || "./data.db";
2010
+ this.saltRounds = options.saltRounds || 12;
2011
+ this.externalDb = !!options.db;
2012
+ if (options.db) {
2013
+ this.db = options.db;
2014
+ }
2015
+ }
2016
+ async connect() {
2017
+ if (this.db) return;
2018
+ const Database = (await import('better-sqlite3')).default;
2019
+ this.db = new Database(this.path);
2020
+ this.db.pragma("journal_mode = WAL");
2021
+ this.db.pragma("foreign_keys = ON");
2022
+ this.ensureTables();
2023
+ }
2024
+ async disconnect() {
2025
+ if (this.db && !this.externalDb) {
2026
+ this.db.close();
2027
+ this.db = null;
2028
+ }
2029
+ }
2030
+ ensureTables() {
2031
+ if (!this.db) return;
2032
+ this.db.exec(`
2033
+ CREATE TABLE IF NOT EXISTS kyro_users (
2034
+ id TEXT PRIMARY KEY,
2035
+ email TEXT UNIQUE NOT NULL,
2036
+ password_hash TEXT NOT NULL,
2037
+ role TEXT NOT NULL DEFAULT 'customer',
2038
+ tenant_id TEXT,
2039
+ email_verified INTEGER DEFAULT 0,
2040
+ locked INTEGER DEFAULT 0,
2041
+ last_login TEXT,
2042
+ failed_login_attempts INTEGER DEFAULT 0,
2043
+ locked_until TEXT,
2044
+ created_at TEXT NOT NULL,
2045
+ updated_at TEXT NOT NULL
2046
+ );
2047
+
2048
+ CREATE TABLE IF NOT EXISTS kyro_sessions (
2049
+ id TEXT PRIMARY KEY,
2050
+ user_id TEXT NOT NULL,
2051
+ token TEXT NOT NULL,
2052
+ refresh_token TEXT,
2053
+ expires_at TEXT NOT NULL,
2054
+ created_at TEXT NOT NULL,
2055
+ ip_address TEXT,
2056
+ user_agent TEXT,
2057
+ FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
2058
+ );
2059
+
2060
+ CREATE TABLE IF NOT EXISTS kyro_password_history (
2061
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2062
+ user_id TEXT NOT NULL,
2063
+ password_hash TEXT NOT NULL,
2064
+ created_at TEXT NOT NULL,
2065
+ FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
2066
+ );
2067
+
2068
+ CREATE INDEX IF NOT EXISTS idx_kyro_users_email ON kyro_users(email);
2069
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_user_id ON kyro_sessions(user_id);
2070
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_token ON kyro_sessions(token);
2071
+ CREATE INDEX IF NOT EXISTS idx_kyro_sessions_refresh_token ON kyro_sessions(refresh_token);
2072
+ CREATE INDEX IF NOT EXISTS idx_kyro_password_history_user_id ON kyro_password_history(user_id);
2073
+ `);
2074
+ }
2075
+ async createUser(data) {
2076
+ if (!this.db) throw new Error("Not connected");
2077
+ const id = randomBytes(16).toString("hex");
2078
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2079
+ const user = {
2080
+ id,
2081
+ email: data.email.toLowerCase(),
2082
+ passwordHash: data.passwordHash,
2083
+ role: data.role || "customer",
2084
+ tenantId: data.tenantId,
2085
+ createdAt: now,
2086
+ updatedAt: now
2087
+ };
2088
+ this.db.prepare(
2089
+ `INSERT INTO kyro_users (id, email, password_hash, role, tenant_id, created_at, updated_at)
2090
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
2091
+ ).run(
2092
+ id,
2093
+ user.email,
2094
+ user.passwordHash,
2095
+ user.role,
2096
+ user.tenantId,
2097
+ now,
2098
+ now
2099
+ );
2100
+ return user;
2101
+ }
2102
+ async findUserByEmail(email) {
2103
+ if (!this.db) throw new Error("Not connected");
2104
+ const row = this.db.prepare("SELECT * FROM kyro_users WHERE email = ?").get(email.toLowerCase());
2105
+ if (!row) return null;
2106
+ return this.rowToUser(row);
2107
+ }
2108
+ async findUserById(userId) {
2109
+ if (!this.db) throw new Error("Not connected");
2110
+ const row = this.db.prepare("SELECT * FROM kyro_users WHERE id = ?").get(userId);
2111
+ if (!row) return null;
2112
+ return this.rowToUser(row);
2113
+ }
2114
+ async updateUser(userId, data) {
2115
+ if (!this.db) throw new Error("Not connected");
2116
+ const existing = await this.findUserById(userId);
2117
+ if (!existing) return null;
2118
+ const updates = [];
2119
+ const values = [];
2120
+ if (data.email !== void 0) {
2121
+ updates.push("email = ?");
2122
+ values.push(data.email.toLowerCase());
2123
+ }
2124
+ if (data.passwordHash !== void 0) {
2125
+ updates.push("password_hash = ?");
2126
+ values.push(data.passwordHash);
2127
+ }
2128
+ if (data.role !== void 0) {
2129
+ updates.push("role = ?");
2130
+ values.push(data.role);
2131
+ }
2132
+ if (data.tenantId !== void 0) {
2133
+ updates.push("tenant_id = ?");
2134
+ values.push(data.tenantId);
2135
+ }
2136
+ if (data.emailVerified !== void 0) {
2137
+ updates.push("email_verified = ?");
2138
+ values.push(data.emailVerified ? 1 : 0);
2139
+ }
2140
+ if (data.locked !== void 0) {
2141
+ updates.push("locked = ?");
2142
+ values.push(data.locked ? 1 : 0);
2143
+ }
2144
+ if (data.lastLogin !== void 0) {
2145
+ updates.push("last_login = ?");
2146
+ values.push(data.lastLogin);
2147
+ }
2148
+ if (data.failedLoginAttempts !== void 0) {
2149
+ updates.push("failed_login_attempts = ?");
2150
+ values.push(data.failedLoginAttempts);
2151
+ }
2152
+ updates.push("updated_at = ?");
2153
+ values.push((/* @__PURE__ */ new Date()).toISOString());
2154
+ values.push(userId);
2155
+ this.db.prepare(`UPDATE kyro_users SET ${updates.join(", ")} WHERE id = ?`).run(...values);
2156
+ return this.findUserById(userId);
2157
+ }
2158
+ async deleteUser(userId) {
2159
+ if (!this.db) throw new Error("Not connected");
2160
+ const result = this.db.prepare("DELETE FROM kyro_users WHERE id = ?").run(userId);
2161
+ return result.changes > 0;
2162
+ }
2163
+ async hashPassword(password) {
2164
+ return bcrypt.hash(password, this.saltRounds);
2165
+ }
2166
+ async verifyPassword(password, hash) {
2167
+ return bcrypt.compare(password, hash);
2168
+ }
2169
+ async createSession(userId, data = {}) {
2170
+ if (!this.db) throw new Error("Not connected");
2171
+ const id = randomBytes(32).toString("hex");
2172
+ const token = randomBytes(32).toString("base64url");
2173
+ const refreshToken = randomBytes(32).toString("base64url");
2174
+ const now = /* @__PURE__ */ new Date();
2175
+ const expiresAt = new Date(now.getTime() + 864e5).toISOString();
2176
+ const session = {
2177
+ id,
2178
+ userId,
2179
+ token,
2180
+ refreshToken,
2181
+ expiresAt,
2182
+ createdAt: now.toISOString(),
2183
+ ipAddress: data.ipAddress,
2184
+ userAgent: data.userAgent
2185
+ };
2186
+ this.db.prepare(
2187
+ `INSERT INTO kyro_sessions (id, user_id, token, refresh_token, expires_at, created_at, ip_address, user_agent)
2188
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
2189
+ ).run(
2190
+ session.id,
2191
+ session.userId,
2192
+ session.token,
2193
+ session.refreshToken,
2194
+ session.expiresAt,
2195
+ session.createdAt,
2196
+ session.ipAddress,
2197
+ session.userAgent
2198
+ );
2199
+ return session;
2200
+ }
2201
+ async findSessionByToken(token) {
2202
+ if (!this.db) throw new Error("Not connected");
2203
+ const row = this.db.prepare("SELECT * FROM kyro_sessions WHERE token = ?").get(token);
2204
+ if (!row) return null;
2205
+ return this.rowToSession(row);
2206
+ }
2207
+ async findSessionByRefreshToken(refreshToken) {
2208
+ if (!this.db) throw new Error("Not connected");
2209
+ const row = this.db.prepare("SELECT * FROM kyro_sessions WHERE refresh_token = ?").get(refreshToken);
2210
+ if (!row) return null;
2211
+ return this.rowToSession(row);
2212
+ }
2213
+ async deleteSession(sessionId) {
2214
+ if (!this.db) throw new Error("Not connected");
2215
+ const result = this.db.prepare("DELETE FROM kyro_sessions WHERE id = ? OR token = ?").run(sessionId, sessionId);
2216
+ return result.changes > 0;
2217
+ }
2218
+ async deleteUserSessions(userId) {
2219
+ if (!this.db) throw new Error("Not connected");
2220
+ const result = this.db.prepare("DELETE FROM kyro_sessions WHERE user_id = ?").run(userId);
2221
+ return result.changes;
2222
+ }
2223
+ async hasAnyUsers() {
2224
+ if (!this.db) throw new Error("Not connected");
2225
+ const row = this.db.prepare("SELECT COUNT(*) as count FROM kyro_users").get();
2226
+ return row.count > 0;
2227
+ }
2228
+ async addPasswordToHistory(userId, passwordHash) {
2229
+ if (!this.db) throw new Error("Not connected");
2230
+ this.db.prepare(
2231
+ "INSERT INTO kyro_password_history (user_id, password_hash, created_at) VALUES (?, ?, ?)"
2232
+ ).run(userId, passwordHash, (/* @__PURE__ */ new Date()).toISOString());
2233
+ this.db.prepare(
2234
+ `DELETE FROM kyro_password_history WHERE id IN (
2235
+ SELECT id FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT -1 OFFSET 5
2236
+ )`
2237
+ ).run(userId);
2238
+ }
2239
+ async getPasswordHistory(userId, count = 5) {
2240
+ if (!this.db) throw new Error("Not connected");
2241
+ const rows = this.db.prepare(
2242
+ "SELECT password_hash FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT ?"
2243
+ ).all(userId, count);
2244
+ return rows.map((r) => r.password_hash);
2245
+ }
2246
+ rowToUser(row) {
2247
+ return {
2248
+ id: row.id,
2249
+ email: row.email,
2250
+ passwordHash: row.password_hash,
2251
+ role: row.role,
2252
+ tenantId: row.tenant_id,
2253
+ emailVerified: row.email_verified === 1,
2254
+ locked: row.locked === 1,
2255
+ lastLogin: row.last_login,
2256
+ failedLoginAttempts: row.failed_login_attempts || 0,
2257
+ createdAt: row.created_at,
2258
+ updatedAt: row.updated_at
2259
+ };
2260
+ }
2261
+ rowToSession(row) {
2262
+ return {
2263
+ id: row.id,
2264
+ userId: row.user_id,
2265
+ token: row.token,
2266
+ refreshToken: row.refresh_token,
2267
+ expiresAt: row.expires_at,
2268
+ createdAt: row.created_at,
2269
+ ipAddress: row.ip_address,
2270
+ userAgent: row.user_agent
2271
+ };
2272
+ }
2273
+ };
2274
+
2275
+ // src/auth/security/lockout.ts
2276
+ var DEFAULT_LOCKOUT_CONFIG = {
2277
+ maxAttempts: 5,
2278
+ lockDuration: 9e5,
2279
+ notifyUser: true,
2280
+ notifyAdmin: true,
2281
+ adminNotifyAfter: 3
2282
+ };
2283
+ var AccountLockout = class {
2284
+ redis;
2285
+ prefix;
2286
+ config;
2287
+ constructor(redis, config = {}, prefix = "kyro:lockout:") {
2288
+ this.redis = redis;
2289
+ this.prefix = prefix;
2290
+ this.config = { ...DEFAULT_LOCKOUT_CONFIG, ...config };
2291
+ }
2292
+ lockKey(userId) {
2293
+ return `${this.prefix}${userId}`;
2294
+ }
2295
+ historyKey(userId) {
2296
+ return `${this.prefix}${userId}:history`;
2297
+ }
2298
+ async checkLockout(userId) {
2299
+ const key = this.lockKey(userId);
2300
+ const data = await this.redis.hgetall(key);
2301
+ if (!data || Object.keys(data).length === 0) {
2302
+ return {
2303
+ locked: false,
2304
+ attemptsRemaining: this.config.maxAttempts,
2305
+ totalAttempts: 0
2306
+ };
2307
+ }
2308
+ const attempts = parseInt(data.attempts, 10);
2309
+ const lockedUntil = data.lockedUntil ? new Date(parseInt(data.lockedUntil, 10)) : void 0;
2310
+ if (lockedUntil && lockedUntil > /* @__PURE__ */ new Date()) {
2311
+ return {
2312
+ locked: true,
2313
+ attemptsRemaining: 0,
2314
+ lockedUntil,
2315
+ totalAttempts: attempts
2316
+ };
2317
+ }
2318
+ if (lockedUntil && lockedUntil <= /* @__PURE__ */ new Date()) {
2319
+ await this.unlockAccount(userId);
2320
+ return {
2321
+ locked: false,
2322
+ attemptsRemaining: this.config.maxAttempts,
2323
+ totalAttempts: 0
2324
+ };
2325
+ }
2326
+ return {
2327
+ locked: false,
2328
+ attemptsRemaining: Math.max(0, this.config.maxAttempts - attempts),
2329
+ totalAttempts: attempts
2330
+ };
2331
+ }
2332
+ async recordFailedAttempt(userId) {
2333
+ const key = this.lockKey(userId);
2334
+ const historyKey = this.historyKey(userId);
2335
+ const now = Date.now();
2336
+ const current = await this.redis.hincrby(key, "attempts", 1);
2337
+ await this.redis.hset(key, "lastAttempt", now.toString());
2338
+ await this.redis.lpush(historyKey, now.toString());
2339
+ await this.redis.ltrim(historyKey, 0, 99);
2340
+ if (current >= this.config.maxAttempts) {
2341
+ const lockedUntil = new Date(now + this.config.lockDuration);
2342
+ await this.redis.hset(key, {
2343
+ lockedAt: now.toString(),
2344
+ lockedUntil: lockedUntil.getTime().toString()
2345
+ });
2346
+ await this.redis.expire(
2347
+ key,
2348
+ Math.ceil(this.config.lockDuration / 1e3) + 3600
2349
+ );
2350
+ return {
2351
+ locked: true,
2352
+ attemptsRemaining: 0,
2353
+ lockedUntil,
2354
+ totalAttempts: current
2355
+ };
2356
+ }
2357
+ return {
2358
+ locked: false,
2359
+ attemptsRemaining: Math.max(0, this.config.maxAttempts - current),
2360
+ totalAttempts: current
2361
+ };
2362
+ }
2363
+ async lockAccount(userId, duration) {
2364
+ const key = this.lockKey(userId);
2365
+ const now = Date.now();
2366
+ const lockDuration = duration || this.config.lockDuration;
2367
+ const lockedUntil = new Date(now + lockDuration);
2368
+ const pipeline = this.redis.pipeline();
2369
+ pipeline.hset(key, {
2370
+ attempts: this.config.maxAttempts.toString(),
2371
+ lockedAt: now.toString(),
2372
+ lockedUntil: lockedUntil.getTime().toString()
2373
+ });
2374
+ pipeline.expire(key, Math.ceil(lockDuration / 1e3) + 3600);
2375
+ await pipeline.exec();
2376
+ }
2377
+ async unlockAccount(userId) {
2378
+ const key = this.lockKey(userId);
2379
+ await this.redis.del(key);
2380
+ }
2381
+ async resetAttempts(userId) {
2382
+ const key = this.lockKey(userId);
2383
+ const data = await this.redis.hgetall(key);
2384
+ if (data.lockedAt) {
2385
+ await this.redis.hset(key, {
2386
+ attempts: "0",
2387
+ lockedAt: "",
2388
+ lockedUntil: ""
2389
+ });
2390
+ } else {
2391
+ await this.redis.del(key);
2392
+ }
2393
+ }
2394
+ async getLockoutHistory(userId, limit = 10) {
2395
+ const historyKey = this.historyKey(userId);
2396
+ const timestamps = await this.redis.lrange(historyKey, 0, limit - 1);
2397
+ return timestamps.map((ts) => new Date(parseInt(ts, 10)));
2398
+ }
2399
+ async getLockoutStats(userId) {
2400
+ const historyKey = this.historyKey(userId);
2401
+ const timestamps = await this.redis.lrange(historyKey, 0, -1);
2402
+ const lockouts = timestamps.filter((_, i) => {
2403
+ const attemptNum = i + 1;
2404
+ return attemptNum % this.config.maxAttempts === 0;
2405
+ }).length;
2406
+ const lastLockoutData = await this.redis.hget(
2407
+ this.lockKey(userId),
2408
+ "lockedAt"
2409
+ );
2410
+ return {
2411
+ totalFailedAttempts: timestamps.length,
2412
+ lockoutCount: lockouts,
2413
+ lastLockout: lastLockoutData ? new Date(parseInt(lastLockoutData, 10)) : null,
2414
+ averageAttemptsBeforeLockout: lockouts > 0 ? this.config.maxAttempts : 0
2415
+ };
2416
+ }
2417
+ shouldNotifyAdmin(currentAttempts) {
2418
+ return this.config.notifyAdmin && currentAttempts >= this.config.adminNotifyAfter;
2419
+ }
2420
+ getConfig() {
2421
+ return { ...this.config };
2422
+ }
2423
+ setConfig(config) {
2424
+ this.config = { ...this.config, ...config };
2425
+ }
2426
+ };
2427
+
2428
+ // src/auth/security/rate-limit.ts
2429
+ var DEFAULT_RATE_LIMITS = {
2430
+ "auth:login": { window: 9e5, max: 5 },
2431
+ "auth:register": { window: 36e5, max: 3 },
2432
+ "auth:forgot": { window: 36e5, max: 3 },
2433
+ "auth:reset": { window: 36e5, max: 5 },
2434
+ "auth:verify": { window: 36e5, max: 5 },
2435
+ "api:general": { window: 6e4, max: 100 },
2436
+ "api:authenticated": { window: 6e4, max: 200 }
2437
+ };
2438
+ var RateLimiter = class {
2439
+ redis;
2440
+ prefix;
2441
+ limits;
2442
+ userLimits;
2443
+ constructor(redis, limits, userLimits, prefix = "kyro:ratelimit:") {
2444
+ this.redis = redis;
2445
+ this.prefix = prefix;
2446
+ this.limits = { ...DEFAULT_RATE_LIMITS, ...limits };
2447
+ this.userLimits = userLimits || {
2448
+ "user:api": { window: 6e4, max: 500 },
2449
+ "user:write": { window: 36e5, max: 100 }
2450
+ };
2451
+ }
2452
+ getKey(type, identifier) {
2453
+ return `${this.prefix}${type}:${identifier}`;
2454
+ }
2455
+ async check(type, identifier) {
2456
+ const config = this.limits[type] || this.limits["api:general"];
2457
+ const key = this.getKey(type, identifier);
2458
+ const now = Date.now();
2459
+ const windowStart = now - config.window;
2460
+ const pipeline = this.redis.pipeline();
2461
+ pipeline.zremrangebyscore(key, 0, windowStart);
2462
+ pipeline.zcard(key);
2463
+ pipeline.zadd(key, now, `${now}:${Math.random()}`);
2464
+ pipeline.expire(key, Math.ceil(config.window / 1e3) + 1);
2465
+ const results = await pipeline.exec();
2466
+ const count = results?.[1]?.[1] || 0;
2467
+ if (count >= config.max) {
2468
+ const oldestTimestamp = await this.redis.zrange(key, 0, 0, "WITHSCORES");
2469
+ const resetAt = oldestTimestamp.length > 1 ? parseInt(oldestTimestamp[1], 10) + config.window : now + config.window;
2470
+ return {
2471
+ allowed: false,
2472
+ remaining: 0,
2473
+ resetAt,
2474
+ retryAfter: Math.ceil((resetAt - now) / 1e3)
2475
+ };
2476
+ }
2477
+ return {
2478
+ allowed: true,
2479
+ remaining: config.max - count - 1,
2480
+ resetAt: now + config.window
2481
+ };
2482
+ }
2483
+ async checkUser(type, userId, identifier) {
2484
+ const config = this.userLimits[type] || this.userLimits["user:api"];
2485
+ const key = this.getKey(`user:${type}:${userId}`, identifier);
2486
+ const now = Date.now();
2487
+ const windowStart = now - config.window;
2488
+ const pipeline = this.redis.pipeline();
2489
+ pipeline.zremrangebyscore(key, 0, windowStart);
2490
+ pipeline.zcard(key);
2491
+ pipeline.zadd(key, now, `${now}:${Math.random()}`);
2492
+ pipeline.expire(key, Math.ceil(config.window / 1e3) + 1);
2493
+ const results = await pipeline.exec();
2494
+ const count = results?.[1]?.[1] || 0;
2495
+ if (count >= config.max) {
2496
+ const oldestTimestamp = await this.redis.zrange(key, 0, 0, "WITHSCORES");
2497
+ const resetAt = oldestTimestamp.length > 1 ? parseInt(oldestTimestamp[1], 10) + config.window : now + config.window;
2498
+ return {
2499
+ allowed: false,
2500
+ remaining: 0,
2501
+ resetAt,
2502
+ retryAfter: Math.ceil((resetAt - now) / 1e3)
2503
+ };
2504
+ }
2505
+ return {
2506
+ allowed: true,
2507
+ remaining: config.max - count - 1,
2508
+ resetAt: now + config.window
2509
+ };
2510
+ }
2511
+ async reset(type, identifier) {
2512
+ const key = this.getKey(type, identifier);
2513
+ await this.redis.del(key);
2514
+ }
2515
+ async resetUser(type, userId, identifier) {
2516
+ const key = this.getKey(`user:${type}:${userId}`, identifier);
2517
+ await this.redis.del(key);
2518
+ }
2519
+ async getStatus(type, identifier) {
2520
+ const config = this.limits[type] || this.limits["api:general"];
2521
+ const key = this.getKey(type, identifier);
2522
+ const now = Date.now();
2523
+ const windowStart = now - config.window;
2524
+ await this.redis.zremrangebyscore(key, 0, windowStart);
2525
+ const count = await this.redis.zcard(key);
2526
+ return {
2527
+ count,
2528
+ limit: config.max,
2529
+ remaining: Math.max(0, config.max - count),
2530
+ resetAt: now + config.window
2531
+ };
2532
+ }
2533
+ setLimit(type, config) {
2534
+ this.limits[type] = config;
2535
+ }
2536
+ setUserLimit(type, config) {
2537
+ this.userLimits[type] = config;
2538
+ }
2539
+ };
2540
+ var AuditLogger = class {
2541
+ redis;
2542
+ prefix;
2543
+ retentionDays;
2544
+ constructor(redis, retentionDays = 30, prefix = "kyro:audit:") {
2545
+ this.redis = redis;
2546
+ this.prefix = prefix;
2547
+ this.retentionDays = retentionDays;
2548
+ }
2549
+ async log(data) {
2550
+ const id = randomBytes(16).toString("hex");
2551
+ const timestamp = /* @__PURE__ */ new Date();
2552
+ const log = {
2553
+ ...data,
2554
+ id,
2555
+ timestamp
2556
+ };
2557
+ const key = this.getKeyForDate(timestamp);
2558
+ const hashKey = `${this.prefix}log:${id}`;
2559
+ await this.redis.hset(hashKey, this.serializeLog(log));
2560
+ await this.redis.expire(hashKey, this.retentionDays * 24 * 60 * 60 + 3600);
2561
+ await this.redis.zadd(key, timestamp.getTime(), id);
2562
+ await this.redis.expire(key, this.retentionDays * 24 * 60 * 60 + 3600);
2563
+ const userIndex = data.userId ? `${this.prefix}user:${data.userId}` : null;
2564
+ if (userIndex) {
2565
+ await this.redis.zadd(userIndex, timestamp.getTime(), id);
2566
+ await this.redis.expire(
2567
+ userIndex,
2568
+ this.retentionDays * 24 * 60 * 60 + 3600
2569
+ );
2570
+ }
2571
+ return id;
2572
+ }
2573
+ async get(id) {
2574
+ const hashKey = `${this.prefix}log:${id}`;
2575
+ const data = await this.redis.hgetall(hashKey);
2576
+ if (!data || Object.keys(data).length === 0) {
2577
+ return null;
2578
+ }
2579
+ return this.deserializeLog(data);
2580
+ }
2581
+ async query(filter = {}) {
2582
+ const { limit = 50, offset = 0 } = filter;
2583
+ let keys = [];
2584
+ if (filter.userId) {
2585
+ keys.push(`${this.prefix}user:${filter.userId}`);
2586
+ } else if (filter.startDate || filter.endDate) {
2587
+ keys = this.getKeysForDateRange(filter.startDate, filter.endDate);
2588
+ } else {
2589
+ const now = /* @__PURE__ */ new Date();
2590
+ keys = this.getKeysForDateRange(
2591
+ new Date(now.getTime() - this.retentionDays * 24 * 60 * 60 * 1e3),
2592
+ now
2593
+ );
2594
+ }
2595
+ let idScores = [];
2596
+ for (const key of keys) {
2597
+ const items = await this.redis.zrange(key, 0, -1, "WITHSCORES");
2598
+ for (let i = 0; i < items.length; i += 2) {
2599
+ idScores.push([items[i], parseInt(items[i + 1], 10)]);
2600
+ }
2601
+ }
2602
+ idScores.sort((a, b) => b[1] - a[1]);
2603
+ const total = idScores.length;
2604
+ idScores = idScores.slice(offset, offset + limit);
2605
+ const logs = [];
2606
+ for (const [id] of idScores) {
2607
+ const log = await this.get(id);
2608
+ if (log) {
2609
+ if (this.matchesFilter(log, filter)) {
2610
+ logs.push(log);
2611
+ }
2612
+ }
2613
+ }
2614
+ return { logs, total };
2615
+ }
2616
+ async getRecent(limit = 50) {
2617
+ const logs = [];
2618
+ const now = /* @__PURE__ */ new Date();
2619
+ const keys = this.getKeysForDateRange(
2620
+ new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3),
2621
+ now
2622
+ );
2623
+ let allIds = [];
2624
+ for (const key of keys) {
2625
+ const items = await this.redis.zrange(key, 0, -1, "WITHSCORES");
2626
+ for (let i = 0; i < items.length; i += 2) {
2627
+ allIds.push([items[i], parseInt(items[i + 1], 10)]);
2628
+ }
2629
+ }
2630
+ allIds.sort((a, b) => b[1] - a[1]);
2631
+ for (const [id] of allIds.slice(0, limit)) {
2632
+ const log = await this.get(id);
2633
+ if (log) logs.push(log);
2634
+ }
2635
+ return logs;
2636
+ }
2637
+ async getUserActivity(userId, limit = 50) {
2638
+ const key = `${this.prefix}user:${userId}`;
2639
+ const ids = await this.redis.zrange(key, 0, limit - 1);
2640
+ const logs = [];
2641
+ for (const id of ids) {
2642
+ const log = await this.get(id);
2643
+ if (log) logs.push(log);
2644
+ }
2645
+ return logs;
2646
+ }
2647
+ async getStats(startDate, endDate) {
2648
+ const keys = this.getKeysForDateRange(
2649
+ startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3),
2650
+ endDate || /* @__PURE__ */ new Date()
2651
+ );
2652
+ const byAction = {};
2653
+ let totalEvents = 0;
2654
+ let failedLogins = 0;
2655
+ let successCount = 0;
2656
+ const uniqueUsers = /* @__PURE__ */ new Set();
2657
+ for (const key of keys) {
2658
+ const ids = await this.redis.zrange(key, 0, -1);
2659
+ for (const id of ids) {
2660
+ const log = await this.get(id);
2661
+ if (log) {
2662
+ totalEvents++;
2663
+ byAction[log.action] = (byAction[log.action] || 0) + 1;
2664
+ if (log.success) {
2665
+ successCount++;
2666
+ }
2667
+ if (log.action === "login_failed") {
2668
+ failedLogins++;
2669
+ }
2670
+ if (log.userId) {
2671
+ uniqueUsers.add(log.userId);
2672
+ }
2673
+ }
2674
+ }
2675
+ }
2676
+ return {
2677
+ totalEvents,
2678
+ byAction,
2679
+ successRate: totalEvents > 0 ? successCount / totalEvents : 1,
2680
+ failedLogins,
2681
+ uniqueUsers
2682
+ };
2683
+ }
2684
+ async cleanup() {
2685
+ const cutoff = Date.now() - this.retentionDays * 24 * 60 * 60 * 1e3;
2686
+ const keys = await this.redis.keys(`${this.prefix}date:*`);
2687
+ let deleted = 0;
2688
+ for (const key of keys) {
2689
+ const timestamp = await this.redis.zrangebyscore(key, 0, cutoff);
2690
+ for (const id of timestamp) {
2691
+ await this.redis.del(`${this.prefix}log:${id}`);
2692
+ deleted++;
2693
+ }
2694
+ await this.redis.zremrangebyscore(key, 0, cutoff);
2695
+ }
2696
+ return deleted;
2697
+ }
2698
+ getKeyForDate(date) {
2699
+ const year = date.getFullYear();
2700
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2701
+ const day = String(date.getDate()).padStart(2, "0");
2702
+ return `${this.prefix}date:${year}-${month}-${day}`;
2703
+ }
2704
+ getKeysForDateRange(start, end) {
2705
+ const keys = [];
2706
+ const startDate = start || new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1e3);
2707
+ const endDate = end || /* @__PURE__ */ new Date();
2708
+ const current = new Date(startDate);
2709
+ while (current <= endDate) {
2710
+ keys.push(this.getKeyForDate(current));
2711
+ current.setDate(current.getDate() + 1);
2712
+ }
2713
+ return keys;
2714
+ }
2715
+ matchesFilter(log, filter) {
2716
+ if (filter.action) {
2717
+ const actions = Array.isArray(filter.action) ? filter.action : [filter.action];
2718
+ if (!actions.includes(log.action)) return false;
2719
+ }
2720
+ if (filter.resource && log.resource !== filter.resource) return false;
2721
+ if (filter.resourceId && log.resourceId !== filter.resourceId) return false;
2722
+ if (filter.success !== void 0 && log.success !== filter.success)
2723
+ return false;
2724
+ return true;
2725
+ }
2726
+ serializeLog(log) {
2727
+ const result = {
2728
+ id: log.id,
2729
+ timestamp: log.timestamp.toISOString(),
2730
+ action: log.action,
2731
+ resource: log.resource,
2732
+ success: log.success ? "1" : "0"
2733
+ };
2734
+ if (log.userId) result.userId = log.userId;
2735
+ if (log.userEmail) result.userEmail = log.userEmail;
2736
+ if (log.role) result.role = log.role;
2737
+ if (log.resourceId) result.resourceId = log.resourceId;
2738
+ if (log.ipAddress) result.ipAddress = log.ipAddress;
2739
+ if (log.userAgent) result.userAgent = log.userAgent;
2740
+ if (log.error) result.error = log.error;
2741
+ if (log.changes) result.changes = JSON.stringify(log.changes);
2742
+ if (log.metadata) result.metadata = JSON.stringify(log.metadata);
2743
+ return result;
2744
+ }
2745
+ deserializeLog(data) {
2746
+ return {
2747
+ id: data.id,
2748
+ timestamp: new Date(data.timestamp),
2749
+ action: data.action,
2750
+ userId: data.userId,
2751
+ userEmail: data.userEmail,
2752
+ role: data.role,
2753
+ resource: data.resource,
2754
+ resourceId: data.resourceId,
2755
+ ipAddress: data.ipAddress,
2756
+ userAgent: data.userAgent,
2757
+ success: data.success === "1",
2758
+ error: data.error,
2759
+ changes: data.changes ? JSON.parse(data.changes) : void 0,
2760
+ metadata: data.metadata ? JSON.parse(data.metadata) : void 0
2761
+ };
2762
+ }
2763
+ };
2764
+ function createAuditContext(req) {
2765
+ return {
2766
+ ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || "unknown",
2767
+ userAgent: req.headers.get("user-agent") || "unknown"
2768
+ };
2769
+ }
2770
+ function defaultExtractToken(req) {
2771
+ const authHeader = req.headers.get("Authorization");
2772
+ if (authHeader?.startsWith("Bearer ")) {
2773
+ return authHeader.slice(7);
2774
+ }
2775
+ const cookieHeader = req.headers.get("Cookie");
2776
+ if (cookieHeader) {
2777
+ const cookies = Object.fromEntries(
2778
+ cookieHeader.split("; ").map((c) => {
2779
+ const [key, ...val] = c.split("=");
2780
+ return [key.trim(), val.join("=")];
2781
+ })
2782
+ );
2783
+ return cookies["auth_token"] || null;
2784
+ }
2785
+ return null;
2786
+ }
2787
+ function generateToken(payload, secret, options = {}) {
2788
+ return jwt2.sign(payload, secret, {
2789
+ expiresIn: options.expiresIn || "24h",
2790
+ issuer: options.issuer,
2791
+ audience: options.audience
2792
+ });
2793
+ }
2794
+
2795
+ // src/api/rest/auth-routes.ts
2796
+ var AuthRoutes = class {
2797
+ redis;
2798
+ email;
2799
+ jwtSecret;
2800
+ jwtExpiresIn;
2801
+ jwtIssuer;
2802
+ jwtAudience;
2803
+ passwordPolicy;
2804
+ lockout;
2805
+ rateLimiter;
2806
+ auditLogger;
2807
+ baseUrl;
2808
+ emailVerificationRequired;
2809
+ constructor(config) {
2810
+ this.redis = config.redis;
2811
+ this.email = config.email;
2812
+ this.jwtSecret = config.jwtSecret;
2813
+ this.jwtExpiresIn = config.jwtExpiresIn || "24h";
2814
+ this.jwtIssuer = config.jwtIssuer;
2815
+ this.jwtAudience = config.jwtAudience;
2816
+ this.passwordPolicy = config.passwordPolicy || new PasswordPolicy();
2817
+ this.lockout = config.lockout;
2818
+ this.rateLimiter = config.rateLimiter;
2819
+ this.auditLogger = config.auditLogger;
2820
+ this.baseUrl = config.baseUrl || "http://localhost:3000";
2821
+ this.emailVerificationRequired = config.emailVerificationRequired ?? true;
2822
+ }
2823
+ async register(req) {
2824
+ const { ipAddress, userAgent } = createAuditContext(req);
2825
+ if (this.rateLimiter) {
2826
+ const limit = await this.rateLimiter.check("auth:register", ipAddress);
2827
+ if (!limit.allowed) {
2828
+ return this.rateLimitResponse(limit);
2829
+ }
2830
+ }
2831
+ try {
2832
+ const body = await req.json();
2833
+ if (!body.email || !body.password) {
2834
+ return this.errorResponse("Email and password are required", 400);
2835
+ }
2836
+ if (body.password !== body.confirmPassword) {
2837
+ return this.errorResponse("Passwords do not match", 400);
2838
+ }
2839
+ const passwordValidation = this.passwordPolicy.validate(body.password);
2840
+ if (!passwordValidation.valid) {
2841
+ return this.errorResponse(passwordValidation.errors.join(". "), 400);
2842
+ }
2843
+ const existingUser = await this.redis.findUserByEmail(body.email);
2844
+ if (existingUser) {
2845
+ return this.errorResponse("Email already registered", 400);
2846
+ }
2847
+ const passwordHash = await this.redis.hashPassword(body.password);
2848
+ const user = await this.redis.createUser({
2849
+ email: body.email,
2850
+ passwordHash,
2851
+ role: body.role || "customer",
2852
+ tenantId: body.tenantId
2853
+ });
2854
+ if (this.emailVerificationRequired && this.email) {
2855
+ const verificationToken = randomBytes(32).toString("hex");
2856
+ const verificationUrl = `${this.baseUrl}/api/auth/verify?token=${verificationToken}`;
2857
+ await this.redis.createSession(user.id, { ipAddress, userAgent });
2858
+ const template = this.email.getTemplates().verifyEmail(verificationUrl, body.email);
2859
+ await this.email.send({ to: body.email, ...template });
2860
+ }
2861
+ if (this.auditLogger) {
2862
+ await this.auditLogger.log({
2863
+ action: "register",
2864
+ userId: user.id,
2865
+ userEmail: user.email,
2866
+ resource: "auth",
2867
+ ipAddress,
2868
+ userAgent,
2869
+ success: true
2870
+ });
2871
+ }
2872
+ return this.jsonResponse(
2873
+ {
2874
+ success: true,
2875
+ message: "Registration successful",
2876
+ user: this.sanitizeUser(user),
2877
+ requiresVerification: this.emailVerificationRequired && !!this.email
2878
+ },
2879
+ 201
2880
+ );
2881
+ } catch (error) {
2882
+ return this.errorResponse("Registration failed", 500);
2883
+ }
2884
+ }
2885
+ async login(req) {
2886
+ const { ipAddress, userAgent } = createAuditContext(req);
2887
+ if (this.rateLimiter) {
2888
+ const limit = await this.rateLimiter.check("auth:login", ipAddress);
2889
+ if (!limit.allowed) {
2890
+ return this.rateLimitResponse(limit);
2891
+ }
2892
+ }
2893
+ try {
2894
+ const body = await req.json();
2895
+ if (!body.email || !body.password) {
2896
+ return this.errorResponse("Email and password are required", 400);
2897
+ }
2898
+ const user = await this.redis.findUserByEmail(body.email);
2899
+ if (!user) {
2900
+ await this.recordFailedLogin(ipAddress, userAgent);
2901
+ return this.errorResponse("Invalid credentials", 401);
2902
+ }
2903
+ if (this.lockout) {
2904
+ const lockoutStatus = await this.lockout.checkLockout(user.id);
2905
+ if (lockoutStatus.locked) {
2906
+ if (this.auditLogger) {
2907
+ await this.auditLogger.log({
2908
+ action: "login_failed",
2909
+ userId: user.id,
2910
+ userEmail: user.email,
2911
+ resource: "auth",
2912
+ ipAddress,
2913
+ userAgent,
2914
+ success: false,
2915
+ error: "Account locked"
2916
+ });
2917
+ }
2918
+ return this.errorResponse(
2919
+ `Account locked. Try again in ${Math.ceil((lockoutStatus.lockedUntil.getTime() - Date.now()) / 6e4)} minutes`,
2920
+ 423
2921
+ );
2922
+ }
2923
+ }
2924
+ const validPassword = user.passwordHash ? await this.redis.verifyPassword(body.password, user.passwordHash) : false;
2925
+ if (!validPassword) {
2926
+ await this.recordFailedLogin(ipAddress, userAgent, user.id, user.email);
2927
+ return this.errorResponse("Invalid credentials", 401);
2928
+ }
2929
+ if (this.lockout) {
2930
+ await this.lockout.resetAttempts(user.id);
2931
+ }
2932
+ const session = await this.redis.createSession(user.id, {
2933
+ ipAddress,
2934
+ userAgent
2935
+ });
2936
+ const payload = {
2937
+ sub: user.id,
2938
+ email: user.email,
2939
+ role: user.role,
2940
+ tenantId: user.tenantId,
2941
+ iat: Math.floor(Date.now() / 1e3),
2942
+ exp: Math.floor(Date.now() / 1e3) + 86400
2943
+ };
2944
+ const accessToken = generateToken(payload, this.jwtSecret, {
2945
+ expiresIn: this.jwtExpiresIn,
2946
+ issuer: this.jwtIssuer,
2947
+ audience: this.jwtAudience
2948
+ });
2949
+ if (this.auditLogger) {
2950
+ await this.auditLogger.log({
2951
+ action: "login",
2952
+ userId: user.id,
2953
+ userEmail: user.email,
2954
+ role: user.role,
2955
+ resource: "auth",
2956
+ ipAddress,
2957
+ userAgent,
2958
+ success: true
2959
+ });
2960
+ }
2961
+ await this.redis.updateUser(user.id, {
2962
+ lastLogin: (/* @__PURE__ */ new Date()).toISOString()
2963
+ });
2964
+ return this.jsonResponse({
2965
+ success: true,
2966
+ user: this.sanitizeUser(user),
2967
+ accessToken,
2968
+ refreshToken: session.refreshToken,
2969
+ expiresIn: this.jwtExpiresIn
2970
+ });
2971
+ } catch (error) {
2972
+ return this.errorResponse("Login failed", 500);
2973
+ }
2974
+ }
2975
+ async logout(req) {
2976
+ const token = defaultExtractToken(req);
2977
+ if (!token) {
2978
+ return this.errorResponse("No session to logout", 401);
2979
+ }
2980
+ const { ipAddress, userAgent } = createAuditContext(req);
2981
+ try {
2982
+ const payload = jwt2.decode(token);
2983
+ if (payload && payload.sub) {
2984
+ await this.redis.deleteUserSessions(payload.sub);
2985
+ if (this.auditLogger) {
2986
+ await this.auditLogger.log({
2987
+ action: "logout",
2988
+ userId: payload.sub,
2989
+ userEmail: payload.email,
2990
+ resource: "auth",
2991
+ ipAddress,
2992
+ userAgent,
2993
+ success: true
2994
+ });
2995
+ }
2996
+ }
2997
+ return this.jsonResponse({
2998
+ success: true,
2999
+ message: "Logged out successfully"
3000
+ });
3001
+ } catch (error) {
3002
+ return this.errorResponse("Logout failed", 500);
3003
+ }
3004
+ }
3005
+ async refresh(req) {
3006
+ try {
3007
+ const body = await req.json();
3008
+ const { refreshToken } = body;
3009
+ if (!refreshToken) {
3010
+ return this.errorResponse("Refresh token required", 400);
3011
+ }
3012
+ return this.jsonResponse({ success: true, accessToken: "" });
3013
+ } catch (error) {
3014
+ return this.errorResponse("Token refresh failed", 500);
3015
+ }
3016
+ }
3017
+ async me(req) {
3018
+ const token = defaultExtractToken(req);
3019
+ if (!token) {
3020
+ return this.errorResponse("Not authenticated", 401);
3021
+ }
3022
+ try {
3023
+ const payload = jwt2.verify(token, this.jwtSecret, {
3024
+ issuer: this.jwtIssuer,
3025
+ audience: this.jwtAudience
3026
+ });
3027
+ const user = await this.redis.findUserById(payload.sub);
3028
+ if (!user) {
3029
+ return this.errorResponse("User not found", 404);
3030
+ }
3031
+ return this.jsonResponse({
3032
+ success: true,
3033
+ user: this.sanitizeUser(user)
3034
+ });
3035
+ } catch (error) {
3036
+ return this.errorResponse("Authentication failed", 401);
3037
+ }
3038
+ }
3039
+ async changePassword(req) {
3040
+ const token = defaultExtractToken(req);
3041
+ if (!token) {
3042
+ return this.errorResponse("Not authenticated", 401);
3043
+ }
3044
+ const { ipAddress, userAgent } = createAuditContext(req);
3045
+ try {
3046
+ const payload = jwt2.verify(token, this.jwtSecret);
3047
+ const body = await req.json();
3048
+ const { currentPassword, newPassword, confirmPassword } = body;
3049
+ if (!currentPassword || !newPassword) {
3050
+ return this.errorResponse("Current and new password required", 400);
3051
+ }
3052
+ if (newPassword !== confirmPassword) {
3053
+ return this.errorResponse("Passwords do not match", 400);
3054
+ }
3055
+ const passwordValidation = this.passwordPolicy.validate(newPassword);
3056
+ if (!passwordValidation.valid) {
3057
+ return this.errorResponse(passwordValidation.errors.join(". "), 400);
3058
+ }
3059
+ const user = await this.redis.findUserById(payload.sub);
3060
+ if (!user) {
3061
+ return this.errorResponse("User not found", 404);
3062
+ }
3063
+ const validPassword = user.passwordHash ? await this.redis.verifyPassword(currentPassword, user.passwordHash) : false;
3064
+ if (!validPassword) {
3065
+ return this.errorResponse("Current password is incorrect", 401);
3066
+ }
3067
+ const passwordHistory = await this.redis.getPasswordHistory(user.id, 5);
3068
+ const isReused = await this.redis.isPasswordInHistory(
3069
+ newPassword,
3070
+ user.id,
3071
+ 5
3072
+ );
3073
+ if (isReused) {
3074
+ return this.errorResponse(
3075
+ "Password was recently used. Please choose a different password",
3076
+ 400
3077
+ );
3078
+ }
3079
+ const newPasswordHash = await this.redis.hashPassword(newPassword);
3080
+ if (user.passwordHash) {
3081
+ await this.redis.addPasswordToHistory(user.id, user.passwordHash);
3082
+ }
3083
+ await this.redis.updateUser(user.id, { passwordHash: newPasswordHash });
3084
+ await this.redis.deleteUserSessions(user.id);
3085
+ if (this.email && this.email.getTemplates) {
3086
+ const template = this.email.getTemplates().passwordChanged(user.email);
3087
+ await this.email.send({ to: user.email, ...template });
3088
+ }
3089
+ if (this.auditLogger) {
3090
+ await this.auditLogger.log({
3091
+ action: "password_change",
3092
+ userId: user.id,
3093
+ userEmail: user.email,
3094
+ resource: "auth",
3095
+ ipAddress,
3096
+ userAgent,
3097
+ success: true
3098
+ });
3099
+ }
3100
+ return this.jsonResponse({
3101
+ success: true,
3102
+ message: "Password changed successfully"
3103
+ });
3104
+ } catch (error) {
3105
+ return this.errorResponse("Password change failed", 500);
3106
+ }
3107
+ }
3108
+ async forgotPassword(req) {
3109
+ const { ipAddress, userAgent } = createAuditContext(req);
3110
+ if (this.rateLimiter) {
3111
+ const limit = await this.rateLimiter.check("auth:forgot", ipAddress);
3112
+ if (!limit.allowed) {
3113
+ return this.rateLimitResponse(limit);
3114
+ }
3115
+ }
3116
+ try {
3117
+ const body = await req.json();
3118
+ const { email } = body;
3119
+ if (!email) {
3120
+ return this.errorResponse("Email required", 400);
3121
+ }
3122
+ const user = await this.redis.findUserByEmail(email);
3123
+ if (!user) {
3124
+ return this.jsonResponse({
3125
+ success: true,
3126
+ message: "If the email exists, a reset link has been sent"
3127
+ });
3128
+ }
3129
+ if (this.email) {
3130
+ const resetToken = randomBytes(32).toString("hex");
3131
+ const resetUrl = `${this.baseUrl}/api/auth/reset-password?token=${resetToken}`;
3132
+ const template = this.email.getTemplates().resetPassword(resetUrl, user.email);
3133
+ await this.email.send({ to: user.email, ...template });
3134
+ }
3135
+ if (this.auditLogger) {
3136
+ await this.auditLogger.log({
3137
+ action: "password_reset_request",
3138
+ userId: user.id,
3139
+ userEmail: user.email,
3140
+ resource: "auth",
3141
+ ipAddress,
3142
+ userAgent,
3143
+ success: true
3144
+ });
3145
+ }
3146
+ return this.jsonResponse({
3147
+ success: true,
3148
+ message: "If the email exists, a reset link has been sent"
3149
+ });
3150
+ } catch (error) {
3151
+ return this.errorResponse("Password reset request failed", 500);
3152
+ }
3153
+ }
3154
+ async verifyEmail(req) {
3155
+ const url = new URL(req.url);
3156
+ const token = url.searchParams.get("token");
3157
+ if (!token) {
3158
+ return this.errorResponse("Verification token required", 400);
3159
+ }
3160
+ try {
3161
+ return this.jsonResponse({ success: true, message: "Email verified" });
3162
+ } catch (error) {
3163
+ return this.errorResponse("Email verification failed", 500);
3164
+ }
3165
+ }
3166
+ async recordFailedLogin(ipAddress, userAgent, userId, userEmail) {
3167
+ if (this.lockout) {
3168
+ await this.lockout.recordFailedAttempt(userId || ipAddress);
3169
+ }
3170
+ if (this.auditLogger) {
3171
+ await this.auditLogger.log({
3172
+ action: "login_failed",
3173
+ userId,
3174
+ userEmail,
3175
+ resource: "auth",
3176
+ ipAddress,
3177
+ userAgent,
3178
+ success: false,
3179
+ error: "Invalid credentials"
3180
+ });
3181
+ }
3182
+ }
3183
+ sanitizeUser(user) {
3184
+ const { passwordHash, ...sanitized } = user;
3185
+ return sanitized;
3186
+ }
3187
+ jsonResponse(data, status = 200) {
3188
+ return new Response(JSON.stringify(data), {
3189
+ status,
3190
+ headers: {
3191
+ "Content-Type": "application/json"
3192
+ }
3193
+ });
3194
+ }
3195
+ errorResponse(message, status) {
3196
+ return new Response(JSON.stringify({ success: false, error: message }), {
3197
+ status,
3198
+ headers: {
3199
+ "Content-Type": "application/json"
3200
+ }
3201
+ });
3202
+ }
3203
+ rateLimitResponse(limit) {
3204
+ return new Response(
3205
+ JSON.stringify({
3206
+ success: false,
3207
+ error: "Too many requests",
3208
+ retryAfter: limit.retryAfter
3209
+ }),
3210
+ {
3211
+ status: 429,
3212
+ headers: {
3213
+ "Content-Type": "application/json",
3214
+ "Retry-After": String(limit.retryAfter || 60)
3215
+ }
3216
+ }
3217
+ );
3218
+ }
3219
+ };
3220
+
3221
+ // src/auth/config.ts
3222
+ function getEnv(key, fallback = "") {
3223
+ return process.env[key] || fallback;
3224
+ }
3225
+ function getEnvBool(key, fallback = false) {
3226
+ const val = process.env[key];
3227
+ if (!val) return fallback;
3228
+ return val.toLowerCase() === "true";
3229
+ }
3230
+ function getEnvNum(key, fallback = 0) {
3231
+ const val = process.env[key];
3232
+ if (!val) return fallback;
3233
+ return parseInt(val, 10);
3234
+ }
3235
+ async function createAuthConfig() {
3236
+ const redisUrl = getEnv("REDIS_URL", "redis://localhost:6379");
3237
+ const redisKeyPrefix = getEnv("REDIS_KEY_PREFIX", "kyro:auth:");
3238
+ const redisSessionTTL = getEnvNum("REDIS_SESSION_TTL", 86400);
3239
+ const redisRefreshTTL = getEnvNum("REDIS_REFRESH_TOKEN_TTL", 604800);
3240
+ const redisAdapter = new RedisAuthAdapter({
3241
+ url: redisUrl,
3242
+ keyPrefix: redisKeyPrefix,
3243
+ tokenExpiration: redisSessionTTL,
3244
+ refreshTokenExpiration: redisRefreshTTL,
3245
+ tls: getEnvBool("REDIS_TLS", false)
3246
+ });
3247
+ await redisAdapter.connect();
3248
+ const redisClient = redisAdapter.redis;
3249
+ const emailConfig = getEmailConfig();
3250
+ const email = emailConfig ? new EmailTransport(emailConfig) : void 0;
3251
+ const passwordPolicy = new PasswordPolicy({
3252
+ minLength: getEnvNum("PASSWORD_MIN_LENGTH", 12),
3253
+ requireUppercase: getEnvBool("PASSWORD_REQUIRE_UPPERCASE", true),
3254
+ requireLowercase: getEnvBool("PASSWORD_REQUIRE_LOWERCASE", true),
3255
+ requireNumbers: getEnvBool("PASSWORD_REQUIRE_NUMBERS", true),
3256
+ requireSpecialChars: getEnvBool("PASSWORD_REQUIRE_SPECIAL", true),
3257
+ preventReuse: getEnvNum("PASSWORD_PREVENT_REUSE", 5),
3258
+ maxLength: getEnvNum("PASSWORD_MAX_LENGTH", 128)
3259
+ });
3260
+ let lockout;
3261
+ if (getEnvBool("LOCKOUT_ENABLED", true)) {
3262
+ lockout = new AccountLockout(redisClient, {
3263
+ maxAttempts: getEnvNum("LOCKOUT_MAX_ATTEMPTS", 5),
3264
+ lockDuration: getEnvNum("LOCKOUT_DURATION_MINUTES", 15) * 60 * 1e3
3265
+ });
3266
+ }
3267
+ let rateLimiter;
3268
+ if (getEnvBool("RATE_LIMIT_ENABLED", true)) {
3269
+ rateLimiter = new RateLimiter(redisClient, {
3270
+ "auth:login": {
3271
+ window: getEnvNum("RATE_LIMIT_AUTH_WINDOW_MS", 9e5),
3272
+ max: getEnvNum("RATE_LIMIT_AUTH_MAX_REQUESTS", 10)
3273
+ },
3274
+ "api:general": {
3275
+ window: getEnvNum("RATE_LIMIT_WINDOW_MS", 6e4),
3276
+ max: getEnvNum("RATE_LIMIT_MAX_REQUESTS", 100)
3277
+ }
3278
+ });
3279
+ }
3280
+ let auditLogger;
3281
+ if (getEnvBool("AUDIT_LOG_ENABLED", true)) {
3282
+ auditLogger = new AuditLogger(
3283
+ redisClient,
3284
+ getEnvNum("AUDIT_LOG_RETENTION_DAYS", 30)
3285
+ );
3286
+ }
3287
+ const routes = new AuthRoutes({
3288
+ redis: redisAdapter,
3289
+ email,
3290
+ jwtSecret: getEnv("JWT_SECRET", "change-me"),
3291
+ jwtExpiresIn: getEnv("JWT_EXPIRES_IN", "24h"),
3292
+ jwtIssuer: getEnv("JWT_ISSUER", "kyro-cms"),
3293
+ jwtAudience: getEnv("JWT_AUDIENCE", "kyro-cms-client"),
3294
+ passwordPolicy,
3295
+ lockout,
3296
+ rateLimiter,
3297
+ auditLogger,
3298
+ baseUrl: getEnv("EMAIL_BASE_URL", "http://localhost:4321"),
3299
+ emailVerificationRequired: getEnvBool("EMAIL_VERIFICATION_REQUIRED", true)
3300
+ });
3301
+ return {
3302
+ redis: redisAdapter,
3303
+ redisClient,
3304
+ email,
3305
+ passwordPolicy,
3306
+ lockout,
3307
+ rateLimiter,
3308
+ auditLogger,
3309
+ routes
3310
+ };
3311
+ }
3312
+ function getEmailConfig() {
3313
+ const host = getEnv("SMTP_HOST");
3314
+ if (!host) return void 0;
3315
+ return {
3316
+ host,
3317
+ port: getEnvNum("SMTP_PORT", 587),
3318
+ secure: getEnvBool("SMTP_SECURE", false),
3319
+ auth: {
3320
+ user: getEnv("SMTP_USER"),
3321
+ pass: getEnv("SMTP_PASS")
3322
+ },
3323
+ from: getEnv("SMTP_FROM", "noreply@example.com"),
3324
+ fromName: getEnv("SMTP_FROM_NAME", "Kyro CMS")
3325
+ };
3326
+ }
3327
+ var authConfig = createAuthConfig();
3328
+
3329
+ // src/auth/index.ts
1995
3330
  var DEFAULT_SALT_ROUNDS = 12;
1996
3331
  var DEFAULT_EXPIRES_IN = "24h";
1997
3332
  var DEFAULT_REFRESH_EXPIRES_IN = "7d";
@@ -2020,7 +3355,7 @@ var Auth = class {
2020
3355
  email: data.email,
2021
3356
  passwordHash,
2022
3357
  role: data.role ?? "customer",
2023
- tenant: data.tenant
3358
+ tenantId: data.tenantId
2024
3359
  });
2025
3360
  return this.createSessionForUser(user);
2026
3361
  } catch (error) {
@@ -2051,7 +3386,7 @@ var Auth = class {
2051
3386
  async refreshToken(refreshToken) {
2052
3387
  try {
2053
3388
  const session = await this.adapter.findSessionByToken(refreshToken);
2054
- if (!session || session.expiresAt < /* @__PURE__ */ new Date()) {
3389
+ if (!session || new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
2055
3390
  return { success: false, error: "Invalid or expired refresh token" };
2056
3391
  }
2057
3392
  const user = await this.adapter.findUserById(session.userId);
@@ -2066,7 +3401,7 @@ var Auth = class {
2066
3401
  }
2067
3402
  async verifyToken(token) {
2068
3403
  try {
2069
- const decoded = jwt.verify(token, this.config.secret, {
3404
+ const decoded = jwt2.verify(token, this.config.secret, {
2070
3405
  issuer: this.config.issuer,
2071
3406
  audience: this.config.audience.length > 0 ? this.config.audience[0] : void 0
2072
3407
  });
@@ -2130,9 +3465,7 @@ var Auth = class {
2130
3465
  }
2131
3466
  async createSessionForUser(user) {
2132
3467
  const token = this.generateToken(user);
2133
- const refreshToken = randomUUID();
2134
- const expiresAt = new Date(Date.now() + this.parseExpiresIn(this.config.refreshExpiresIn));
2135
- const session = await this.adapter.createSession(user.id, refreshToken, expiresAt);
3468
+ const session = await this.adapter.createSession(user.id);
2136
3469
  return {
2137
3470
  success: true,
2138
3471
  user,
@@ -2145,7 +3478,7 @@ var Auth = class {
2145
3478
  sub: user.id,
2146
3479
  email: user.email,
2147
3480
  role: user.role,
2148
- tenant: user.tenant
3481
+ tenantId: user.tenantId
2149
3482
  };
2150
3483
  const signOptions = {
2151
3484
  expiresIn: this.parseExpiresIn(this.config.expiresIn) / 1e3,
@@ -2154,10 +3487,10 @@ var Auth = class {
2154
3487
  if (this.config.audience.length > 0) {
2155
3488
  signOptions.audience = this.config.audience[0];
2156
3489
  }
2157
- return jwt.sign(payload, this.config.secret, signOptions);
3490
+ return jwt2.sign(payload, this.config.secret, signOptions);
2158
3491
  }
2159
3492
  async hashPassword(password) {
2160
- return bcrypt.hash(password, this.config.saltRounds);
3493
+ return bcrypt2.hash(password, this.config.saltRounds);
2161
3494
  }
2162
3495
  parseExpiresIn(value) {
2163
3496
  if (typeof value === "number") return value;
@@ -2329,6 +3662,35 @@ function isArchived(status) {
2329
3662
  return status === "archived";
2330
3663
  }
2331
3664
 
2332
- export { ALL_FIELD_TYPES, AnalyticsPlugin, Auth, COMPLEX_FIELD_TYPES, CSSGenerator, CommentsPlugin, ConfigValidationError, Kyro, KyroPlugin, LAYOUT_FIELD_TYPES, LocalAdapter, PRIMITIVE_FIELD_TYPES, PluginManager, RELATIONAL_FIELD_TYPES, Registry, ReviewsPlugin, SEOPLugin, VersionManager, WishlistPlugin, collectionToCreateZod, collectionToUpdateZod, collectionToWhereZod, collectionToZod, createAdminStyling, createAuth, createKyro, createLocalAdapter, createRegistry, createVersionManager, defaultDarkTheme, defaultFieldStyling, defaultLightTheme, ecommerce2026Theme, fieldToZod, generateCSSVariables, generateTailwindConfig, getDefaultDraftPublishConfig, getRegistry, globalToZod, isArchived, isArrayField, isBlocksField, isDraft, isGroupField, isLayoutField, isNumberField, isPublished, isRelationshipField, isRichTextField, isSelectField, isTextField, isUploadField, presetPlugins, resetRegistry, runFieldHooks, runHooks, validateCollection, validateConfig, validateFields, validateGlobal };
3665
+ // src/registry/config.ts
3666
+ function normalizeCollections(collections) {
3667
+ if (!collections) return [];
3668
+ if (Array.isArray(collections)) return collections;
3669
+ return Object.values(collections);
3670
+ }
3671
+ function normalizeGlobals(globals) {
3672
+ if (!globals) return [];
3673
+ if (Array.isArray(globals)) return globals;
3674
+ return Object.values(globals);
3675
+ }
3676
+ function defineConfig(config) {
3677
+ return {
3678
+ collections: normalizeCollections(config.collections),
3679
+ globals: normalizeGlobals(config.globals),
3680
+ adapter: config.adapter,
3681
+ plugins: config.plugins,
3682
+ auth: config.auth,
3683
+ cors: config.cors,
3684
+ admin: config.admin,
3685
+ upload: config.upload,
3686
+ graphQL: config.graphQL,
3687
+ typescript: config.typescript,
3688
+ localization: config.localization,
3689
+ rateLimit: config.rateLimit,
3690
+ debug: config.debug
3691
+ };
3692
+ }
3693
+
3694
+ export { ALL_FIELD_TYPES, AccountLockout, AnalyticsPlugin, AuditLogger, Auth, COMPLEX_FIELD_TYPES, CSSGenerator, CommentsPlugin, ConfigValidationError, Kyro, KyroPlugin, LAYOUT_FIELD_TYPES, LocalAdapter, PRIMITIVE_FIELD_TYPES, PluginManager, RELATIONAL_FIELD_TYPES, RateLimiter, Registry, ReviewsPlugin, SEOPLugin, SQLiteAuthAdapter, VersionManager, WishlistPlugin, authConfig, collectionToCreateZod, collectionToUpdateZod, collectionToWhereZod, collectionToZod, createAdminStyling, createAuditContext, createAuth, createAuthConfig, createKyro, createLocalAdapter, createRegistry, createVersionManager, defaultDarkTheme, defaultFieldStyling, defaultLightTheme, defineConfig, ecommerce2026Theme, fieldToZod, generateCSSVariables, generateTailwindConfig, getDefaultDraftPublishConfig, getRegistry, globalToZod, isArchived, isArrayField, isBlocksField, isDraft, isGroupField, isLayoutField, isNumberField, isPublished, isRelationshipField, isRichTextField, isSelectField, isTextField, isUploadField, presetPlugins, resetRegistry, runFieldHooks, runHooks, validateCollection, validateConfig, validateFields, validateGlobal };
2333
3695
  //# sourceMappingURL=index.js.map
2334
3696
  //# sourceMappingURL=index.js.map