@kyro-cms/core 0.1.0 → 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.
- package/README.md +556 -130
- package/dist/bootstrap-2WJK6PG7.cjs +29 -0
- package/dist/bootstrap-2WJK6PG7.cjs.map +1 -0
- package/dist/bootstrap-Q2TWUQF3.js +4 -0
- package/dist/bootstrap-Q2TWUQF3.js.map +1 -0
- package/dist/chunk-3QX6KG2S.js +2125 -0
- package/dist/chunk-3QX6KG2S.js.map +1 -0
- package/dist/chunk-5AOILNGY.cjs +212 -0
- package/dist/chunk-5AOILNGY.cjs.map +1 -0
- package/dist/{chunk-DKSMFC3L.js → chunk-EINVJPFM.js} +2 -2
- package/dist/{chunk-DKSMFC3L.js.map → chunk-EINVJPFM.js.map} +1 -1
- package/dist/chunk-F5B64H5S.cjs +2149 -0
- package/dist/chunk-F5B64H5S.cjs.map +1 -0
- package/dist/chunk-I4BORBXT.cjs +914 -0
- package/dist/chunk-I4BORBXT.cjs.map +1 -0
- package/dist/chunk-KA3UOIFC.js +206 -0
- package/dist/chunk-KA3UOIFC.js.map +1 -0
- package/dist/chunk-KWTKEBHM.cjs +176 -0
- package/dist/chunk-KWTKEBHM.cjs.map +1 -0
- package/dist/chunk-M4JFHQ5J.js +170 -0
- package/dist/chunk-M4JFHQ5J.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/chunk-U4CHJTWX.cjs +94 -0
- package/dist/chunk-U4CHJTWX.cjs.map +1 -0
- package/dist/{chunk-3Q3FS5J4.cjs → chunk-V3B25QOK.cjs} +2 -2
- package/dist/{chunk-3Q3FS5J4.cjs.map → chunk-V3B25QOK.cjs.map} +1 -1
- package/dist/chunk-V67YXRBT.js +899 -0
- package/dist/chunk-V67YXRBT.js.map +1 -0
- package/dist/chunk-XLMVCGXA.js +86 -0
- package/dist/chunk-XLMVCGXA.js.map +1 -0
- package/dist/cli/index.cjs +106 -14
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +106 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/database-37KXWUER.js +5 -0
- package/dist/database-37KXWUER.js.map +1 -0
- package/dist/database-LJKD3HE4.cjs +22 -0
- package/dist/database-LJKD3HE4.cjs.map +1 -0
- package/dist/drizzle/index.cjs +25 -5
- package/dist/drizzle/index.d.cts +5 -49
- package/dist/drizzle/index.d.ts +5 -49
- package/dist/drizzle/index.js +5 -1
- package/dist/graphql/index.cjs +1 -0
- package/dist/graphql/index.js +1 -0
- package/dist/index-BVFlb7uU.d.ts +192 -0
- package/dist/index-CzkEHKqu.d.cts +192 -0
- package/dist/index.cjs +1203 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +382 -68
- package/dist/index.d.ts +382 -68
- package/dist/index.js +1110 -20
- package/dist/index.js.map +1 -1
- package/dist/mongodb/index.cjs +1 -0
- package/dist/mongodb/index.js +1 -0
- package/dist/postgres-auth-adapter-CYZAVPPP.cjs +14 -0
- package/dist/postgres-auth-adapter-CYZAVPPP.cjs.map +1 -0
- package/dist/postgres-auth-adapter-LTDUGBMB.js +5 -0
- package/dist/postgres-auth-adapter-LTDUGBMB.js.map +1 -0
- package/dist/rest/index.cjs +1 -0
- package/dist/rest/index.js +1 -0
- package/dist/templates/index.cjs +101 -0
- package/dist/templates/index.cjs.map +1 -0
- package/dist/templates/index.d.cts +55 -0
- package/dist/templates/index.d.ts +55 -0
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/trpc/index.cjs +1 -0
- package/dist/trpc/index.js +1 -0
- package/dist/ws/index.cjs +1 -0
- package/dist/ws/index.js +1 -0
- package/package.json +40 -6
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,19 @@ 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-
|
|
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
23
|
import bcrypt from 'bcrypt';
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
24
|
+
import jwt2 from 'jsonwebtoken';
|
|
25
|
+
import { randomBytes } from 'crypto';
|
|
19
26
|
|
|
20
27
|
// src/registry/validator.ts
|
|
21
28
|
var ConfigValidationError = class extends Error {
|
|
@@ -645,10 +652,6 @@ var Registry = class {
|
|
|
645
652
|
if (this.initialized) {
|
|
646
653
|
throw new Error("Cannot add collections after Registry has been initialized");
|
|
647
654
|
}
|
|
648
|
-
const errors = validateCollection(config);
|
|
649
|
-
if (errors.length > 0) {
|
|
650
|
-
throw new Error(`Invalid collection config: ${errors.join(", ")}`);
|
|
651
|
-
}
|
|
652
655
|
let finalConfig = { ...config };
|
|
653
656
|
for (const plugin of this.plugins) {
|
|
654
657
|
if (plugin.extendCollection) {
|
|
@@ -656,6 +659,10 @@ var Registry = class {
|
|
|
656
659
|
}
|
|
657
660
|
}
|
|
658
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
|
+
}
|
|
659
666
|
this.collections.set(finalConfig.slug, finalConfig);
|
|
660
667
|
this.clearSchemaCache(finalConfig.slug);
|
|
661
668
|
}
|
|
@@ -1143,13 +1150,13 @@ async function runFieldHooks(hooks, args) {
|
|
|
1143
1150
|
// src/database/local/adapter.ts
|
|
1144
1151
|
var LocalAdapter = class extends AbstractBaseAdapter {
|
|
1145
1152
|
db;
|
|
1153
|
+
path;
|
|
1146
1154
|
migrations = /* @__PURE__ */ new Map();
|
|
1147
1155
|
constructor(options) {
|
|
1148
1156
|
super();
|
|
1157
|
+
this.path = options.path;
|
|
1149
1158
|
if (options.db) {
|
|
1150
1159
|
this.db = options.db;
|
|
1151
|
-
} else if (options.path) {
|
|
1152
|
-
this.db = null;
|
|
1153
1160
|
} else {
|
|
1154
1161
|
this.db = null;
|
|
1155
1162
|
}
|
|
@@ -1157,12 +1164,12 @@ var LocalAdapter = class extends AbstractBaseAdapter {
|
|
|
1157
1164
|
async connect() {
|
|
1158
1165
|
if (!this.db) {
|
|
1159
1166
|
const Database = (await import('better-sqlite3')).default;
|
|
1160
|
-
this.db = new Database(":memory:");
|
|
1167
|
+
this.db = new Database(this.path || ":memory:");
|
|
1161
1168
|
}
|
|
1162
1169
|
this.db.pragma("journal_mode = WAL");
|
|
1163
1170
|
this.db.pragma("foreign_keys = ON");
|
|
1164
1171
|
this.connected = true;
|
|
1165
|
-
console.log(
|
|
1172
|
+
console.log(`[LocalAdapter] Connected to SQLite (${this.path || "memory"})`);
|
|
1166
1173
|
}
|
|
1167
1174
|
async disconnect() {
|
|
1168
1175
|
if (this.db) {
|
|
@@ -1992,6 +1999,1062 @@ var defaultFieldStyling = {
|
|
|
1992
1999
|
}
|
|
1993
2000
|
}
|
|
1994
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 = 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.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 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 = 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.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.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.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 = 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 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 EmailTransport(emailConfig) : void 0;
|
|
2979
|
+
const passwordPolicy = new 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
|
|
1995
3058
|
var DEFAULT_SALT_ROUNDS = 12;
|
|
1996
3059
|
var DEFAULT_EXPIRES_IN = "24h";
|
|
1997
3060
|
var DEFAULT_REFRESH_EXPIRES_IN = "7d";
|
|
@@ -2020,7 +3083,7 @@ var Auth = class {
|
|
|
2020
3083
|
email: data.email,
|
|
2021
3084
|
passwordHash,
|
|
2022
3085
|
role: data.role ?? "customer",
|
|
2023
|
-
|
|
3086
|
+
tenantId: data.tenantId
|
|
2024
3087
|
});
|
|
2025
3088
|
return this.createSessionForUser(user);
|
|
2026
3089
|
} catch (error) {
|
|
@@ -2051,7 +3114,7 @@ var Auth = class {
|
|
|
2051
3114
|
async refreshToken(refreshToken) {
|
|
2052
3115
|
try {
|
|
2053
3116
|
const session = await this.adapter.findSessionByToken(refreshToken);
|
|
2054
|
-
if (!session || session.expiresAt < /* @__PURE__ */ new Date()) {
|
|
3117
|
+
if (!session || new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
2055
3118
|
return { success: false, error: "Invalid or expired refresh token" };
|
|
2056
3119
|
}
|
|
2057
3120
|
const user = await this.adapter.findUserById(session.userId);
|
|
@@ -2066,7 +3129,7 @@ var Auth = class {
|
|
|
2066
3129
|
}
|
|
2067
3130
|
async verifyToken(token) {
|
|
2068
3131
|
try {
|
|
2069
|
-
const decoded =
|
|
3132
|
+
const decoded = jwt2.verify(token, this.config.secret, {
|
|
2070
3133
|
issuer: this.config.issuer,
|
|
2071
3134
|
audience: this.config.audience.length > 0 ? this.config.audience[0] : void 0
|
|
2072
3135
|
});
|
|
@@ -2130,9 +3193,7 @@ var Auth = class {
|
|
|
2130
3193
|
}
|
|
2131
3194
|
async createSessionForUser(user) {
|
|
2132
3195
|
const token = this.generateToken(user);
|
|
2133
|
-
const
|
|
2134
|
-
const expiresAt = new Date(Date.now() + this.parseExpiresIn(this.config.refreshExpiresIn));
|
|
2135
|
-
const session = await this.adapter.createSession(user.id, refreshToken, expiresAt);
|
|
3196
|
+
const session = await this.adapter.createSession(user.id);
|
|
2136
3197
|
return {
|
|
2137
3198
|
success: true,
|
|
2138
3199
|
user,
|
|
@@ -2145,7 +3206,7 @@ var Auth = class {
|
|
|
2145
3206
|
sub: user.id,
|
|
2146
3207
|
email: user.email,
|
|
2147
3208
|
role: user.role,
|
|
2148
|
-
|
|
3209
|
+
tenantId: user.tenantId
|
|
2149
3210
|
};
|
|
2150
3211
|
const signOptions = {
|
|
2151
3212
|
expiresIn: this.parseExpiresIn(this.config.expiresIn) / 1e3,
|
|
@@ -2154,7 +3215,7 @@ var Auth = class {
|
|
|
2154
3215
|
if (this.config.audience.length > 0) {
|
|
2155
3216
|
signOptions.audience = this.config.audience[0];
|
|
2156
3217
|
}
|
|
2157
|
-
return
|
|
3218
|
+
return jwt2.sign(payload, this.config.secret, signOptions);
|
|
2158
3219
|
}
|
|
2159
3220
|
async hashPassword(password) {
|
|
2160
3221
|
return bcrypt.hash(password, this.config.saltRounds);
|
|
@@ -2329,6 +3390,35 @@ function isArchived(status) {
|
|
|
2329
3390
|
return status === "archived";
|
|
2330
3391
|
}
|
|
2331
3392
|
|
|
2332
|
-
|
|
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
|
+
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, 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
3423
|
//# sourceMappingURL=index.js.map
|
|
2334
3424
|
//# sourceMappingURL=index.js.map
|