@kyro-cms/core 0.1.1 → 0.1.2

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 +196 -109
  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 +1203 -23
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +382 -68
  53. package/dist/index.d.ts +382 -68
  54. package/dist/index.js +1110 -20
  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.cjs CHANGED
@@ -1,22 +1,28 @@
1
1
  'use strict';
2
2
 
3
+ var chunkF5B64H5S_cjs = require('./chunk-F5B64H5S.cjs');
4
+ var chunkI4BORBXT_cjs = require('./chunk-I4BORBXT.cjs');
3
5
  var chunk3VZCX4DF_cjs = require('./chunk-3VZCX4DF.cjs');
4
6
  var chunkK7QF2QCM_cjs = require('./chunk-K7QF2QCM.cjs');
5
7
  var chunkUEG7KMKC_cjs = require('./chunk-UEG7KMKC.cjs');
6
8
  var chunkR3XIBBAW_cjs = require('./chunk-R3XIBBAW.cjs');
7
9
  var chunkDVD5P72E_cjs = require('./chunk-DVD5P72E.cjs');
8
- var chunk3Q3FS5J4_cjs = require('./chunk-3Q3FS5J4.cjs');
10
+ var chunkV3B25QOK_cjs = require('./chunk-V3B25QOK.cjs');
11
+ var chunkKWTKEBHM_cjs = require('./chunk-KWTKEBHM.cjs');
12
+ var chunkU4CHJTWX_cjs = require('./chunk-U4CHJTWX.cjs');
13
+ require('./chunk-5AOILNGY.cjs');
9
14
  var chunkHT6VE4NW_cjs = require('./chunk-HT6VE4NW.cjs');
10
15
  var chunkRLTG4YZM_cjs = require('./chunk-RLTG4YZM.cjs');
16
+ require('./chunk-Q7SFCCGT.cjs');
11
17
  var zod = require('zod');
12
18
  var bcrypt = require('bcrypt');
13
- var jwt = require('jsonwebtoken');
19
+ var jwt2 = require('jsonwebtoken');
14
20
  var crypto = require('crypto');
15
21
 
16
22
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
23
 
18
24
  var bcrypt__default = /*#__PURE__*/_interopDefault(bcrypt);
19
- var jwt__default = /*#__PURE__*/_interopDefault(jwt);
25
+ var jwt2__default = /*#__PURE__*/_interopDefault(jwt2);
20
26
 
21
27
  // src/registry/validator.ts
