@kyro-cms/core 0.1.3 → 0.1.5
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/dist/bootstrap-BDTTUGY2.js +4 -0
- package/dist/{bootstrap-Q2TWUQF3.js.map → bootstrap-BDTTUGY2.js.map} +1 -1
- package/dist/bootstrap-X6TP3NKX.cjs +29 -0
- package/dist/{bootstrap-2WJK6PG7.cjs.map → bootstrap-X6TP3NKX.cjs.map} +1 -1
- package/dist/chunk-5BLDMQED.cjs +18 -0
- package/dist/{chunk-Q7SFCCGT.cjs.map → chunk-5BLDMQED.cjs.map} +1 -1
- package/dist/{chunk-U4CHJTWX.cjs → chunk-7G6EVYCU.cjs} +5 -5
- package/dist/{chunk-U4CHJTWX.cjs.map → chunk-7G6EVYCU.cjs.map} +1 -1
- package/dist/chunk-A3RQWHKD.cjs +263 -0
- package/dist/chunk-A3RQWHKD.cjs.map +1 -0
- package/dist/{chunk-V67YXRBT.js → chunk-C74MQIRL.js} +517 -203
- package/dist/chunk-C74MQIRL.js.map +1 -0
- package/dist/{chunk-XLMVCGXA.js → chunk-LRTZJJPD.js} +3 -3
- package/dist/{chunk-XLMVCGXA.js.map → chunk-LRTZJJPD.js.map} +1 -1
- package/dist/{chunk-I4BORBXT.cjs → chunk-MHS6CPO5.cjs} +517 -204
- package/dist/chunk-MHS6CPO5.cjs.map +1 -0
- package/dist/chunk-NSBPE2FW.js +15 -0
- package/dist/{chunk-PZ5AY32C.js.map → chunk-NSBPE2FW.js.map} +1 -1
- package/dist/{chunk-M4JFHQ5J.js → chunk-QUJ4OLSC.js} +3 -3
- package/dist/{chunk-M4JFHQ5J.js.map → chunk-QUJ4OLSC.js.map} +1 -1
- package/dist/{chunk-5AOILNGY.cjs → chunk-TZFJMPCH.cjs} +4 -4
- package/dist/{chunk-5AOILNGY.cjs.map → chunk-TZFJMPCH.cjs.map} +1 -1
- package/dist/chunk-VMSRTAH7.js +256 -0
- package/dist/chunk-VMSRTAH7.js.map +1 -0
- package/dist/{chunk-KA3UOIFC.js → chunk-XTZSUDSI.js} +3 -3
- package/dist/{chunk-KA3UOIFC.js.map → chunk-XTZSUDSI.js.map} +1 -1
- package/dist/{chunk-KWTKEBHM.cjs → chunk-YD7Y25W7.cjs} +19 -19
- package/dist/{chunk-KWTKEBHM.cjs.map → chunk-YD7Y25W7.cjs.map} +1 -1
- package/dist/cli/index.cjs +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/database-7CJOXEZR.js +5 -0
- package/dist/{database-37KXWUER.js.map → database-7CJOXEZR.js.map} +1 -1
- package/dist/database-QOIV44GT.cjs +22 -0
- package/dist/{database-LJKD3HE4.cjs.map → database-QOIV44GT.cjs.map} +1 -1
- package/dist/drizzle/index.cjs +8 -8
- package/dist/drizzle/index.d.cts +1 -1
- package/dist/drizzle/index.d.ts +1 -1
- package/dist/drizzle/index.js +4 -4
- package/dist/graphql/index.cjs +1 -1
- package/dist/graphql/index.js +1 -1
- package/dist/{index-CzkEHKqu.d.cts → index-BMySjW6o.d.cts} +6 -0
- package/dist/{index-BVFlb7uU.d.ts → index-CMUNCIWQ.d.ts} +6 -0
- package/dist/index.cjs +727 -346
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -62
- package/dist/index.d.ts +229 -62
- package/dist/index.js +706 -331
- package/dist/index.js.map +1 -1
- package/dist/mongodb/index.cjs +1 -1
- package/dist/mongodb/index.js +1 -1
- package/dist/postgres-auth-adapter-REJFUMP7.js +5 -0
- package/dist/{postgres-auth-adapter-LTDUGBMB.js.map → postgres-auth-adapter-REJFUMP7.js.map} +1 -1
- package/dist/postgres-auth-adapter-VK6GY7LX.cjs +14 -0
- package/dist/{postgres-auth-adapter-CYZAVPPP.cjs.map → postgres-auth-adapter-VK6GY7LX.cjs.map} +1 -1
- package/dist/redis-adapter-4YDY4LWE.js +4 -0
- package/dist/redis-adapter-4YDY4LWE.js.map +1 -0
- package/dist/redis-adapter-LBLNKGNS.cjs +13 -0
- package/dist/redis-adapter-LBLNKGNS.cjs.map +1 -0
- package/dist/rest/index.cjs +1 -1
- package/dist/rest/index.js +1 -1
- package/dist/templates/index.cjs +1 -1
- package/dist/templates/index.js +1 -1
- package/dist/trpc/index.cjs +1 -1
- package/dist/trpc/index.js +1 -1
- package/dist/ws/index.cjs +1 -1
- package/dist/ws/index.js +1 -1
- package/package.json +2 -2
- package/dist/bootstrap-2WJK6PG7.cjs +0 -29
- package/dist/bootstrap-Q2TWUQF3.js +0 -4
- package/dist/chunk-I4BORBXT.cjs.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
- package/dist/chunk-V67YXRBT.js.map +0 -1
- package/dist/database-37KXWUER.js +0 -5
- package/dist/database-LJKD3HE4.cjs +0 -22
- package/dist/postgres-auth-adapter-CYZAVPPP.cjs +0 -14
- package/dist/postgres-auth-adapter-LTDUGBMB.js +0 -5
|
@@ -1,55 +1,270 @@
|
|
|
1
|
-
import Redis2 from 'ioredis';
|
|
2
1
|
import bcrypt from 'bcryptjs';
|
|
3
2
|
import { randomBytes } from 'crypto';
|
|
4
3
|
import nodemailer from 'nodemailer';
|
|
5
4
|
|
|
6
|
-
// src/auth/
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
// src/auth/sqlite-adapter.ts
|
|
6
|
+
var DEFAULT_BUSY_TIMEOUT = 5e3;
|
|
7
|
+
var DEFAULT_WAL_CHECKPOINT = 1e3;
|
|
8
|
+
var DEFAULT_CACHE_SIZE = -64e3;
|
|
9
|
+
var DEFAULT_MMAP_SIZE = 268435456;
|
|
10
|
+
var SQLiteAuthAdapter = class {
|
|
11
|
+
db = null;
|
|
12
|
+
path;
|
|
13
|
+
saltRounds;
|
|
14
|
+
externalDb;
|
|
15
|
+
busyTimeout;
|
|
16
|
+
walAutoCheckpoint;
|
|
17
|
+
cacheSize;
|
|
18
|
+
mmapSize;
|
|
19
|
+
preparedStatements = /* @__PURE__ */ new Map();
|
|
15
20
|
constructor(options = {}) {
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
this.path = options.path || "./data/auth.db";
|
|
22
|
+
this.saltRounds = options.saltRounds || 12;
|
|
23
|
+
this.externalDb = !!options.db;
|
|
24
|
+
this.busyTimeout = options.busyTimeout ?? DEFAULT_BUSY_TIMEOUT;
|
|
25
|
+
this.walAutoCheckpoint = options.walAutoCheckpoint ?? DEFAULT_WAL_CHECKPOINT;
|
|
26
|
+
this.cacheSize = options.cacheSize ?? DEFAULT_CACHE_SIZE;
|
|
27
|
+
this.mmapSize = options.mmapSize ?? DEFAULT_MMAP_SIZE;
|
|
28
|
+
if (options.db) {
|
|
29
|
+
this.db = options.db;
|
|
30
|
+
}
|
|
26
31
|
}
|
|
27
32
|
async connect() {
|
|
28
|
-
|
|
33
|
+
if (this.db) return;
|
|
34
|
+
const Database = (await import('better-sqlite3')).default;
|
|
35
|
+
this.db = new Database(this.path, {
|
|
36
|
+
timeout: this.busyTimeout
|
|
37
|
+
});
|
|
38
|
+
this.db.pragma("journal_mode = WAL");
|
|
39
|
+
this.db.pragma("synchronous = NORMAL");
|
|
40
|
+
this.db.pragma("cache_size = " + this.cacheSize);
|
|
41
|
+
this.db.pragma("mmap_size = " + this.mmapSize);
|
|
42
|
+
this.db.pragma("wal_autocheckpoint = " + this.walAutoCheckpoint);
|
|
43
|
+
this.db.pragma("foreign_keys = ON");
|
|
44
|
+
this.db.pragma("temp_store = MEMORY");
|
|
45
|
+
this.ensureTables();
|
|
46
|
+
this.prepareStatements();
|
|
29
47
|
}
|
|
30
48
|
async disconnect() {
|
|
31
|
-
|
|
49
|
+
if (this.db && !this.externalDb) {
|
|
50
|
+
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
51
|
+
this.db.close();
|
|
52
|
+
this.db = null;
|
|
53
|
+
this.preparedStatements.clear();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ensureTables() {
|
|
57
|
+
if (!this.db) return;
|
|
58
|
+
this.db.exec(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS kyro_users (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
email TEXT UNIQUE NOT NULL,
|
|
62
|
+
password_hash TEXT NOT NULL,
|
|
63
|
+
role TEXT NOT NULL DEFAULT 'customer',
|
|
64
|
+
tenant_id TEXT,
|
|
65
|
+
email_verified INTEGER DEFAULT 0,
|
|
66
|
+
locked INTEGER DEFAULT 0,
|
|
67
|
+
last_login TEXT,
|
|
68
|
+
failed_login_attempts INTEGER DEFAULT 0,
|
|
69
|
+
locked_until TEXT,
|
|
70
|
+
created_at TEXT NOT NULL,
|
|
71
|
+
updated_at TEXT NOT NULL
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS kyro_sessions (
|
|
75
|
+
id TEXT PRIMARY KEY,
|
|
76
|
+
user_id TEXT NOT NULL,
|
|
77
|
+
token TEXT NOT NULL,
|
|
78
|
+
refresh_token TEXT,
|
|
79
|
+
expires_at TEXT NOT NULL,
|
|
80
|
+
created_at TEXT NOT NULL,
|
|
81
|
+
ip_address TEXT,
|
|
82
|
+
user_agent TEXT,
|
|
83
|
+
FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE IF NOT EXISTS kyro_password_history (
|
|
87
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
88
|
+
user_id TEXT NOT NULL,
|
|
89
|
+
password_hash TEXT NOT NULL,
|
|
90
|
+
created_at TEXT NOT NULL,
|
|
91
|
+
FOREIGN KEY (user_id) REFERENCES kyro_users(id) ON DELETE CASCADE
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS kyro_rate_limits (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
key TEXT NOT NULL,
|
|
97
|
+
window_start INTEGER NOT NULL,
|
|
98
|
+
count INTEGER NOT NULL DEFAULT 1,
|
|
99
|
+
UNIQUE(key, window_start)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE TABLE IF NOT EXISTS kyro_lockouts (
|
|
103
|
+
user_id TEXT PRIMARY KEY,
|
|
104
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
last_attempt INTEGER,
|
|
106
|
+
locked_at INTEGER,
|
|
107
|
+
locked_until INTEGER
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE TABLE IF NOT EXISTS kyro_audit_logs (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
timestamp TEXT NOT NULL,
|
|
113
|
+
action TEXT NOT NULL,
|
|
114
|
+
user_id TEXT,
|
|
115
|
+
user_email TEXT,
|
|
116
|
+
role TEXT,
|
|
117
|
+
resource TEXT NOT NULL,
|
|
118
|
+
resource_id TEXT,
|
|
119
|
+
ip_address TEXT,
|
|
120
|
+
user_agent TEXT,
|
|
121
|
+
success INTEGER NOT NULL,
|
|
122
|
+
error TEXT,
|
|
123
|
+
metadata TEXT,
|
|
124
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_users_email ON kyro_users(email);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_sessions_user_id ON kyro_sessions(user_id);
|
|
129
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_sessions_token ON kyro_sessions(token);
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_sessions_refresh_token ON kyro_sessions(refresh_token);
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_sessions_expires ON kyro_sessions(expires_at);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_password_history_user_id ON kyro_password_history(user_id);
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_rate_limits_key ON kyro_rate_limits(key);
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_rate_limits_window ON kyro_rate_limits(window_start);
|
|
135
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_lockouts_locked_until ON kyro_lockouts(locked_until);
|
|
136
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_audit_logs_timestamp ON kyro_audit_logs(timestamp);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_audit_logs_action ON kyro_audit_logs(action);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_audit_logs_user_id ON kyro_audit_logs(user_id);
|
|
139
|
+
CREATE INDEX IF NOT EXISTS idx_kyro_audit_logs_resource ON kyro_audit_logs(resource);
|
|
140
|
+
`);
|
|
32
141
|
}
|
|
33
|
-
|
|
34
|
-
|
|
142
|
+
prepareStatements() {
|
|
143
|
+
if (!this.db) return;
|
|
144
|
+
this.preparedStatements.set(
|
|
145
|
+
"findUserByEmail",
|
|
146
|
+
this.db.prepare("SELECT * FROM kyro_users WHERE email = ?")
|
|
147
|
+
);
|
|
148
|
+
this.preparedStatements.set(
|
|
149
|
+
"findUserById",
|
|
150
|
+
this.db.prepare("SELECT * FROM kyro_users WHERE id = ?")
|
|
151
|
+
);
|
|
152
|
+
this.preparedStatements.set(
|
|
153
|
+
"findSessionByToken",
|
|
154
|
+
this.db.prepare("SELECT * FROM kyro_sessions WHERE token = ?")
|
|
155
|
+
);
|
|
156
|
+
this.preparedStatements.set(
|
|
157
|
+
"findSessionByRefreshToken",
|
|
158
|
+
this.db.prepare("SELECT * FROM kyro_sessions WHERE refresh_token = ?")
|
|
159
|
+
);
|
|
160
|
+
this.preparedStatements.set(
|
|
161
|
+
"deleteSession",
|
|
162
|
+
this.db.prepare("DELETE FROM kyro_sessions WHERE id = ? OR token = ?")
|
|
163
|
+
);
|
|
164
|
+
this.preparedStatements.set(
|
|
165
|
+
"deleteUserSessions",
|
|
166
|
+
this.db.prepare("DELETE FROM kyro_sessions WHERE user_id = ?")
|
|
167
|
+
);
|
|
168
|
+
this.preparedStatements.set(
|
|
169
|
+
"countUsers",
|
|
170
|
+
this.db.prepare("SELECT COUNT(*) as count FROM kyro_users")
|
|
171
|
+
);
|
|
172
|
+
this.preparedStatements.set(
|
|
173
|
+
"deleteUser",
|
|
174
|
+
this.db.prepare("DELETE FROM kyro_users WHERE id = ?")
|
|
175
|
+
);
|
|
176
|
+
this.preparedStatements.set(
|
|
177
|
+
"getPasswordHistory",
|
|
178
|
+
this.db.prepare(
|
|
179
|
+
"SELECT password_hash FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT ?"
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
this.preparedStatements.set(
|
|
183
|
+
"addPasswordHistory",
|
|
184
|
+
this.db.prepare(
|
|
185
|
+
"INSERT INTO kyro_password_history (user_id, password_hash, created_at) VALUES (?, ?, ?)"
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
this.preparedStatements.set(
|
|
189
|
+
"trimPasswordHistory",
|
|
190
|
+
this.db.prepare(
|
|
191
|
+
`DELETE FROM kyro_password_history WHERE id IN (
|
|
192
|
+
SELECT id FROM kyro_password_history WHERE user_id = ? ORDER BY created_at DESC LIMIT -1 OFFSET 5
|
|
193
|
+
)`
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
this.preparedStatements.set(
|
|
197
|
+
"deleteExpiredSessions",
|
|
198
|
+
this.db.prepare("DELETE FROM kyro_sessions WHERE expires_at < ?")
|
|
199
|
+
);
|
|
200
|
+
this.preparedStatements.set(
|
|
201
|
+
"cleanupOldAuditLogs",
|
|
202
|
+
this.db.prepare("DELETE FROM kyro_audit_logs WHERE timestamp < ?")
|
|
203
|
+
);
|
|
204
|
+
this.preparedStatements.set(
|
|
205
|
+
"cleanupExpiredLockouts",
|
|
206
|
+
this.db.prepare(
|
|
207
|
+
"UPDATE kyro_lockouts SET attempts = 0, locked_at = NULL, locked_until = NULL WHERE locked_until < ?"
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
this.preparedStatements.set(
|
|
211
|
+
"getLockout",
|
|
212
|
+
this.db.prepare("SELECT * FROM kyro_lockouts WHERE user_id = ?")
|
|
213
|
+
);
|
|
214
|
+
this.preparedStatements.set(
|
|
215
|
+
"upsertLockout",
|
|
216
|
+
this.db.prepare(`
|
|
217
|
+
INSERT INTO kyro_lockouts (user_id, attempts, last_attempt, locked_at, locked_until)
|
|
218
|
+
VALUES (?, ?, ?, ?, ?)
|
|
219
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
220
|
+
attempts = excluded.attempts,
|
|
221
|
+
last_attempt = excluded.last_attempt,
|
|
222
|
+
locked_at = excluded.locked_at,
|
|
223
|
+
locked_until = excluded.locked_until
|
|
224
|
+
`)
|
|
225
|
+
);
|
|
226
|
+
this.preparedStatements.set(
|
|
227
|
+
"resetLockout",
|
|
228
|
+
this.db.prepare(
|
|
229
|
+
"UPDATE kyro_lockouts SET attempts = 0, locked_at = NULL, locked_until = NULL WHERE user_id = ?"
|
|
230
|
+
)
|
|
231
|
+
);
|
|
35
232
|
}
|
|
36
|
-
|
|
37
|
-
|
|
233
|
+
stmt(name) {
|
|
234
|
+
const stmt = this.preparedStatements.get(name);
|
|
235
|
+
if (!stmt) throw new Error(`Prepared statement not found: ${name}`);
|
|
236
|
+
return stmt;
|
|
38
237
|
}
|
|
39
|
-
|
|
40
|
-
|
|
238
|
+
async cleanupExpiredSessions() {
|
|
239
|
+
if (!this.db) throw new Error("Not connected");
|
|
240
|
+
const result = this.stmt("deleteExpiredSessions").run(
|
|
241
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
242
|
+
);
|
|
243
|
+
return result.changes;
|
|
41
244
|
}
|
|
42
|
-
|
|
43
|
-
|
|
245
|
+
async cleanupOldAuditLogs(retentionDays = 30) {
|
|
246
|
+
if (!this.db) throw new Error("Not connected");
|
|
247
|
+
const cutoff = new Date(
|
|
248
|
+
Date.now() - retentionDays * 24 * 60 * 60 * 1e3
|
|
249
|
+
).toISOString();
|
|
250
|
+
const result = this.stmt("cleanupOldAuditLogs").run(cutoff);
|
|
251
|
+
return result.changes;
|
|
44
252
|
}
|
|
45
|
-
|
|
46
|
-
|
|
253
|
+
async getStats() {
|
|
254
|
+
if (!this.db) throw new Error("Not connected");
|
|
255
|
+
const userCount = this.stmt("countUsers").get().count;
|
|
256
|
+
const activeSessionCount = this.db.prepare(
|
|
257
|
+
"SELECT COUNT(*) as count FROM kyro_sessions WHERE expires_at > ?"
|
|
258
|
+
).get((/* @__PURE__ */ new Date()).toISOString()).count;
|
|
259
|
+
const auditLogCount = this.db.prepare("SELECT COUNT(*) as count FROM kyro_audit_logs").get().count;
|
|
260
|
+
return { userCount, activeSessionCount, auditLogCount };
|
|
47
261
|
}
|
|
48
262
|
async createUser(data) {
|
|
49
|
-
|
|
263
|
+
if (!this.db) throw new Error("Not connected");
|
|
264
|
+
const id = randomBytes(16).toString("hex");
|
|
50
265
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
51
266
|
const user = {
|
|
52
|
-
id
|
|
267
|
+
id,
|
|
53
268
|
email: data.email.toLowerCase(),
|
|
54
269
|
passwordHash: data.passwordHash,
|
|
55
270
|
role: data.role || "customer",
|
|
@@ -57,197 +272,317 @@ var RedisAuthAdapter = class {
|
|
|
57
272
|
createdAt: now,
|
|
58
273
|
updatedAt: now
|
|
59
274
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
275
|
+
this.db.prepare(
|
|
276
|
+
`INSERT INTO kyro_users (id, email, password_hash, role, tenant_id, created_at, updated_at)
|
|
277
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
278
|
+
).run(
|
|
279
|
+
id,
|
|
280
|
+
user.email,
|
|
281
|
+
user.passwordHash,
|
|
282
|
+
user.role,
|
|
283
|
+
user.tenantId,
|
|
284
|
+
now,
|
|
285
|
+
now
|
|
286
|
+
);
|
|
64
287
|
return user;
|
|
65
288
|
}
|
|
66
289
|
async findUserByEmail(email) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return this.findUserById(userId);
|
|
290
|
+
if (!this.db) throw new Error("Not connected");
|
|
291
|
+
const row = this.stmt("findUserByEmail").get(email.toLowerCase());
|
|
292
|
+
if (!row) return null;
|
|
293
|
+
return this.rowToUser(row);
|
|
72
294
|
}
|
|
73
295
|
async findUserById(userId) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
296
|
+
if (!this.db) throw new Error("Not connected");
|
|
297
|
+
const row = this.stmt("findUserById").get(userId);
|
|
298
|
+
if (!row) return null;
|
|
299
|
+
return this.rowToUser(row);
|
|
77
300
|
}
|
|
78
301
|
async updateUser(userId, data) {
|
|
302
|
+
if (!this.db) throw new Error("Not connected");
|
|
79
303
|
const existing = await this.findUserById(userId);
|
|
80
304
|
if (!existing) return null;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
};
|
|
87
|
-
if (data.email && data.email !== existing.email) {
|
|
88
|
-
const pipeline = this.redis.pipeline();
|
|
89
|
-
pipeline.del(this.userByEmailKey(existing.email));
|
|
90
|
-
pipeline.set(this.userByEmailKey(data.email), userId);
|
|
91
|
-
await pipeline.exec();
|
|
305
|
+
const updates = [];
|
|
306
|
+
const values = [];
|
|
307
|
+
if (data.email !== void 0) {
|
|
308
|
+
updates.push("email = ?");
|
|
309
|
+
values.push(data.email.toLowerCase());
|
|
92
310
|
}
|
|
93
|
-
|
|
94
|
-
|
|
311
|
+
if (data.passwordHash !== void 0) {
|
|
312
|
+
updates.push("password_hash = ?");
|
|
313
|
+
values.push(data.passwordHash);
|
|
314
|
+
}
|
|
315
|
+
if (data.role !== void 0) {
|
|
316
|
+
updates.push("role = ?");
|
|
317
|
+
values.push(data.role);
|
|
318
|
+
}
|
|
319
|
+
if (data.tenantId !== void 0) {
|
|
320
|
+
updates.push("tenant_id = ?");
|
|
321
|
+
values.push(data.tenantId);
|
|
322
|
+
}
|
|
323
|
+
if (data.emailVerified !== void 0) {
|
|
324
|
+
updates.push("email_verified = ?");
|
|
325
|
+
values.push(data.emailVerified ? 1 : 0);
|
|
326
|
+
}
|
|
327
|
+
if (data.locked !== void 0) {
|
|
328
|
+
updates.push("locked = ?");
|
|
329
|
+
values.push(data.locked ? 1 : 0);
|
|
330
|
+
}
|
|
331
|
+
if (data.lastLogin !== void 0) {
|
|
332
|
+
updates.push("last_login = ?");
|
|
333
|
+
values.push(data.lastLogin);
|
|
334
|
+
}
|
|
335
|
+
if (data.failedLoginAttempts !== void 0) {
|
|
336
|
+
updates.push("failed_login_attempts = ?");
|
|
337
|
+
values.push(data.failedLoginAttempts);
|
|
338
|
+
}
|
|
339
|
+
updates.push("updated_at = ?");
|
|
340
|
+
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
341
|
+
values.push(userId);
|
|
342
|
+
this.db.prepare(`UPDATE kyro_users SET ${updates.join(", ")} WHERE id = ?`).run(...values);
|
|
343
|
+
return this.findUserById(userId);
|
|
95
344
|
}
|
|
96
345
|
async deleteUser(userId) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
pipeline.del(this.userKey(userId));
|
|
101
|
-
pipeline.del(this.userByEmailKey(user.email));
|
|
102
|
-
pipeline.del(this.passwordHistoryKey(userId));
|
|
103
|
-
await pipeline.exec();
|
|
104
|
-
return true;
|
|
346
|
+
if (!this.db) throw new Error("Not connected");
|
|
347
|
+
const result = this.stmt("deleteUser").run(userId);
|
|
348
|
+
return result.changes > 0;
|
|
105
349
|
}
|
|
106
350
|
async hashPassword(password) {
|
|
107
|
-
return bcrypt.hash(password,
|
|
351
|
+
return bcrypt.hash(password, this.saltRounds);
|
|
108
352
|
}
|
|
109
353
|
async verifyPassword(password, hash) {
|
|
110
354
|
return bcrypt.compare(password, hash);
|
|
111
355
|
}
|
|
112
356
|
async createSession(userId, data = {}) {
|
|
113
|
-
|
|
357
|
+
if (!this.db) throw new Error("Not connected");
|
|
358
|
+
const id = randomBytes(32).toString("hex");
|
|
114
359
|
const token = randomBytes(32).toString("base64url");
|
|
115
360
|
const refreshToken = randomBytes(32).toString("base64url");
|
|
116
361
|
const now = /* @__PURE__ */ new Date();
|
|
362
|
+
const expiresAt = new Date(now.getTime() + 864e5).toISOString();
|
|
117
363
|
const session = {
|
|
118
|
-
id
|
|
364
|
+
id,
|
|
119
365
|
userId,
|
|
120
366
|
token,
|
|
121
367
|
refreshToken,
|
|
122
|
-
expiresAt
|
|
123
|
-
now.getTime() + this.tokenExpiration * 1e3
|
|
124
|
-
).toISOString(),
|
|
368
|
+
expiresAt,
|
|
125
369
|
createdAt: now.toISOString(),
|
|
126
370
|
ipAddress: data.ipAddress,
|
|
127
371
|
userAgent: data.userAgent
|
|
128
372
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
373
|
+
this.db.prepare(
|
|
374
|
+
`INSERT INTO kyro_sessions (id, user_id, token, refresh_token, expires_at, created_at, ip_address, user_agent)
|
|
375
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
376
|
+
).run(
|
|
377
|
+
session.id,
|
|
378
|
+
session.userId,
|
|
379
|
+
session.token,
|
|
380
|
+
session.refreshToken,
|
|
381
|
+
session.expiresAt,
|
|
382
|
+
session.createdAt,
|
|
383
|
+
session.ipAddress,
|
|
384
|
+
session.userAgent
|
|
135
385
|
);
|
|
136
|
-
await pipeline.exec();
|
|
137
386
|
return session;
|
|
138
387
|
}
|
|
139
388
|
async findSessionByToken(token) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
389
|
+
if (!this.db) throw new Error("Not connected");
|
|
390
|
+
const row = this.stmt("findSessionByToken").get(token);
|
|
391
|
+
if (!row) return null;
|
|
392
|
+
return this.rowToSession(row);
|
|
393
|
+
}
|
|
394
|
+
async findSessionByRefreshToken(refreshToken) {
|
|
395
|
+
if (!this.db) throw new Error("Not connected");
|
|
396
|
+
const row = this.stmt("findSessionByRefreshToken").get(refreshToken);
|
|
397
|
+
if (!row) return null;
|
|
398
|
+
return this.rowToSession(row);
|
|
143
399
|
}
|
|
144
400
|
async deleteSession(sessionId) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
pipeline.del(this.sessionKey(sessionId));
|
|
149
|
-
if (session.refreshToken) {
|
|
150
|
-
pipeline.del(this.refreshKey(session.refreshToken));
|
|
151
|
-
}
|
|
152
|
-
await pipeline.exec();
|
|
153
|
-
return true;
|
|
401
|
+
if (!this.db) throw new Error("Not connected");
|
|
402
|
+
const result = this.stmt("deleteSession").run(sessionId, sessionId);
|
|
403
|
+
return result.changes > 0;
|
|
154
404
|
}
|
|
155
405
|
async deleteUserSessions(userId) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
"COUNT",
|
|
165
|
-
100
|
|
166
|
-
);
|
|
167
|
-
cursor = nextCursor;
|
|
168
|
-
for (const key of keys) {
|
|
169
|
-
const sessionData = await this.redis.hgetall(key);
|
|
170
|
-
if (sessionData.userId === userId) {
|
|
171
|
-
const sessionId = key.replace(`${this.prefix}sessions:`, "");
|
|
172
|
-
await this.deleteSession(sessionId);
|
|
173
|
-
deleted++;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
} while (cursor !== "0");
|
|
177
|
-
return deleted;
|
|
406
|
+
if (!this.db) throw new Error("Not connected");
|
|
407
|
+
const result = this.stmt("deleteUserSessions").run(userId);
|
|
408
|
+
return result.changes;
|
|
409
|
+
}
|
|
410
|
+
async hasAnyUsers() {
|
|
411
|
+
if (!this.db) throw new Error("Not connected");
|
|
412
|
+
const row = this.stmt("countUsers").get();
|
|
413
|
+
return row.count > 0;
|
|
178
414
|
}
|
|
179
415
|
async addPasswordToHistory(userId, passwordHash) {
|
|
180
|
-
|
|
181
|
-
|
|
416
|
+
if (!this.db) throw new Error("Not connected");
|
|
417
|
+
this.stmt("addPasswordHistory").run(
|
|
418
|
+
userId,
|
|
419
|
+
passwordHash,
|
|
420
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
421
|
+
);
|
|
422
|
+
this.stmt("trimPasswordHistory").run(userId);
|
|
182
423
|
}
|
|
183
424
|
async getPasswordHistory(userId, count = 5) {
|
|
184
|
-
|
|
425
|
+
if (!this.db) throw new Error("Not connected");
|
|
426
|
+
const rows = this.stmt("getPasswordHistory").all(userId, count);
|
|
427
|
+
return rows.map((r) => r.password_hash);
|
|
185
428
|
}
|
|
186
429
|
async isPasswordInHistory(password, userId, historyCount = 5) {
|
|
187
430
|
const history = await this.getPasswordHistory(userId, historyCount);
|
|
188
431
|
for (const hash of history) {
|
|
189
|
-
if (await
|
|
432
|
+
if (await bcrypt.compare(password, hash)) {
|
|
190
433
|
return true;
|
|
191
434
|
}
|
|
192
435
|
}
|
|
193
436
|
return false;
|
|
194
437
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
438
|
+
async recordFailedAttempt(userId) {
|
|
439
|
+
if (!this.db) throw new Error("Not connected");
|
|
440
|
+
const now = Date.now();
|
|
441
|
+
const lockout = this.stmt("getLockout").get(userId);
|
|
442
|
+
const attempts = (lockout?.attempts || 0) + 1;
|
|
443
|
+
const lockedUntil = attempts >= 5 ? now + 15 * 60 * 1e3 : lockout?.locked_until || null;
|
|
444
|
+
this.stmt("upsertLockout").run(
|
|
445
|
+
userId,
|
|
446
|
+
attempts,
|
|
447
|
+
now,
|
|
448
|
+
lockedUntil !== null ? now : null,
|
|
449
|
+
lockedUntil
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
async resetAttempts(userId) {
|
|
453
|
+
if (!this.db) throw new Error("Not connected");
|
|
454
|
+
this.stmt("resetLockout").run(userId);
|
|
455
|
+
}
|
|
456
|
+
async checkLockout(userId) {
|
|
457
|
+
if (!this.db) throw new Error("Not connected");
|
|
458
|
+
this.stmt("cleanupExpiredLockouts").run(Date.now());
|
|
459
|
+
const lockout = this.stmt("getLockout").get(userId);
|
|
460
|
+
if (!lockout) {
|
|
461
|
+
return {
|
|
462
|
+
locked: false,
|
|
463
|
+
attemptsRemaining: 5,
|
|
464
|
+
totalAttempts: 0
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (lockout.locked_until !== null && lockout.locked_until > Date.now()) {
|
|
468
|
+
return {
|
|
469
|
+
locked: true,
|
|
470
|
+
attemptsRemaining: 0,
|
|
471
|
+
lockedUntil: new Date(lockout.locked_until),
|
|
472
|
+
totalAttempts: lockout.attempts
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
locked: false,
|
|
477
|
+
attemptsRemaining: Math.max(0, 5 - lockout.attempts),
|
|
478
|
+
totalAttempts: lockout.attempts
|
|
203
479
|
};
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
480
|
+
}
|
|
481
|
+
async logAudit(data) {
|
|
482
|
+
if (!this.db) throw new Error("Not connected");
|
|
483
|
+
const id = randomBytes(16).toString("hex");
|
|
484
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
485
|
+
this.db.prepare(
|
|
486
|
+
`INSERT INTO kyro_audit_logs (
|
|
487
|
+
id, timestamp, action, user_id, user_email, role, resource, resource_id,
|
|
488
|
+
ip_address, user_agent, success, error, metadata, created_at
|
|
489
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
490
|
+
).run(
|
|
491
|
+
id,
|
|
492
|
+
timestamp,
|
|
493
|
+
data.action,
|
|
494
|
+
data.userId || null,
|
|
495
|
+
data.userEmail || null,
|
|
496
|
+
data.role || null,
|
|
497
|
+
data.resource,
|
|
498
|
+
data.resourceId || null,
|
|
499
|
+
data.ipAddress || null,
|
|
500
|
+
data.userAgent || null,
|
|
501
|
+
data.success ? 1 : 0,
|
|
502
|
+
data.error || null,
|
|
503
|
+
data.metadata ? JSON.stringify(data.metadata) : null,
|
|
504
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
505
|
+
);
|
|
506
|
+
return id;
|
|
507
|
+
}
|
|
508
|
+
async queryAuditLogs(options = {}) {
|
|
509
|
+
if (!this.db) throw new Error("Not connected");
|
|
510
|
+
const conditions = [];
|
|
511
|
+
const params = [];
|
|
512
|
+
if (options.action) {
|
|
513
|
+
conditions.push("action = ?");
|
|
514
|
+
params.push(options.action);
|
|
515
|
+
}
|
|
516
|
+
if (options.userId) {
|
|
517
|
+
conditions.push("user_id = ?");
|
|
518
|
+
params.push(options.userId);
|
|
519
|
+
}
|
|
520
|
+
if (options.resource) {
|
|
521
|
+
conditions.push("resource = ?");
|
|
522
|
+
params.push(options.resource);
|
|
523
|
+
}
|
|
524
|
+
if (options.success !== void 0) {
|
|
525
|
+
conditions.push("success = ?");
|
|
526
|
+
params.push(options.success ? 1 : 0);
|
|
527
|
+
}
|
|
528
|
+
if (options.startDate) {
|
|
529
|
+
conditions.push("timestamp >= ?");
|
|
530
|
+
params.push(options.startDate.toISOString());
|
|
531
|
+
}
|
|
532
|
+
if (options.endDate) {
|
|
533
|
+
conditions.push("timestamp <= ?");
|
|
534
|
+
params.push(options.endDate.toISOString());
|
|
535
|
+
}
|
|
536
|
+
const where = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
537
|
+
const limit = options.limit || 50;
|
|
538
|
+
const offset = options.offset || 0;
|
|
539
|
+
const totalResult = this.db.prepare(`SELECT COUNT(*) as count FROM kyro_audit_logs ${where}`).get(...params);
|
|
540
|
+
const rows = this.db.prepare(
|
|
541
|
+
`SELECT * FROM kyro_audit_logs ${where} ORDER BY timestamp DESC LIMIT ? OFFSET ?`
|
|
542
|
+
).all(...params, limit, offset);
|
|
214
543
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
544
|
+
total: totalResult.count,
|
|
545
|
+
logs: rows.map((row) => ({
|
|
546
|
+
id: row.id,
|
|
547
|
+
timestamp: new Date(row.timestamp),
|
|
548
|
+
action: row.action,
|
|
549
|
+
userId: row.user_id || void 0,
|
|
550
|
+
userEmail: row.user_email || void 0,
|
|
551
|
+
resource: row.resource,
|
|
552
|
+
resourceId: row.resource_id || void 0,
|
|
553
|
+
ipAddress: row.ip_address || void 0,
|
|
554
|
+
userAgent: row.user_agent || void 0,
|
|
555
|
+
success: row.success === 1,
|
|
556
|
+
error: row.error || void 0,
|
|
557
|
+
metadata: row.metadata ? JSON.parse(row.metadata) : void 0
|
|
558
|
+
}))
|
|
226
559
|
};
|
|
227
560
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
id:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
561
|
+
rowToUser(row) {
|
|
562
|
+
return {
|
|
563
|
+
id: row.id,
|
|
564
|
+
email: row.email,
|
|
565
|
+
passwordHash: row.password_hash,
|
|
566
|
+
role: row.role,
|
|
567
|
+
tenantId: row.tenant_id,
|
|
568
|
+
emailVerified: row.email_verified === 1,
|
|
569
|
+
locked: row.locked === 1,
|
|
570
|
+
lastLogin: row.last_login,
|
|
571
|
+
failedLoginAttempts: row.failed_login_attempts || 0,
|
|
572
|
+
createdAt: row.created_at,
|
|
573
|
+
updatedAt: row.updated_at
|
|
235
574
|
};
|
|
236
|
-
if (session.refreshToken) hash.refreshToken = session.refreshToken;
|
|
237
|
-
if (session.ipAddress) hash.ipAddress = session.ipAddress;
|
|
238
|
-
if (session.userAgent) hash.userAgent = session.userAgent;
|
|
239
|
-
return hash;
|
|
240
575
|
}
|
|
241
|
-
|
|
576
|
+
rowToSession(row) {
|
|
242
577
|
return {
|
|
243
|
-
id:
|
|
244
|
-
userId:
|
|
245
|
-
token:
|
|
246
|
-
refreshToken:
|
|
247
|
-
expiresAt:
|
|
248
|
-
createdAt:
|
|
249
|
-
ipAddress:
|
|
250
|
-
userAgent:
|
|
578
|
+
id: row.id,
|
|
579
|
+
userId: row.user_id,
|
|
580
|
+
token: row.token,
|
|
581
|
+
refreshToken: row.refresh_token,
|
|
582
|
+
expiresAt: row.expires_at,
|
|
583
|
+
createdAt: row.created_at,
|
|
584
|
+
ipAddress: row.ip_address,
|
|
585
|
+
userAgent: row.user_agent
|
|
251
586
|
};
|
|
252
587
|
}
|
|
253
588
|
};
|
|
@@ -743,10 +1078,6 @@ var PasswordPolicy = class {
|
|
|
743
1078
|
// src/auth/bootstrap.ts
|
|
744
1079
|
async function bootstrapAdmin(config) {
|
|
745
1080
|
const {
|
|
746
|
-
redisUrl,
|
|
747
|
-
redisHost,
|
|
748
|
-
redisPort,
|
|
749
|
-
redisPassword,
|
|
750
1081
|
adminEmail,
|
|
751
1082
|
adminPassword,
|
|
752
1083
|
adminRole = "super_admin",
|
|
@@ -754,34 +1085,21 @@ async function bootstrapAdmin(config) {
|
|
|
754
1085
|
emailConfig,
|
|
755
1086
|
sendWelcomeEmail = false
|
|
756
1087
|
} = config;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
} else {
|
|
761
|
-
redis = new Redis2({
|
|
762
|
-
host: redisHost || "localhost",
|
|
763
|
-
port: redisPort || 6379,
|
|
764
|
-
password: redisPassword,
|
|
765
|
-
lazyConnect: true
|
|
766
|
-
});
|
|
767
|
-
}
|
|
1088
|
+
const authAdapter = config.authAdapter || new SQLiteAuthAdapter({
|
|
1089
|
+
path: config.authDbPath || "./data/auth.db"
|
|
1090
|
+
});
|
|
768
1091
|
try {
|
|
769
|
-
await
|
|
1092
|
+
await authAdapter.connect?.();
|
|
770
1093
|
} catch (error) {
|
|
771
1094
|
return {
|
|
772
1095
|
success: false,
|
|
773
|
-
error: "Failed to connect to
|
|
1096
|
+
error: "Failed to connect to auth storage"
|
|
774
1097
|
};
|
|
775
1098
|
}
|
|
776
|
-
const authAdapter = new RedisAuthAdapter({
|
|
777
|
-
url: redisUrl,
|
|
778
|
-
host: redisHost,
|
|
779
|
-
port: redisPort,
|
|
780
|
-
password: redisPassword
|
|
781
|
-
});
|
|
782
1099
|
const passwordPolicy = new PasswordPolicy();
|
|
783
1100
|
const passwordValidation = passwordPolicy.validate(adminPassword);
|
|
784
1101
|
if (!passwordValidation.valid) {
|
|
1102
|
+
await authAdapter.disconnect?.();
|
|
785
1103
|
return {
|
|
786
1104
|
success: false,
|
|
787
1105
|
error: `Invalid password: ${passwordValidation.errors.join(", ")}`
|
|
@@ -789,6 +1107,7 @@ async function bootstrapAdmin(config) {
|
|
|
789
1107
|
}
|
|
790
1108
|
const existingUser = await authAdapter.findUserByEmail(adminEmail);
|
|
791
1109
|
if (existingUser) {
|
|
1110
|
+
await authAdapter.disconnect?.();
|
|
792
1111
|
return {
|
|
793
1112
|
success: false,
|
|
794
1113
|
error: "Admin user already exists"
|
|
@@ -811,23 +1130,21 @@ async function bootstrapAdmin(config) {
|
|
|
811
1130
|
...welcomeTemplate
|
|
812
1131
|
});
|
|
813
1132
|
}
|
|
1133
|
+
await authAdapter.disconnect?.();
|
|
814
1134
|
return {
|
|
815
1135
|
success: true,
|
|
816
1136
|
user
|
|
817
1137
|
};
|
|
818
1138
|
} catch (error) {
|
|
1139
|
+
await authAdapter.disconnect?.();
|
|
819
1140
|
return {
|
|
820
1141
|
success: false,
|
|
821
1142
|
error: error instanceof Error ? error.message : "Failed to create admin user"
|
|
822
1143
|
};
|
|
823
|
-
} finally {
|
|
824
|
-
await redis.quit();
|
|
825
1144
|
}
|
|
826
1145
|
}
|
|
827
|
-
async function checkBootstrapRequired(
|
|
828
|
-
const existingUser = await
|
|
829
|
-
`kyro:auth:users:email:${adminEmail.toLowerCase()}`
|
|
830
|
-
);
|
|
1146
|
+
async function checkBootstrapRequired(authAdapter, adminEmail) {
|
|
1147
|
+
const existingUser = await authAdapter.findUserByEmail(adminEmail);
|
|
831
1148
|
return !existingUser;
|
|
832
1149
|
}
|
|
833
1150
|
function getBootstrapFromEnv() {
|
|
@@ -837,10 +1154,7 @@ function getBootstrapFromEnv() {
|
|
|
837
1154
|
return null;
|
|
838
1155
|
}
|
|
839
1156
|
return {
|
|
840
|
-
|
|
841
|
-
redisHost: process.env.REDIS_HOST,
|
|
842
|
-
redisPort: process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT, 10) : void 0,
|
|
843
|
-
redisPassword: process.env.REDIS_PASSWORD,
|
|
1157
|
+
authDbPath: process.env.KYRO_AUTH_DB_PATH || "./data/auth.db",
|
|
844
1158
|
adminEmail: email,
|
|
845
1159
|
adminPassword: password,
|
|
846
1160
|
adminRole: process.env.KYRO_ADMIN_ROLE || "super_admin",
|
|
@@ -894,6 +1208,6 @@ async function bootstrapWithRetry(config, maxRetries = 3, retryDelayMs = 2e3) {
|
|
|
894
1208
|
};
|
|
895
1209
|
}
|
|
896
1210
|
|
|
897
|
-
export { EmailTransport, PasswordPolicy,
|
|
898
|
-
//# sourceMappingURL=chunk-
|
|
899
|
-
//# sourceMappingURL=chunk-
|
|
1211
|
+
export { EmailTransport, PasswordPolicy, SQLiteAuthAdapter, autoBootstrap, bootstrapAdmin, bootstrapWithRetry, checkBootstrapRequired, getBootstrapFromEnv };
|
|
1212
|
+
//# sourceMappingURL=chunk-C74MQIRL.js.map
|
|
1213
|
+
//# sourceMappingURL=chunk-C74MQIRL.js.map
|