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