22
28
  var ConfigValidationError = class extends Error {
@@ -646,10 +652,6 @@ var Registry = class {
646
652
  if (this.initialized) {
647
653
  throw new Error("Cannot add collections after Registry has been initialized");
648
654
  }
649
- const errors = validateCollection(config);
650
- if (errors.length > 0) {
651
- throw new Error(`Invalid collection config: ${errors.join(", ")}`);
652
- }
653
655
  let finalConfig = { ...config };
654
656
  for (const plugin of this.plugins) {
655
657
  if (plugin.extendCollection) {
@@ -657,6 +659,10 @@ var Registry = class {
657
659
  }
658
660
  }
659
661
  finalConfig.fields = this.applyFieldDefaults(finalConfig);
662
+ const errors = validateCollection(finalConfig);
663
+ if (errors.length > 0) {
664
+ throw new Error(`Invalid collection config: ${errors.join(", ")}`);
665
+ }
660
666
  this.collections.set(finalConfig.slug, finalConfig);
661
667
  this.clearSchemaCache(finalConfig.slug);
662
668
  }
@@ -1144,13 +1150,13 @@ async function runFieldHooks(hooks, args) {
1144
1150
  // src/database/local/adapter.ts
1145
1151
  var LocalAdapter = class extends chunkRLTG4YZM_cjs.AbstractBaseAdapter {
1146
1152
  db;
1153
+ path;
1147
1154
  migrations = /* @__PURE__ */ new Map();
1148
1155
  constructor(options) {
1149
1156
  super();
1157
+ this.path = options.path;
1150
1158
  if (options.db) {
1151
1159
  this.db = options.db;
1152
- } else if (options.path) {
1153
- this.db = null;
1154
1160
  } else {
1155
1161
  this.db = null;
1156
1162
  }
@@ -1158,12 +1164,12 @@ var LocalAdapter = class extends chunkRLTG4YZM_cjs.AbstractBaseAdapter {
1158
1164
  async connect() {
1159
1165
  if (!this.db) {
1160
1166
  const Database = (await import('better-sqlite3')).default;
1161
- this.db = new Database(":memory:");
1167
+ this.db = new Database(this.path || ":memory:");
1162
1168
  }
1163
1169
  this.db.pragma("journal_mode = WAL");
1164
1170
  this.db.pragma("foreign_keys = ON");
1165
1171
  this.connected = true;
1166
- console.log("[LocalAdapter] Connected to SQLite");
1172
+ console.log(`[LocalAdapter] Connected to SQLite (${this.path || "memory"})`);
1167
1173
  }
1168
1174
  async disconnect() {
1169
1175
  if (this.db) {
@@ -1993,6 +1999,1062 @@ var defaultFieldStyling = {
1993
1999
  }
1994
2000
  }
1995
2001
  };
2002
+
2003
+ // src/auth/security/lockout.ts
2004
+ var DEFAULT_LOCKOUT_CONFIG = {
2005
+ maxAttempts: 5,
2006
+ lockDuration: 9e5,
2007
+ notifyUser: true,
2008
+ notifyAdmin: true,
2009
+ adminNotifyAfter: 3
2010
+ };
2011
+ var AccountLockout = class {
2012
+ redis;
2013
+ prefix;
2014
+ config;
2015
+ constructor(redis, config = {}, prefix = "kyro:lockout:") {
2016
+ this.redis = redis;
2017
+ this.prefix = prefix;
2018
+ this.config = { ...DEFAULT_LOCKOUT_CONFIG, ...config };
2019
+ }
2020
+ lockKey(userId) {
2021
+ return `${this.prefix}${userId}`;
2022
+ }
2023
+ historyKey(userId) {
2024
+ return `${this.prefix}${userId}:history`;
2025
+ }
2026
+ async checkLockout(userId) {
2027
+ const key = this.lockKey(userId);
2028
+ const data = await this.redis.hgetall(key);
2029
+ if (!data || Object.keys(data).length === 0) {
2030
+ return {
2031
+ locked: false,
2032
+ attemptsRemaining: this.config.maxAttempts,
2033
+ totalAttempts: 0
2034
+ };
2035
+ }
2036
+ const attempts = parseInt(data.attempts, 10);
2037
+ const lockedUntil = data.lockedUntil ? new Date(parseInt(data.lockedUntil, 10)) : void 0;
2038
+ if (lockedUntil && lockedUntil > /* @__PURE__ */ new Date()) {
2039
+ return {
2040
+ locked: true,
2041
+ attemptsRemaining: 0,
2042
+ lockedUntil,
2043
+ totalAttempts: attempts
2044
+ };
2045
+ }
2046
+ if (lockedUntil && lockedUntil <= /* @__PURE__ */ new Date()) {
2047
+ await this.unlockAccount(userId);
2048
+ return {
2049
+ locked: false,
2050
+ attemptsRemaining: this.config.maxAttempts,
2051
+ totalAttempts: 0
2052
+ };
2053
+ }
2054
+ return {
2055
+ locked: false,
2056
+ attemptsRemaining: Math.max(0, this.config.maxAttempts - attempts),
2057
+ totalAttempts: attempts
2058
+ };
2059
+ }
2060
+ async recordFailedAttempt(userId) {
2061
+ const key = this.lockKey(userId);
2062
+ const historyKey = this.historyKey(userId);
2063
+ const now = Date.now();
2064
+ const current = await this.redis.hincrby(key, "attempts", 1);
2065
+ await this.redis.hset(key, "lastAttempt", now.toString());
2066
+ await this.redis.lpush(historyKey, now.toString());
2067
+ await this.redis.ltrim(historyKey, 0, 99);
2068
+ if (current >= this.config.maxAttempts) {
2069
+ const lockedUntil = new Date(now + this.config.lockDuration);
2070
+ await this.redis.hset(key, {
2071
+ lockedAt: now.toString(),
2072
+ lockedUntil: lockedUntil.getTime().toString()
2073
+ });
2074
+ await this.redis.expire(
2075
+ key,
2076
+ Math.ceil(this.config.lockDuration / 1e3) + 3600
2077
+ );
2078
+ return {
2079
+ locked: true,
2080
+ attemptsRemaining: 0,
2081
+ lockedUntil,
2082
+ totalAttempts: current
2083
+ };
2084
+ }
2085
+ return {
2086
+ locked: false,
2087
+ attemptsRemaining: Math.max(0, this.config.maxAttempts - current),
2088
+ totalAttempts: current
2089
+ };
2090
+ }
2091
+ async lockAccount(userId, duration) {
2092
+ const key = this.lockKey(userId);
2093
+ const now = Date.now();
2094
+ const lockDuration = duration || this.config.lockDuration;
2095
+ const lockedUntil = new Date(now + lockDuration);
2096
+ const pipeline = this.redis.pipeline();
2097
+ pipeline.hset(key, {
2098
+ attempts: this.config.maxAttempts.toString(),
2099
+ lockedAt: now.toString(),
2100
+ lockedUntil: lockedUntil.getTime().toString()
2101
+ });
2102
+ pipeline.expire(key, Math.ceil(lockDuration / 1e3) + 3600);
2103
+ await pipeline.exec();
2104
+ }
2105
+ async unlockAccount(userId) {
2106
+ const key = this.lockKey(userId);
2107
+ await this.redis.del(key);
2108
+ }
2109
+ async resetAttempts(userId) {
2110
+ const key = this.lockKey(userId);
2111
+ const data = await this.redis.hgetall(key);
2112
+ if (data.lockedAt) {
2113
+ await this.redis.hset(key, {
2114
+ attempts: "0",
2115
+ lockedAt: "",
2116
+ lockedUntil: ""
2117
+ });
2118
+ } else {
2119
+ await this.redis.del(key);
2120
+ }
2121
+ }
2122
+ async getLockoutHistory(userId, limit = 10) {
2123
+ const historyKey = this.historyKey(userId);
2124
+ const timestamps = await this.redis.lrange(historyKey, 0, limit - 1);
2125
+ return timestamps.map((ts) => new Date(parseInt(ts, 10)));
2126
+ }
2127
+ async getLockoutStats(userId) {
2128
+ const historyKey = this.historyKey(userId);
2129
+ const timestamps = await this.redis.lrange(historyKey, 0, -1);
2130
+ const lockouts = timestamps.filter((_, i) => {
2131
+ const attemptNum = i + 1;
2132
+ return attemptNum % this.config.maxAttempts === 0;
2133
+ }).length;
2134
+ const lastLockoutData = await this.redis.hget(
2135
+ this.lockKey(userId),
2136
+ "lockedAt"
2137
+ );
2138
+ return {
2139
+ totalFailedAttempts: timestamps.length,
2140
+ lockoutCount: lockouts,
2141
+ lastLockout: lastLockoutData ? new Date(parseInt(lastLockoutData, 10)) : null,
2142
+ averageAttemptsBeforeLockout: lockouts > 0 ? this.config.maxAttempts : 0
2143
+ };
2144
+ }
2145
+ shouldNotifyAdmin(currentAttempts) {
2146
+ return this.config.notifyAdmin && currentAttempts >= this.config.adminNotifyAfter;
2147
+ }
2148
+ getConfig() {
2149
+ return { ...this.config };
2150
+ }
2151
+ setConfig(config) {
2152
+ this.config = { ...this.config, ...config };
2153
+ }
2154
+ };
2155
+
2156
+ // src/auth/security/rate-limit.ts
2157
+ var DEFAULT_RATE_LIMITS = {
2158
+ "auth:login": { window: 9e5, max: 5 },
2159
+ "auth:register": { window: 36e5, max: 3 },
2160
+ "auth:forgot": { window: 36e5, max: 3 },
2161
+ "auth:reset": { window: 36e5, max: 5 },
2162
+ "auth:verify": { window: 36e5, max: 5 },
2163
+ "api:general": { window: 6e4, max: 100 },
2164
+ "api:authenticated": { window: 6e4, max: 200 }
2165
+ };
2166
+ var RateLimiter = class {
2167
+ redis;
2168
+ prefix;
2169
+ limits;
2170
+ userLimits;
2171
+ constructor(redis, limits, userLimits, prefix = "kyro:ratelimit:") {
2172
+ this.redis = redis;
2173
+ this.prefix = prefix;
2174
+ this.limits = { ...DEFAULT_RATE_LIMITS, ...limits };
2175
+ this.userLimits = userLimits || {
2176
+ "user:api": { window: 6e4, max: 500 },
2177
+ "user:write": { window: 36e5, max: 100 }
2178
+ };
2179
+ }
2180
+ getKey(type, identifier) {
2181
+ return `${this.prefix}${type}:${identifier}`;
2182
+ }
2183
+ async check(type, identifier) {
2184
+ const config = this.limits[type] || this.limits["api:general"];
2185
+ const key = this.getKey(type, identifier);
2186
+ const now = Date.now();
2187
+ const windowStart = now - config.window;
2188
+ const pipeline = this.redis.pipeline();
2189
+ pipeline.zremrangebyscore(key, 0, windowStart);
2190
+ pipeline.zcard(key);
2191
+ pipeline.zadd(key, now, `${now}:${Math.random()}`);
2192
+ pipeline.expire(key, Math.ceil(config.window / 1e3) + 1);
2193
+ const results = await pipeline.exec();
2194
+ const count = results?.[1]?.[1] || 0;
2195
+ if (count >= config.max) {
2196
+ const oldestTimestamp = await this.redis.zrange(key, 0, 0, "WITHSCORES");
2197
+ const resetAt = oldestTimestamp.length > 1 ? parseInt(oldestTimestamp[1], 10) + config.window : now + config.window;
2198
+ return {
2199
+ allowed: false,
2200
+ remaining: 0,
2201
+ resetAt,
2202
+ retryAfter: Math.ceil((resetAt - now) / 1e3)
2203
+ };
2204
+ }
2205
+ return {
2206
+ allowed: true,
2207
+ remaining: config.max - count - 1,
2208
+ resetAt: now + config.window
2209
+ };
2210
+ }
2211
+ async checkUser(type, userId, identifier) {
2212
+ const config = this.userLimits[type] || this.userLimits["user:api"];
2213
+ const key = this.getKey(`user:${type}:${userId}`, identifier);
2214
+ const now = Date.now();
2215
+ const windowStart = now - config.window;
2216
+ const pipeline = this.redis.pipeline();
2217
+ pipeline.zremrangebyscore(key, 0, windowStart);
2218
+ pipeline.zcard(key);
2219
+ pipeline.zadd(key, now, `${now}:${Math.random()}`);
2220
+ pipeline.expire(key, Math.ceil(config.window / 1e3) + 1);
2221
+ const results = await pipeline.exec();
2222
+ const count = results?.[1]?.[1] || 0;
2223
+ if (count >= config.max) {
2224
+ const oldestTimestamp = await this.redis.zrange(key, 0, 0, "WITHSCORES");
2225
+ const resetAt = oldestTimestamp.length > 1 ? parseInt(oldestTimestamp[1], 10) + config.window : now + config.window;
2226
+ return {
2227
+ allowed: false,
2228
+ remaining: 0,
2229
+ resetAt,
2230
+ retryAfter: Math.ceil((resetAt - now) / 1e3)
2231
+ };
2232
+ }
2233
+ return {
2234
+ allowed: true,
2235
+ remaining: config.max - count - 1,
2236
+ resetAt: now + config.window
2237
+ };
2238
+ }
2239
+ async reset(type, identifier) {
2240
+ const key = this.getKey(type, identifier);
2241
+ await this.redis.del(key);
2242
+ }
2243
+ async resetUser(type, userId, identifier) {
2244
+ const key = this.getKey(`user:${type}:${userId}`, identifier);
2245
+ await this.redis.del(key);
2246
+ }
2247
+ async getStatus(type, identifier) {
2248
+ const config = this.limits[type] || this.limits["api:general"];
2249
+ const key = this.getKey(type, identifier);
2250
+ const now = Date.now();
2251
+ const windowStart = now - config.window;
2252
+ await this.redis.zremrangebyscore(key, 0, windowStart);
2253
+ const count = await this.redis.zcard(key);
2254
+ return {
2255
+ count,
2256
+ limit: config.max,
2257
+ remaining: Math.max(0, config.max - count),
2258
+ resetAt: now + config.window
2259
+ };
2260
+ }
2261
+ setLimit(type, config) {
2262
+ this.limits[type] = config;
2263
+ }
2264
+ setUserLimit(type, config) {
2265
+ this.userLimits[type] = config;
2266
+ }
2267
+ };
2268
+ var AuditLogger = class {
2269
+ redis;
2270
+ prefix;
2271
+ retentionDays;
2272
+ constructor(redis, retentionDays = 30, prefix = "kyro:audit:") {
2273
+ this.redis = redis;
2274
+ this.prefix = prefix;
2275
+ this.retentionDays = retentionDays;
2276
+ }
2277
+ async log(data) {
2278
+ const id = crypto.randomBytes(16).toString("hex");
2279
+ const timestamp = /* @__PURE__ */ new Date();
2280
+ const log = {
2281
+ ...data,
2282
+ id,
2283
+ timestamp
2284
+ };
2285
+ const key = this.getKeyForDate(timestamp);
2286
+ const hashKey = `${this.prefix}log:${id}`;
2287
+ await this.redis.hset(hashKey, this.serializeLog(log));
2288
+ await this.redis.expire(hashKey, this.retentionDays * 24 * 60 * 60 + 3600);
2289
+ await this.redis.zadd(key, timestamp.getTime(), id);
2290
+ await this.redis.expire(key, this.retentionDays * 24 * 60 * 60 + 3600);
2291
+ const userIndex = data.userId ? `${this.prefix}user:${data.userId}` : null;
2292
+ if (userIndex) {
2293
+ await this.redis.zadd(userIndex, timestamp.getTime(), id);
2294
+ await this.redis.expire(
2295
+ userIndex,
2296
+ this.retentionDays * 24 * 60 * 60 + 3600
2297
+ );
2298
+ }
2299
+ return id;
2300
+ }
2301
+ async get(id) {
2302
+ const hashKey = `${this.prefix}log:${id}`;
2303
+ const data = await this.redis.hgetall(hashKey);
2304
+ if (!data || Object.keys(data).length === 0) {
2305
+ return null;
2306
+ }
2307
+ return this.deserializeLog(data);
2308
+ }
2309
+ async query(filter = {}) {
2310
+ const { limit = 50, offset = 0 } = filter;
2311
+ let keys = [];
2312
+ if (filter.userId) {
2313
+ keys.push(`${this.prefix}user:${filter.userId}`);
2314
+ } else if (filter.startDate || filter.endDate) {
2315
+ keys = this.getKeysForDateRange(filter.startDate, filter.endDate);
2316
+ } else {
2317
+ const now = /* @__PURE__ */ new Date();
2318
+ keys = this.getKeysForDateRange(
2319
+ new Date(now.getTime() - this.retentionDays * 24 * 60 * 60 * 1e3),
2320
+ now
2321
+ );
2322
+ }
2323
+ let idScores = [];
2324
+ for (const key of keys) {
2325
+ const items = await this.redis.zrange(key, 0, -1, "WITHSCORES");
2326
+ for (let i = 0; i < items.length; i += 2) {
2327
+ idScores.push([items[i], parseInt(items[i + 1], 10)]);
2328
+ }
2329
+ }
2330
+ idScores.sort((a, b) => b[1] - a[1]);
2331
+ const total = idScores.length;
2332
+ idScores = idScores.slice(offset, offset + limit);
2333
+ const logs = [];
2334
+ for (const [id] of idScores) {
2335
+ const log = await this.get(id);
2336
+ if (log) {
2337
+ if (this.matchesFilter(log, filter)) {
2338
+ logs.push(log);
2339
+ }
2340
+ }
2341
+ }
2342
+ return { logs, total };
2343
+ }
2344
+ async getRecent(limit = 50) {
2345
+ const logs = [];
2346
+ const now = /* @__PURE__ */ new Date();
2347
+ const keys = this.getKeysForDateRange(
2348
+ new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3),
2349
+ now
2350
+ );
2351
+ let allIds = [];
2352
+ for (const key of keys) {
2353
+ const items = await this.redis.zrange(key, 0, -1, "WITHSCORES");
2354
+ for (let i = 0; i < items.length; i += 2) {
2355
+ allIds.push([items[i], parseInt(items[i + 1], 10)]);
2356
+ }
2357
+ }
2358
+ allIds.sort((a, b) => b[1] - a[1]);
2359
+ for (const [id] of allIds.slice(0, limit)) {
2360
+ const log = await this.get(id);
2361
+ if (log) logs.push(log);
2362
+ }
2363
+ return logs;
2364
+ }
2365
+ async getUserActivity(userId, limit = 50) {
2366
+ const key = `${this.prefix}user:${userId}`;
2367
+ const ids = await this.redis.zrange(key, 0, limit - 1);
2368
+ const logs = [];
2369
+ for (const id of ids) {
2370
+ const log = await this.get(id);
2371
+ if (log) logs.push(log);
2372
+ }
2373
+ return logs;
2374
+ }
2375
+ async getStats(startDate, endDate) {
2376
+ const keys = this.getKeysForDateRange(
2377
+ startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3),
2378
+ endDate || /* @__PURE__ */ new Date()
2379
+ );
2380
+ const byAction = {};
2381
+ let totalEvents = 0;
2382
+ let failedLogins = 0;
2383
+ let successCount = 0;
2384
+ const uniqueUsers = /* @__PURE__ */ new Set();
2385
+ for (const key of keys) {
2386
+ const ids = await this.redis.zrange(key, 0, -1);
2387
+ for (const id of ids) {
2388
+ const log = await this.get(id);
2389
+ if (log) {
2390
+ totalEvents++;
2391
+ byAction[log.action] = (byAction[log.action] || 0) + 1;
2392
+ if (log.success) {
2393
+ successCount++;
2394
+ }
2395
+ if (log.action === "login_failed") {
2396
+ failedLogins++;
2397
+ }
2398
+ if (log.userId) {
2399
+ uniqueUsers.add(log.userId);
2400
+ }
2401
+ }
2402
+ }
2403
+ }
2404
+ return {
2405
+ totalEvents,
2406
+ byAction,
2407
+ successRate: totalEvents > 0 ? successCount / totalEvents : 1,
2408
+ failedLogins,
2409
+ uniqueUsers
2410
+ };
2411
+ }
2412
+ async cleanup() {
2413
+ const cutoff = Date.now() - this.retentionDays * 24 * 60 * 60 * 1e3;
2414
+ const keys = await this.redis.keys(`${this.prefix}date:*`);
2415
+ let deleted = 0;
2416
+ for (const key of keys) {
2417
+ const timestamp = await this.redis.zrangebyscore(key, 0, cutoff);
2418
+ for (const id of timestamp) {
2419
+ await this.redis.del(`${this.prefix}log:${id}`);
2420
+ deleted++;
2421
+ }
2422
+ await this.redis.zremrangebyscore(key, 0, cutoff);
2423
+ }
2424
+ return deleted;
2425
+ }
2426
+ getKeyForDate(date) {
2427
+ const year = date.getFullYear();
2428
+ const month = String(date.getMonth() + 1).padStart(2, "0");
2429
+ const day = String(date.getDate()).padStart(2, "0");
2430
+ return `${this.prefix}date:${year}-${month}-${day}`;
2431
+ }
2432
+ getKeysForDateRange(start, end) {
2433
+ const keys = [];
2434
+ const startDate = start || new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1e3);
2435
+ const endDate = end || /* @__PURE__ */ new Date();
2436
+ const current = new Date(startDate);
2437
+ while (current <= endDate) {
2438
+ keys.push(this.getKeyForDate(current));
2439
+ current.setDate(current.getDate() + 1);
2440
+ }
2441
+ return keys;
2442
+ }
2443
+ matchesFilter(log, filter) {
2444
+ if (filter.action) {
2445
+ const actions = Array.isArray(filter.action) ? filter.action : [filter.action];
2446
+ if (!actions.includes(log.action)) return false;
2447
+ }
2448
+ if (filter.resource && log.resource !== filter.resource) return false;
2449
+ if (filter.resourceId && log.resourceId !== filter.resourceId) return false;
2450
+ if (filter.success !== void 0 && log.success !== filter.success)
2451
+ return false;
2452
+ return true;
2453
+ }
2454
+ serializeLog(log) {
2455
+ const result = {
2456
+ id: log.id,
2457
+ timestamp: log.timestamp.toISOString(),
2458
+ action: log.action,
2459
+ resource: log.resource,
2460
+ success: log.success ? "1" : "0"
2461
+ };
2462
+ if (log.userId) result.userId = log.userId;
2463
+ if (log.userEmail) result.userEmail = log.userEmail;
2464
+ if (log.role) result.role = log.role;
2465
+ if (log.resourceId) result.resourceId = log.resourceId;
2466
+ if (log.ipAddress) result.ipAddress = log.ipAddress;
2467
+ if (log.userAgent) result.userAgent = log.userAgent;
2468
+ if (log.error) result.error = log.error;
2469
+ if (log.changes) result.changes = JSON.stringify(log.changes);
2470
+ if (log.metadata) result.metadata = JSON.stringify(log.metadata);
2471
+ return result;
2472
+ }
2473
+ deserializeLog(data) {
2474
+ return {
2475
+ id: data.id,
2476
+ timestamp: new Date(data.timestamp),
2477
+ action: data.action,
2478
+ userId: data.userId,
2479
+ userEmail: data.userEmail,
2480
+ role: data.role,
2481
+ resource: data.resource,
2482
+ resourceId: data.resourceId,
2483
+ ipAddress: data.ipAddress,
2484
+ userAgent: data.userAgent,
2485
+ success: data.success === "1",
2486
+ error: data.error,
2487
+ changes: data.changes ? JSON.parse(data.changes) : void 0,
2488
+ metadata: data.metadata ? JSON.parse(data.metadata) : void 0
2489
+ };
2490
+ }
2491
+ };
2492
+ function createAuditContext(req) {
2493
+ return {
2494
+ ipAddress: req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || "unknown",
2495
+ userAgent: req.headers.get("user-agent") || "unknown"
2496
+ };
2497
+ }
2498
+ function defaultExtractToken(req) {
2499
+ const authHeader = req.headers.get("Authorization");
2500
+ if (authHeader?.startsWith("Bearer ")) {
2501
+ return authHeader.slice(7);
2502
+ }
2503
+ const cookieHeader = req.headers.get("Cookie");
2504
+ if (cookieHeader) {
2505
+ const cookies = Object.fromEntries(
2506
+ cookieHeader.split("; ").map((c) => {
2507
+ const [key, ...val] = c.split("=");
2508
+ return [key.trim(), val.join("=")];
2509
+ })
2510
+ );
2511
+ return cookies["auth_token"] || null;
2512
+ }
2513
+ return null;
2514
+ }
2515
+ function generateToken(payload, secret, options = {}) {
2516
+ return jwt2__default.default.sign(payload, secret, {
2517
+ expiresIn: options.expiresIn || "24h",
2518
+ issuer: options.issuer,
2519
+ audience: options.audience
2520
+ });
2521
+ }
2522
+
2523
+ // src/api/rest/auth-routes.ts
2524
+ var AuthRoutes = class {
2525
+ redis;
2526
+ email;
2527
+ jwtSecret;
2528
+ jwtExpiresIn;
2529
+ jwtIssuer;
2530
+ jwtAudience;
2531
+ passwordPolicy;
2532
+ lockout;
2533
+ rateLimiter;
2534
+ auditLogger;
2535
+ baseUrl;
2536
+ emailVerificationRequired;
2537
+ constructor(config) {
2538
+ this.redis = config.redis;
2539
+ this.email = config.email;
2540
+ this.jwtSecret = config.jwtSecret;
2541
+ this.jwtExpiresIn = config.jwtExpiresIn || "24h";
2542
+ this.jwtIssuer = config.jwtIssuer;
2543
+ this.jwtAudience = config.jwtAudience;
2544
+ this.passwordPolicy = config.passwordPolicy || new chunkI4BORBXT_cjs.PasswordPolicy();
2545
+ this.lockout = config.lockout;
2546
+ this.rateLimiter = config.rateLimiter;
2547
+ this.auditLogger = config.auditLogger;
2548
+ this.baseUrl = config.baseUrl || "http://localhost:3000";
2549
+ this.emailVerificationRequired = config.emailVerificationRequired ?? true;
2550
+ }
2551
+ async register(req) {
2552
+ const { ipAddress, userAgent } = createAuditContext(req);
2553
+ if (this.rateLimiter) {
2554
+ const limit = await this.rateLimiter.check("auth:register", ipAddress);
2555
+ if (!limit.allowed) {
2556
+ return this.rateLimitResponse(limit);
2557
+ }
2558
+ }
2559
+ try {
2560
+ const body = await req.json();
2561
+ if (!body.email || !body.password) {
2562
+ return this.errorResponse("Email and password are required", 400);
2563
+ }
2564
+ if (body.password !== body.confirmPassword) {
2565
+ return this.errorResponse("Passwords do not match", 400);
2566
+ }
2567
+ const passwordValidation = this.passwordPolicy.validate(body.password);
2568
+ if (!passwordValidation.valid) {
2569
+ return this.errorResponse(passwordValidation.errors.join(". "), 400);
2570
+ }
2571
+ const existingUser = await this.redis.findUserByEmail(body.email);
2572
+ if (existingUser) {
2573
+ return this.errorResponse("Email already registered", 400);
2574
+ }
2575
+ const passwordHash = await this.redis.hashPassword(body.password);
2576
+ const user = await this.redis.createUser({
2577
+ email: body.email,
2578
+ passwordHash,
2579
+ role: body.role || "customer",
2580
+ tenantId: body.tenantId
2581
+ });
2582
+ if (this.emailVerificationRequired && this.email) {
2583
+ const verificationToken = crypto.randomBytes(32).toString("hex");
2584
+ const verificationUrl = `${this.baseUrl}/api/auth/verify?token=${verificationToken}`;
2585
+ await this.redis.createSession(user.id, { ipAddress, userAgent });
2586
+ const template = this.email.getTemplates().verifyEmail(verificationUrl, body.email);
2587
+ await this.email.send({ to: body.email, ...template });
2588
+ }
2589
+ if (this.auditLogger) {
2590
+ await this.auditLogger.log({
2591
+ action: "register",
2592
+ userId: user.id,
2593
+ userEmail: user.email,
2594
+ resource: "auth",
2595
+ ipAddress,
2596
+ userAgent,
2597
+ success: true
2598
+ });
2599
+ }
2600
+ return this.jsonResponse(
2601
+ {
2602
+ success: true,
2603
+ message: "Registration successful",
2604
+ user: this.sanitizeUser(user),
2605
+ requiresVerification: this.emailVerificationRequired && !!this.email
2606
+ },
2607
+ 201
2608
+ );
2609
+ } catch (error) {
2610
+ return this.errorResponse("Registration failed", 500);
2611
+ }
2612
+ }
2613
+ async login(req) {
2614
+ const { ipAddress, userAgent } = createAuditContext(req);
2615
+ if (this.rateLimiter) {
2616
+ const limit = await this.rateLimiter.check("auth:login", ipAddress);
2617
+ if (!limit.allowed) {
2618
+ return this.rateLimitResponse(limit);
2619
+ }
2620
+ }
2621
+ try {
2622
+ const body = await req.json();
2623
+ if (!body.email || !body.password) {
2624
+ return this.errorResponse("Email and password are required", 400);
2625
+ }
2626
+ const user = await this.redis.findUserByEmail(body.email);
2627
+ if (!user) {
2628
+ await this.recordFailedLogin(ipAddress, userAgent);
2629
+ return this.errorResponse("Invalid credentials", 401);
2630
+ }
2631
+ if (this.lockout) {
2632
+ const lockoutStatus = await this.lockout.checkLockout(user.id);
2633
+ if (lockoutStatus.locked) {
2634
+ if (this.auditLogger) {
2635
+ await this.auditLogger.log({
2636
+ action: "login_failed",
2637
+ userId: user.id,
2638
+ userEmail: user.email,
2639
+ resource: "auth",
2640
+ ipAddress,
2641
+ userAgent,
2642
+ success: false,
2643
+ error: "Account locked"
2644
+ });
2645
+ }
2646
+ return this.errorResponse(
2647
+ `Account locked. Try again in ${Math.ceil((lockoutStatus.lockedUntil.getTime() - Date.now()) / 6e4)} minutes`,
2648
+ 423
2649
+ );
2650
+ }
2651
+ }
2652
+ const validPassword = user.passwordHash ? await this.redis.verifyPassword(body.password, user.passwordHash) : false;
2653
+ if (!validPassword) {
2654
+ await this.recordFailedLogin(ipAddress, userAgent, user.id, user.email);
2655
+ return this.errorResponse("Invalid credentials", 401);
2656
+ }
2657
+ if (this.lockout) {
2658
+ await this.lockout.resetAttempts(user.id);
2659
+ }
2660
+ const session = await this.redis.createSession(user.id, {
2661
+ ipAddress,
2662
+ userAgent
2663
+ });
2664
+ const payload = {
2665
+ sub: user.id,
2666
+ email: user.email,
2667
+ role: user.role,
2668
+ tenantId: user.tenantId,
2669
+ iat: Math.floor(Date.now() / 1e3),
2670
+ exp: Math.floor(Date.now() / 1e3) + 86400
2671
+ };
2672
+ const accessToken = generateToken(payload, this.jwtSecret, {
2673
+ expiresIn: this.jwtExpiresIn,
2674
+ issuer: this.jwtIssuer,
2675
+ audience: this.jwtAudience
2676
+ });
2677
+ if (this.auditLogger) {
2678
+ await this.auditLogger.log({
2679
+ action: "login",
2680
+ userId: user.id,
2681
+ userEmail: user.email,
2682
+ role: user.role,
2683
+ resource: "auth",
2684
+ ipAddress,
2685
+ userAgent,
2686
+ success: true
2687
+ });
2688
+ }
2689
+ await this.redis.updateUser(user.id, {
2690
+ lastLogin: (/* @__PURE__ */ new Date()).toISOString()
2691
+ });
2692
+ return this.jsonResponse({
2693
+ success: true,
2694
+ user: this.sanitizeUser(user),
2695
+ accessToken,
2696
+ refreshToken: session.refreshToken,
2697
+ expiresIn: this.jwtExpiresIn
2698
+ });
2699
+ } catch (error) {
2700
+ return this.errorResponse("Login failed", 500);
2701
+ }
2702
+ }
2703
+ async logout(req) {
2704
+ const token = defaultExtractToken(req);
2705
+ if (!token) {
2706
+ return this.errorResponse("No session to logout", 401);
2707
+ }
2708
+ const { ipAddress, userAgent } = createAuditContext(req);
2709
+ try {
2710
+ const payload = jwt2__default.default.decode(token);
2711
+ if (payload && payload.sub) {
2712
+ await this.redis.deleteUserSessions(payload.sub);
2713
+ if (this.auditLogger) {
2714
+ await this.auditLogger.log({
2715
+ action: "logout",
2716
+ userId: payload.sub,
2717
+ userEmail: payload.email,
2718
+ resource: "auth",
2719
+ ipAddress,
2720
+ userAgent,
2721
+ success: true
2722
+ });
2723
+ }
2724
+ }
2725
+ return this.jsonResponse({
2726
+ success: true,
2727
+ message: "Logged out successfully"
2728
+ });
2729
+ } catch (error) {
2730
+ return this.errorResponse("Logout failed", 500);
2731
+ }
2732
+ }
2733
+ async refresh(req) {
2734
+ try {
2735
+ const body = await req.json();
2736
+ const { refreshToken } = body;
2737
+ if (!refreshToken) {
2738
+ return this.errorResponse("Refresh token required", 400);
2739
+ }
2740
+ return this.jsonResponse({ success: true, accessToken: "" });
2741
+ } catch (error) {
2742
+ return this.errorResponse("Token refresh failed", 500);
2743
+ }
2744
+ }
2745
+ async me(req) {
2746
+ const token = defaultExtractToken(req);
2747
+ if (!token) {
2748
+ return this.errorResponse("Not authenticated", 401);
2749
+ }
2750
+ try {
2751
+ const payload = jwt2__default.default.verify(token, this.jwtSecret, {
2752
+ issuer: this.jwtIssuer,
2753
+ audience: this.jwtAudience
2754
+ });
2755
+ const user = await this.redis.findUserById(payload.sub);
2756
+ if (!user) {
2757
+ return this.errorResponse("User not found", 404);
2758
+ }
2759
+ return this.jsonResponse({
2760
+ success: true,
2761
+ user: this.sanitizeUser(user)
2762
+ });
2763
+ } catch (error) {
2764
+ return this.errorResponse("Authentication failed", 401);
2765
+ }
2766
+ }
2767
+ async changePassword(req) {
2768
+ const token = defaultExtractToken(req);
2769
+ if (!token) {
2770
+ return this.errorResponse("Not authenticated", 401);
2771
+ }
2772
+ const { ipAddress, userAgent } = createAuditContext(req);
2773
+ try {
2774
+ const payload = jwt2__default.default.verify(token, this.jwtSecret);
2775
+ const body = await req.json();
2776
+ const { currentPassword, newPassword, confirmPassword } = body;
2777
+ if (!currentPassword || !newPassword) {
2778
+ return this.errorResponse("Current and new password required", 400);
2779
+ }
2780
+ if (newPassword !== confirmPassword) {
2781
+ return this.errorResponse("Passwords do not match", 400);
2782
+ }
2783
+ const passwordValidation = this.passwordPolicy.validate(newPassword);
2784
+ if (!passwordValidation.valid) {
2785
+ return this.errorResponse(passwordValidation.errors.join(". "), 400);
2786
+ }
2787
+ const user = await this.redis.findUserById(payload.sub);
2788
+ if (!user) {
2789
+ return this.errorResponse("User not found", 404);
2790
+ }
2791
+ const validPassword = user.passwordHash ? await this.redis.verifyPassword(currentPassword, user.passwordHash) : false;
2792
+ if (!validPassword) {
2793
+ return this.errorResponse("Current password is incorrect", 401);
2794
+ }
2795
+ const passwordHistory = await this.redis.getPasswordHistory(user.id, 5);
2796
+ const isReused = await this.redis.isPasswordInHistory(
2797
+ newPassword,
2798
+ user.id,
2799
+ 5
2800
+ );
2801
+ if (isReused) {
2802
+ return this.errorResponse(
2803
+ "Password was recently used. Please choose a different password",
2804
+ 400
2805
+ );
2806
+ }
2807
+ const newPasswordHash = await this.redis.hashPassword(newPassword);
2808
+ if (user.passwordHash) {
2809
+ await this.redis.addPasswordToHistory(user.id, user.passwordHash);
2810
+ }
2811
+ await this.redis.updateUser(user.id, { passwordHash: newPasswordHash });
2812
+ await this.redis.deleteUserSessions(user.id);
2813
+ if (this.email && this.email.getTemplates) {
2814
+ const template = this.email.getTemplates().passwordChanged(user.email);
2815
+ await this.email.send({ to: user.email, ...template });
2816
+ }
2817
+ if (this.auditLogger) {
2818
+ await this.auditLogger.log({
2819
+ action: "password_change",
2820
+ userId: user.id,
2821
+ userEmail: user.email,
2822
+ resource: "auth",
2823
+ ipAddress,
2824
+ userAgent,
2825
+ success: true
2826
+ });
2827
+ }
2828
+ return this.jsonResponse({
2829
+ success: true,
2830
+ message: "Password changed successfully"
2831
+ });
2832
+ } catch (error) {
2833
+ return this.errorResponse("Password change failed", 500);
2834
+ }
2835
+ }
2836
+ async forgotPassword(req) {
2837
+ const { ipAddress, userAgent } = createAuditContext(req);
2838
+ if (this.rateLimiter) {
2839
+ const limit = await this.rateLimiter.check("auth:forgot", ipAddress);
2840
+ if (!limit.allowed) {
2841
+ return this.rateLimitResponse(limit);
2842
+ }
2843
+ }
2844
+ try {
2845
+ const body = await req.json();
2846
+ const { email } = body;
2847
+ if (!email) {
2848
+ return this.errorResponse("Email required", 400);
2849
+ }
2850
+ const user = await this.redis.findUserByEmail(email);
2851
+ if (!user) {
2852
+ return this.jsonResponse({
2853
+ success: true,
2854
+ message: "If the email exists, a reset link has been sent"
2855
+ });
2856
+ }
2857
+ if (this.email) {
2858
+ const resetToken = crypto.randomBytes(32).toString("hex");
2859
+ const resetUrl = `${this.baseUrl}/api/auth/reset-password?token=${resetToken}`;
2860
+ const template = this.email.getTemplates().resetPassword(resetUrl, user.email);
2861
+ await this.email.send({ to: user.email, ...template });
2862
+ }
2863
+ if (this.auditLogger) {
2864
+ await this.auditLogger.log({
2865
+ action: "password_reset_request",
2866
+ userId: user.id,
2867
+ userEmail: user.email,
2868
+ resource: "auth",
2869
+ ipAddress,
2870
+ userAgent,
2871
+ success: true
2872
+ });
2873
+ }
2874
+ return this.jsonResponse({
2875
+ success: true,
2876
+ message: "If the email exists, a reset link has been sent"
2877
+ });
2878
+ } catch (error) {
2879
+ return this.errorResponse("Password reset request failed", 500);
2880
+ }
2881
+ }
2882
+ async verifyEmail(req) {
2883
+ const url = new URL(req.url);
2884
+ const token = url.searchParams.get("token");
2885
+ if (!token) {
2886
+ return this.errorResponse("Verification token required", 400);
2887
+ }
2888
+ try {
2889
+ return this.jsonResponse({ success: true, message: "Email verified" });
2890
+ } catch (error) {
2891
+ return this.errorResponse("Email verification failed", 500);
2892
+ }
2893
+ }
2894
+ async recordFailedLogin(ipAddress, userAgent, userId, userEmail) {
2895
+ if (this.lockout) {
2896
+ await this.lockout.recordFailedAttempt(userId || ipAddress);
2897
+ }
2898
+ if (this.auditLogger) {
2899
+ await this.auditLogger.log({
2900
+ action: "login_failed",
2901
+ userId,
2902
+ userEmail,
2903
+ resource: "auth",
2904
+ ipAddress,
2905
+ userAgent,
2906
+ success: false,
2907
+ error: "Invalid credentials"
2908
+ });
2909
+ }
2910
+ }
2911
+ sanitizeUser(user) {
2912
+ const { passwordHash, ...sanitized } = user;
2913
+ return sanitized;
2914
+ }
2915
+ jsonResponse(data, status = 200) {
2916
+ return new Response(JSON.stringify(data), {
2917
+ status,
2918
+ headers: {
2919
+ "Content-Type": "application/json"
2920
+ }
2921
+ });
2922
+ }
2923
+ errorResponse(message, status) {
2924
+ return new Response(JSON.stringify({ success: false, error: message }), {
2925
+ status,
2926
+ headers: {
2927
+ "Content-Type": "application/json"
2928
+ }
2929
+ });
2930
+ }
2931
+ rateLimitResponse(limit) {
2932
+ return new Response(
2933
+ JSON.stringify({
2934
+ success: false,
2935
+ error: "Too many requests",
2936
+ retryAfter: limit.retryAfter
2937
+ }),
2938
+ {
2939
+ status: 429,
2940
+ headers: {
2941
+ "Content-Type": "application/json",
2942
+ "Retry-After": String(limit.retryAfter || 60)
2943
+ }
2944
+ }
2945
+ );
2946
+ }
2947
+ };
2948
+
2949
+ // src/auth/config.ts
2950
+ function getEnv(key, fallback = "") {
2951
+ return process.env[key] || fallback;
2952
+ }
2953
+ function getEnvBool(key, fallback = false) {
2954
+ const val = process.env[key];
2955
+ if (!val) return fallback;
2956
+ return val.toLowerCase() === "true";
2957
+ }
2958
+ function getEnvNum(key, fallback = 0) {
2959
+ const val = process.env[key];
2960
+ if (!val) return fallback;
2961
+ return parseInt(val, 10);
2962
+ }
2963
+ async function createAuthConfig() {
2964
+ const redisUrl = getEnv("REDIS_URL", "redis://localhost:6379");
2965
+ const redisKeyPrefix = getEnv("REDIS_KEY_PREFIX", "kyro:auth:");
2966
+ const redisSessionTTL = getEnvNum("REDIS_SESSION_TTL", 86400);
2967
+ const redisRefreshTTL = getEnvNum("REDIS_REFRESH_TOKEN_TTL", 604800);
2968
+ const redisAdapter = new chunkI4BORBXT_cjs.RedisAuthAdapter({
2969
+ url: redisUrl,
2970
+ keyPrefix: redisKeyPrefix,
2971
+ tokenExpiration: redisSessionTTL,
2972
+ refreshTokenExpiration: redisRefreshTTL,
2973
+ tls: getEnvBool("REDIS_TLS", false)
2974
+ });
2975
+ await redisAdapter.connect();
2976
+ const redisClient = redisAdapter.redis;
2977
+ const emailConfig = getEmailConfig();
2978
+ const email = emailConfig ? new chunkI4BORBXT_cjs.EmailTransport(emailConfig) : void 0;
2979
+ const passwordPolicy = new chunkI4BORBXT_cjs.PasswordPolicy({
2980
+ minLength: getEnvNum("PASSWORD_MIN_LENGTH", 12),
2981
+ requireUppercase: getEnvBool("PASSWORD_REQUIRE_UPPERCASE", true),
2982
+ requireLowercase: getEnvBool("PASSWORD_REQUIRE_LOWERCASE", true),
2983
+ requireNumbers: getEnvBool("PASSWORD_REQUIRE_NUMBERS", true),
2984
+ requireSpecialChars: getEnvBool("PASSWORD_REQUIRE_SPECIAL", true),
2985
+ preventReuse: getEnvNum("PASSWORD_PREVENT_REUSE", 5),
2986
+ maxLength: getEnvNum("PASSWORD_MAX_LENGTH", 128)
2987
+ });
2988
+ let lockout;
2989
+ if (getEnvBool("LOCKOUT_ENABLED", true)) {
2990
+ lockout = new AccountLockout(redisClient, {
2991
+ maxAttempts: getEnvNum("LOCKOUT_MAX_ATTEMPTS", 5),
2992
+ lockDuration: getEnvNum("LOCKOUT_DURATION_MINUTES", 15) * 60 * 1e3
2993
+ });
2994
+ }
2995
+ let rateLimiter;
2996
+ if (getEnvBool("RATE_LIMIT_ENABLED", true)) {
2997
+ rateLimiter = new RateLimiter(redisClient, {
2998
+ "auth:login": {
2999
+ window: getEnvNum("RATE_LIMIT_AUTH_WINDOW_MS", 9e5),
3000
+ max: getEnvNum("RATE_LIMIT_AUTH_MAX_REQUESTS", 10)
3001
+ },
3002
+ "api:general": {
3003
+ window: getEnvNum("RATE_LIMIT_WINDOW_MS", 6e4),
3004
+ max: getEnvNum("RATE_LIMIT_MAX_REQUESTS", 100)
3005
+ }
3006
+ });
3007
+ }
3008
+ let auditLogger;
3009
+ if (getEnvBool("AUDIT_LOG_ENABLED", true)) {
3010
+ auditLogger = new AuditLogger(
3011
+ redisClient,
3012
+ getEnvNum("AUDIT_LOG_RETENTION_DAYS", 30)
3013
+ );
3014
+ }
3015
+ const routes = new AuthRoutes({
3016
+ redis: redisAdapter,
3017
+ email,
3018
+ jwtSecret: getEnv("JWT_SECRET", "change-me"),
3019
+ jwtExpiresIn: getEnv("JWT_EXPIRES_IN", "24h"),
3020
+ jwtIssuer: getEnv("JWT_ISSUER", "kyro-cms"),
3021
+ jwtAudience: getEnv("JWT_AUDIENCE", "kyro-cms-client"),
3022
+ passwordPolicy,
3023
+ lockout,
3024
+ rateLimiter,
3025
+ auditLogger,
3026
+ baseUrl: getEnv("EMAIL_BASE_URL", "http://localhost:4321"),
3027
+ emailVerificationRequired: getEnvBool("EMAIL_VERIFICATION_REQUIRED", true)
3028
+ });
3029
+ return {
3030
+ redis: redisAdapter,
3031
+ redisClient,
3032
+ email,
3033
+ passwordPolicy,
3034
+ lockout,
3035
+ rateLimiter,
3036
+ auditLogger,
3037
+ routes
3038
+ };
3039
+ }
3040
+ function getEmailConfig() {
3041
+ const host = getEnv("SMTP_HOST");
3042
+ if (!host) return void 0;
3043
+ return {
3044
+ host,
3045
+ port: getEnvNum("SMTP_PORT", 587),
3046
+ secure: getEnvBool("SMTP_SECURE", false),
3047
+ auth: {
3048
+ user: getEnv("SMTP_USER"),
3049
+ pass: getEnv("SMTP_PASS")
3050
+ },
3051
+ from: getEnv("SMTP_FROM", "noreply@example.com"),
3052
+ fromName: getEnv("SMTP_FROM_NAME", "Kyro CMS")
3053
+ };
3054
+ }
3055
+ var authConfig = createAuthConfig();
3056
+
3057
+ // src/auth/index.ts
1996
3058
  var DEFAULT_SALT_ROUNDS = 12;
1997
3059
  var DEFAULT_EXPIRES_IN = "24h";
1998
3060
  var DEFAULT_REFRESH_EXPIRES_IN = "7d";
@@ -2021,7 +3083,7 @@ var Auth = class {
2021
3083
  email: data.email,
2022
3084
  passwordHash,
2023
3085
  role: data.role ?? "customer",
2024
- tenant: data.tenant
3086
+ tenantId: data.tenantId
2025
3087
  });
2026
3088
  return this.createSessionForUser(user);
2027
3089
  } catch (error) {
@@ -2052,7 +3114,7 @@ var Auth = class {
2052
3114
  async refreshToken(refreshToken) {
2053
3115
  try {
2054
3116
  const session = await this.adapter.findSessionByToken(refreshToken);
2055
- if (!session || session.expiresAt < /* @__PURE__ */ new Date()) {
3117
+ if (!session || new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
2056
3118
  return { success: false, error: "Invalid or expired refresh token" };
2057
3119
  }
2058
3120
  const user = await this.adapter.findUserById(session.userId);
@@ -2067,7 +3129,7 @@ var Auth = class {
2067
3129
  }
2068
3130
  async verifyToken(token) {
2069
3131
  try {
2070
- const decoded = jwt__default.default.verify(token, this.config.secret, {
3132
+ const decoded = jwt2__default.default.verify(token, this.config.secret, {
2071
3133
  issuer: this.config.issuer,
2072
3134
  audience: this.config.audience.length > 0 ? this.config.audience[0] : void 0
2073
3135
  });
@@ -2131,9 +3193,7 @@ var Auth = class {
2131
3193
  }
2132
3194
  async createSessionForUser(user) {
2133
3195
  const token = this.generateToken(user);
2134
- const refreshToken = crypto.randomUUID();
2135
- const expiresAt = new Date(Date.now() + this.parseExpiresIn(this.config.refreshExpiresIn));
2136
- const session = await this.adapter.createSession(user.id, refreshToken, expiresAt);
3196
+ const session = await this.adapter.createSession(user.id);
2137
3197
  return {
2138
3198
  success: true,
2139
3199
  user,
@@ -2146,7 +3206,7 @@ var Auth = class {
2146
3206
  sub: user.id,
2147
3207
  email: user.email,
2148
3208
  role: user.role,
2149
- tenant: user.tenant
3209
+ tenantId: user.tenantId
2150
3210
  };
2151
3211
  const signOptions = {
2152
3212
  expiresIn: this.parseExpiresIn(this.config.expiresIn) / 1e3,
@@ -2155,7 +3215,7 @@ var Auth = class {
2155
3215
  if (this.config.audience.length > 0) {
2156
3216
  signOptions.audience = this.config.audience[0];
2157
3217
  }
2158
- return jwt__default.default.sign(payload, this.config.secret, signOptions);
3218
+ return jwt2__default.default.sign(payload, this.config.secret, signOptions);
2159
3219
  }
2160
3220
  async hashPassword(password) {
2161
3221
  return bcrypt__default.default.hash(password, this.config.saltRounds);
@@ -2330,6 +3390,103 @@ function isArchived(status) {
2330
3390
  return status === "archived";
2331
3391
  }
2332
3392
 
3393
+ // src/registry/config.ts
3394
+ function normalizeCollections(collections) {
3395
+ if (!collections) return [];
3396
+ if (Array.isArray(collections)) return collections;
3397
+ return Object.values(collections);
3398
+ }
3399
+ function normalizeGlobals(globals) {
3400
+ if (!globals) return [];
3401
+ if (Array.isArray(globals)) return globals;
3402
+ return Object.values(globals);
3403
+ }
3404
+ function defineConfig(config) {
3405
+ return {
3406
+ collections: normalizeCollections(config.collections),
3407
+ globals: normalizeGlobals(config.globals),
3408
+ adapter: config.adapter,
3409
+ plugins: config.plugins,
3410
+ auth: config.auth,
3411
+ cors: config.cors,
3412
+ admin: config.admin,
3413
+ upload: config.upload,
3414
+ graphQL: config.graphQL,
3415
+ typescript: config.typescript,
3416
+ localization: config.localization,
3417
+ rateLimit: config.rateLimit,
3418
+ debug: config.debug
3419
+ };
3420
+ }
3421
+
3422
+ Object.defineProperty(exports, "allSettingsGlobals", {
3423
+ enumerable: true,
3424
+ get: function () { return chunkF5B64H5S_cjs.allSettingsGlobals; }
3425
+ });
3426
+ Object.defineProperty(exports, "blogCollections", {
3427
+ enumerable: true,
3428
+ get: function () { return chunkF5B64H5S_cjs.blogCollections; }
3429
+ });
3430
+ Object.defineProperty(exports, "blogGlobals", {
3431
+ enumerable: true,
3432
+ get: function () { return chunkF5B64H5S_cjs.blogGlobals; }
3433
+ });
3434
+ Object.defineProperty(exports, "coreSettingsGlobals", {
3435
+ enumerable: true,
3436
+ get: function () { return chunkF5B64H5S_cjs.coreSettingsGlobals; }
3437
+ });
3438
+ Object.defineProperty(exports, "createTemplateConfig", {
3439
+ enumerable: true,
3440
+ get: function () { return chunkF5B64H5S_cjs.createTemplateConfig; }
3441
+ });
3442
+ Object.defineProperty(exports, "ecommerceCollections", {
3443
+ enumerable: true,
3444
+ get: function () { return chunkF5B64H5S_cjs.ecommerceCollections; }
3445
+ });
3446
+ Object.defineProperty(exports, "ecommerceGlobals", {
3447
+ enumerable: true,
3448
+ get: function () { return chunkF5B64H5S_cjs.ecommerceGlobals; }
3449
+ });
3450
+ Object.defineProperty(exports, "ecommerceSettingsGlobals", {
3451
+ enumerable: true,
3452
+ get: function () { return chunkF5B64H5S_cjs.ecommerceSettingsGlobals; }
3453
+ });
3454
+ Object.defineProperty(exports, "kitchenSinkCollections", {
3455
+ enumerable: true,
3456
+ get: function () { return chunkF5B64H5S_cjs.kitchenSinkCollections; }
3457
+ });
3458
+ Object.defineProperty(exports, "mediaCollections", {
3459
+ enumerable: true,
3460
+ get: function () { return chunkF5B64H5S_cjs.mediaCollections; }
3461
+ });
3462
+ Object.defineProperty(exports, "minimalCollections", {
3463
+ enumerable: true,
3464
+ get: function () { return chunkF5B64H5S_cjs.minimalCollections; }
3465
+ });
3466
+ Object.defineProperty(exports, "EmailTransport", {
3467
+ enumerable: true,
3468
+ get: function () { return chunkI4BORBXT_cjs.EmailTransport; }
3469
+ });
3470
+ Object.defineProperty(exports, "PasswordPolicy", {
3471
+ enumerable: true,
3472
+ get: function () { return chunkI4BORBXT_cjs.PasswordPolicy; }
3473
+ });
3474
+ Object.defineProperty(exports, "RedisAuthAdapter", {
3475
+ enumerable: true,
3476
+ get: function () { return chunkI4BORBXT_cjs.RedisAuthAdapter; }
3477
+ });
3478
+ Object.defineProperty(exports, "autoBootstrap", {
3479
+ enumerable: true,
3480
+ get: function () { return chunkI4BORBXT_cjs.autoBootstrap; }
3481
+ });
3482
+ Object.defineProperty(exports, "bootstrapAdmin", {
3483
+ enumerable: true,
3484
+ get: function () { return chunkI4BORBXT_cjs.bootstrapAdmin; }
3485
+ });
3486
+ Object.defineProperty(exports, "getBootstrapFromEnv", {
3487
+ enumerable: true,
3488
+ get: function () { return chunkI4BORBXT_cjs.getBootstrapFromEnv; }
3489
+ });
2333
3490
  Object.defineProperty(exports, "createContext", {
2334
3491
  enumerable: true,
2335
3492
  get: function () { return chunk3VZCX4DF_cjs.createContext; }
@@ -2412,19 +3569,35 @@ Object.defineProperty(exports, "createWSServer", {
2412
3569
  });
2413
3570
  Object.defineProperty(exports, "DrizzleAdapter", {
2414
3571
  enumerable: true,
2415
- get: function () { return chunk3Q3FS5J4_cjs.DrizzleAdapter; }
3572
+ get: function () { return chunkV3B25QOK_cjs.DrizzleAdapter; }
2416
3573
  });
2417
3574
  Object.defineProperty(exports, "collectionToDrizzleSchema", {
2418
3575
  enumerable: true,
2419
- get: function () { return chunk3Q3FS5J4_cjs.collectionToDrizzleSchema; }
3576
+ get: function () { return chunkV3B25QOK_cjs.collectionToDrizzleSchema; }
2420
3577
  });
2421
3578
  Object.defineProperty(exports, "createDrizzleAdapter", {
2422
3579
  enumerable: true,
2423
- get: function () { return chunk3Q3FS5J4_cjs.createDrizzleAdapter; }
3580
+ get: function () { return chunkV3B25QOK_cjs.createDrizzleAdapter; }
2424
3581
  });
2425
3582
  Object.defineProperty(exports, "fieldToDrizzleType", {
2426
3583
  enumerable: true,
2427
- get: function () { return chunk3Q3FS5J4_cjs.fieldToDrizzleType; }
3584
+ get: function () { return chunkV3B25QOK_cjs.fieldToDrizzleType; }
3585
+ });
3586
+ Object.defineProperty(exports, "PostgresAuthAdapter", {
3587
+ enumerable: true,
3588
+ get: function () { return chunkKWTKEBHM_cjs.PostgresAuthAdapter; }
3589
+ });
3590
+ Object.defineProperty(exports, "createDatabase", {
3591
+ enumerable: true,
3592
+ get: function () { return chunkU4CHJTWX_cjs.createDatabase; }
3593
+ });
3594
+ Object.defineProperty(exports, "runMigrations", {
3595
+ enumerable: true,
3596
+ get: function () { return chunkU4CHJTWX_cjs.runMigrations; }
3597
+ });
3598
+ Object.defineProperty(exports, "seedDefaultRoles", {
3599
+ enumerable: true,
3600
+ get: function () { return chunkU4CHJTWX_cjs.seedDefaultRoles; }
2428
3601
  });
2429
3602
  Object.defineProperty(exports, "MongoDBAdapter", {
2430
3603
  enumerable: true,
@@ -2443,7 +3616,9 @@ Object.defineProperty(exports, "z", {
2443
3616
  get: function () { return zod.z; }
2444
3617
  });
2445
3618
  exports.ALL_FIELD_TYPES = ALL_FIELD_TYPES;
3619
+ exports.AccountLockout = AccountLockout;
2446
3620
  exports.AnalyticsPlugin = AnalyticsPlugin;
3621
+ exports.AuditLogger = AuditLogger;
2447
3622
  exports.Auth = Auth;
2448
3623
  exports.COMPLEX_FIELD_TYPES = COMPLEX_FIELD_TYPES;
2449
3624
  exports.CSSGenerator = CSSGenerator;
@@ -2456,17 +3631,21 @@ exports.LocalAdapter = LocalAdapter;
2456
3631
  exports.PRIMITIVE_FIELD_TYPES = PRIMITIVE_FIELD_TYPES;
2457
3632
  exports.PluginManager = PluginManager;
2458
3633
  exports.RELATIONAL_FIELD_TYPES = RELATIONAL_FIELD_TYPES;
3634
+ exports.RateLimiter = RateLimiter;
2459
3635
  exports.Registry = Registry;
2460
3636
  exports.ReviewsPlugin = ReviewsPlugin;
2461
3637
  exports.SEOPLugin = SEOPLugin;
2462
3638
  exports.VersionManager = VersionManager;
2463
3639
  exports.WishlistPlugin = WishlistPlugin;
3640
+ exports.authConfig = authConfig;
2464
3641
  exports.collectionToCreateZod = collectionToCreateZod;
2465
3642
  exports.collectionToUpdateZod = collectionToUpdateZod;
2466
3643
  exports.collectionToWhereZod = collectionToWhereZod;
2467
3644
  exports.collectionToZod = collectionToZod;
2468
3645
  exports.createAdminStyling = createAdminStyling;
3646
+ exports.createAuditContext = createAuditContext;
2469
3647
  exports.createAuth = createAuth;
3648
+ exports.createAuthConfig = createAuthConfig;
2470
3649
  exports.createKyro = createKyro;
2471
3650
  exports.createLocalAdapter = createLocalAdapter;
2472
3651
  exports.createRegistry = createRegistry;
@@ -2474,6 +3653,7 @@ exports.createVersionManager = createVersionManager;
2474
3653
  exports.defaultDarkTheme = defaultDarkTheme;
2475
3654
  exports.defaultFieldStyling = defaultFieldStyling;
2476
3655
  exports.defaultLightTheme = defaultLightTheme;
3656
+ exports.defineConfig = defineConfig;
2477
3657
  exports.ecommerce2026Theme = ecommerce2026Theme;
2478
3658
  exports.fieldToZod = fieldToZod;
2479
3659
  exports.generateCSSVariables = generateCSSVariables;