@guren/server 1.0.0-rc.25 → 1.0.0-rc.26
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/{Application-Hlryz3ln.d.ts → Application-BnsyCKXY.d.ts} +3 -2
- package/dist/AuthManager-SfhCNkAU.d.ts +79 -0
- package/dist/{ConsoleKernel-CVvaK-I6.d.ts → ConsoleKernel-BDtBETjm.d.ts} +1 -1
- package/dist/{api-token-ChekhpB7.d.ts → api-token-BSSCLlFW.d.ts} +6 -76
- package/dist/auth/index.d.ts +6 -327
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-J2ZAO6TV.js → chunk-2T6JN4VR.js} +20 -0
- package/dist/{chunk-X4E2I4Z5.js → chunk-NRLZJILN.js} +12 -5
- package/dist/chunk-UY3AZSYL.js +14 -0
- package/dist/client-CKXJLsTe.d.ts +232 -0
- package/dist/email-verification-CAeArjui.d.ts +327 -0
- package/dist/index.d.ts +27 -238
- package/dist/index.js +25 -20
- package/dist/lambda/index.d.ts +4 -3
- package/dist/mcp/index.d.ts +3 -2
- package/dist/notifications/index.d.ts +4 -3
- package/dist/queue/index.d.ts +4 -3
- package/dist/redis/index.d.ts +366 -0
- package/dist/redis/index.js +597 -0
- package/dist/runtime/index.d.ts +4 -3
- package/dist/runtime/index.js +5 -3
- package/package.json +12 -5
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Redis,
|
|
3
|
+
createRedisClient
|
|
4
|
+
} from "../chunk-UY3AZSYL.js";
|
|
5
|
+
|
|
6
|
+
// src/redis/scan-keys.ts
|
|
7
|
+
async function scanKeys(redis, pattern) {
|
|
8
|
+
const keys = [];
|
|
9
|
+
let cursor = "0";
|
|
10
|
+
do {
|
|
11
|
+
const [newCursor, foundKeys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
12
|
+
cursor = newCursor;
|
|
13
|
+
keys.push(...foundKeys);
|
|
14
|
+
} while (cursor !== "0");
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/redis/RedisSessionStore.ts
|
|
19
|
+
var RedisSessionStore = class {
|
|
20
|
+
constructor(redis, options = {}) {
|
|
21
|
+
this.redis = redis;
|
|
22
|
+
this.prefix = options.prefix ?? "session:";
|
|
23
|
+
}
|
|
24
|
+
prefix;
|
|
25
|
+
/**
|
|
26
|
+
* Read session data from Redis.
|
|
27
|
+
*/
|
|
28
|
+
async read(id) {
|
|
29
|
+
const key = this.prefix + id;
|
|
30
|
+
const data = await this.redis.get(key);
|
|
31
|
+
if (!data) {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(data);
|
|
36
|
+
} catch {
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write session data to Redis with TTL.
|
|
42
|
+
*/
|
|
43
|
+
async write(id, data, ttlSeconds) {
|
|
44
|
+
const key = this.prefix + id;
|
|
45
|
+
const serialized = JSON.stringify(data);
|
|
46
|
+
await this.redis.setex(key, ttlSeconds, serialized);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Delete a session from Redis.
|
|
50
|
+
*/
|
|
51
|
+
async destroy(id) {
|
|
52
|
+
const key = this.prefix + id;
|
|
53
|
+
await this.redis.del(key);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get all session keys (for debugging/admin purposes).
|
|
57
|
+
* Note: This uses SCAN to avoid blocking Redis.
|
|
58
|
+
*/
|
|
59
|
+
async keys() {
|
|
60
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
61
|
+
return keys.map((key) => key.slice(this.prefix.length));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the count of sessions (for monitoring).
|
|
65
|
+
*/
|
|
66
|
+
async size() {
|
|
67
|
+
const keys = await this.keys();
|
|
68
|
+
return keys.length;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clear all sessions.
|
|
72
|
+
* Use with caution in production!
|
|
73
|
+
*/
|
|
74
|
+
async clear() {
|
|
75
|
+
const keys = await this.keys();
|
|
76
|
+
if (keys.length > 0) {
|
|
77
|
+
const prefixedKeys = keys.map((key) => this.prefix + key);
|
|
78
|
+
await this.redis.del(...prefixedKeys);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/redis/RedisRateLimitStore.ts
|
|
84
|
+
var INCREMENT_SCRIPT = `
|
|
85
|
+
local key = KEYS[1]
|
|
86
|
+
local window = tonumber(ARGV[1])
|
|
87
|
+
local now = tonumber(ARGV[2])
|
|
88
|
+
|
|
89
|
+
local count = redis.call('INCR', key)
|
|
90
|
+
if count == 1 then
|
|
91
|
+
redis.call('PEXPIRE', key, window)
|
|
92
|
+
end
|
|
93
|
+
local ttl = redis.call('PTTL', key)
|
|
94
|
+
local resetAt = now + ttl
|
|
95
|
+
return {count, resetAt}
|
|
96
|
+
`;
|
|
97
|
+
var RedisRateLimitStore = class {
|
|
98
|
+
constructor(redis, options = {}) {
|
|
99
|
+
this.redis = redis;
|
|
100
|
+
this.prefix = options.prefix ?? "ratelimit:";
|
|
101
|
+
}
|
|
102
|
+
prefix;
|
|
103
|
+
/**
|
|
104
|
+
* Get current rate limit entry for a key.
|
|
105
|
+
*/
|
|
106
|
+
async get(key) {
|
|
107
|
+
const redisKey = this.prefix + key;
|
|
108
|
+
const [countStr, ttl] = await Promise.all([
|
|
109
|
+
this.redis.get(redisKey),
|
|
110
|
+
this.redis.pttl(redisKey)
|
|
111
|
+
]);
|
|
112
|
+
if (!countStr || ttl <= 0) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
count: parseInt(countStr, 10),
|
|
117
|
+
resetAt: Date.now() + ttl
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Increment the rate limit counter atomically.
|
|
122
|
+
*/
|
|
123
|
+
async increment(key, windowMs) {
|
|
124
|
+
const redisKey = this.prefix + key;
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const result = await this.redis.eval(
|
|
127
|
+
INCREMENT_SCRIPT,
|
|
128
|
+
1,
|
|
129
|
+
redisKey,
|
|
130
|
+
windowMs.toString(),
|
|
131
|
+
now.toString()
|
|
132
|
+
);
|
|
133
|
+
return {
|
|
134
|
+
count: result[0],
|
|
135
|
+
resetAt: result[1]
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Reset the rate limit for a key.
|
|
140
|
+
*/
|
|
141
|
+
async reset(key) {
|
|
142
|
+
const redisKey = this.prefix + key;
|
|
143
|
+
await this.redis.del(redisKey);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get all rate limit keys (for debugging/admin purposes).
|
|
147
|
+
*/
|
|
148
|
+
async keys() {
|
|
149
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
150
|
+
return keys.map((key) => key.slice(this.prefix.length));
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Clear all rate limits.
|
|
154
|
+
*/
|
|
155
|
+
async clear() {
|
|
156
|
+
const keys = await this.keys();
|
|
157
|
+
if (keys.length > 0) {
|
|
158
|
+
const prefixedKeys = keys.map((key) => this.prefix + key);
|
|
159
|
+
await this.redis.del(...prefixedKeys);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var RedisSlidingWindowRateLimitStore = class {
|
|
164
|
+
constructor(redis, options = {}) {
|
|
165
|
+
this.redis = redis;
|
|
166
|
+
this.prefix = options.prefix ?? "ratelimit:sw:";
|
|
167
|
+
}
|
|
168
|
+
prefix;
|
|
169
|
+
/**
|
|
170
|
+
* Get current rate limit entry for a key.
|
|
171
|
+
*/
|
|
172
|
+
async get(key) {
|
|
173
|
+
const redisKey = this.prefix + key;
|
|
174
|
+
const count = await this.redis.zcard(redisKey);
|
|
175
|
+
if (count === 0) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const ttl = await this.redis.pttl(redisKey);
|
|
179
|
+
return {
|
|
180
|
+
count,
|
|
181
|
+
resetAt: ttl > 0 ? Date.now() + ttl : Date.now()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Increment the rate limit counter using sorted set.
|
|
186
|
+
* Each request is stored with its timestamp as score.
|
|
187
|
+
*/
|
|
188
|
+
async increment(key, windowMs) {
|
|
189
|
+
const redisKey = this.prefix + key;
|
|
190
|
+
const now = Date.now();
|
|
191
|
+
const windowStart = now - windowMs;
|
|
192
|
+
const pipeline = this.redis.pipeline();
|
|
193
|
+
pipeline.zremrangebyscore(redisKey, "-inf", windowStart);
|
|
194
|
+
pipeline.zadd(redisKey, now, `${now}:${Math.random()}`);
|
|
195
|
+
pipeline.zcard(redisKey);
|
|
196
|
+
pipeline.pexpire(redisKey, windowMs);
|
|
197
|
+
const results = await pipeline.exec();
|
|
198
|
+
const count = results?.[2]?.[1] ?? 1;
|
|
199
|
+
return {
|
|
200
|
+
count,
|
|
201
|
+
resetAt: now + windowMs
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Reset the rate limit for a key.
|
|
206
|
+
*/
|
|
207
|
+
async reset(key) {
|
|
208
|
+
const redisKey = this.prefix + key;
|
|
209
|
+
await this.redis.del(redisKey);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Clear all rate limits.
|
|
213
|
+
*/
|
|
214
|
+
async clear() {
|
|
215
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
216
|
+
if (keys.length > 0) {
|
|
217
|
+
await this.redis.del(...keys);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/redis/RedisApiTokenStore.ts
|
|
223
|
+
var RedisApiTokenStore = class {
|
|
224
|
+
constructor(redis, options = {}) {
|
|
225
|
+
this.redis = redis;
|
|
226
|
+
this.prefix = options.prefix ?? "apitoken:";
|
|
227
|
+
}
|
|
228
|
+
prefix;
|
|
229
|
+
/**
|
|
230
|
+
* Store a new API token.
|
|
231
|
+
*/
|
|
232
|
+
async store(token) {
|
|
233
|
+
const tokenKey = `${this.prefix}${token.id}`;
|
|
234
|
+
const hashKey = `${this.prefix}hash:${token.hashedToken}`;
|
|
235
|
+
const userKey = `${this.prefix}user:${token.userId}`;
|
|
236
|
+
const serialized = this.serializeToken(token);
|
|
237
|
+
const pipeline = this.redis.pipeline();
|
|
238
|
+
pipeline.hset(tokenKey, serialized);
|
|
239
|
+
pipeline.set(hashKey, token.id);
|
|
240
|
+
pipeline.sadd(userKey, token.id);
|
|
241
|
+
if (token.expiresAt) {
|
|
242
|
+
const ttl = Math.max(0, token.expiresAt.getTime() - Date.now());
|
|
243
|
+
if (ttl > 0) {
|
|
244
|
+
pipeline.pexpire(tokenKey, ttl);
|
|
245
|
+
pipeline.pexpire(hashKey, ttl);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
await pipeline.exec();
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Find a token by its hashed value.
|
|
252
|
+
*/
|
|
253
|
+
async findByHashedToken(hashedToken) {
|
|
254
|
+
const hashKey = `${this.prefix}hash:${hashedToken}`;
|
|
255
|
+
const tokenId = await this.redis.get(hashKey);
|
|
256
|
+
if (!tokenId) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
return this.findById(tokenId);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find all tokens for a user.
|
|
263
|
+
*/
|
|
264
|
+
async findByUserId(userId) {
|
|
265
|
+
const userKey = `${this.prefix}user:${userId}`;
|
|
266
|
+
const tokenIds = await this.redis.smembers(userKey);
|
|
267
|
+
const tokens = [];
|
|
268
|
+
for (const id of tokenIds) {
|
|
269
|
+
const token = await this.findById(id);
|
|
270
|
+
if (token) {
|
|
271
|
+
tokens.push(token);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return tokens;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Delete a token by its ID.
|
|
278
|
+
*/
|
|
279
|
+
async delete(id) {
|
|
280
|
+
const token = await this.findById(id);
|
|
281
|
+
if (!token) return;
|
|
282
|
+
const tokenKey = `${this.prefix}${id}`;
|
|
283
|
+
const hashKey = `${this.prefix}hash:${token.hashedToken}`;
|
|
284
|
+
const userKey = `${this.prefix}user:${token.userId}`;
|
|
285
|
+
const pipeline = this.redis.pipeline();
|
|
286
|
+
pipeline.del(tokenKey);
|
|
287
|
+
pipeline.del(hashKey);
|
|
288
|
+
pipeline.srem(userKey, id);
|
|
289
|
+
await pipeline.exec();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Delete all tokens for a user.
|
|
293
|
+
*/
|
|
294
|
+
async deleteForUser(userId) {
|
|
295
|
+
const tokens = await this.findByUserId(userId);
|
|
296
|
+
for (const token of tokens) {
|
|
297
|
+
await this.delete(token.id);
|
|
298
|
+
}
|
|
299
|
+
const userKey = `${this.prefix}user:${userId}`;
|
|
300
|
+
await this.redis.del(userKey);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Update the last used timestamp.
|
|
304
|
+
*/
|
|
305
|
+
async updateLastUsed(id, timestamp) {
|
|
306
|
+
const tokenKey = `${this.prefix}${id}`;
|
|
307
|
+
await this.redis.hset(tokenKey, "lastUsedAt", timestamp.toISOString());
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Find a token by its ID.
|
|
311
|
+
*/
|
|
312
|
+
async findById(id) {
|
|
313
|
+
const tokenKey = `${this.prefix}${id}`;
|
|
314
|
+
const data = await this.redis.hgetall(tokenKey);
|
|
315
|
+
if (!data || Object.keys(data).length === 0) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return this.deserializeToken(data);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Serialize a token for Redis storage.
|
|
322
|
+
*/
|
|
323
|
+
serializeToken(token) {
|
|
324
|
+
return {
|
|
325
|
+
id: token.id,
|
|
326
|
+
name: token.name,
|
|
327
|
+
hashedToken: token.hashedToken,
|
|
328
|
+
userId: String(token.userId),
|
|
329
|
+
abilities: JSON.stringify(token.abilities),
|
|
330
|
+
lastUsedAt: token.lastUsedAt?.toISOString() ?? "",
|
|
331
|
+
expiresAt: token.expiresAt?.toISOString() ?? "",
|
|
332
|
+
createdAt: token.createdAt.toISOString()
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Deserialize a token from Redis storage.
|
|
337
|
+
*/
|
|
338
|
+
deserializeToken(data) {
|
|
339
|
+
return {
|
|
340
|
+
id: data.id,
|
|
341
|
+
name: data.name,
|
|
342
|
+
hashedToken: data.hashedToken,
|
|
343
|
+
userId: data.userId,
|
|
344
|
+
abilities: JSON.parse(data.abilities),
|
|
345
|
+
lastUsedAt: data.lastUsedAt ? new Date(data.lastUsedAt) : null,
|
|
346
|
+
expiresAt: data.expiresAt ? new Date(data.expiresAt) : null,
|
|
347
|
+
createdAt: new Date(data.createdAt)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Clear all tokens (for testing).
|
|
352
|
+
*/
|
|
353
|
+
async clear() {
|
|
354
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
355
|
+
if (keys.length > 0) {
|
|
356
|
+
await this.redis.del(...keys);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/redis/RedisPasswordResetStore.ts
|
|
362
|
+
var RedisPasswordResetStore = class {
|
|
363
|
+
constructor(redis, options = {}) {
|
|
364
|
+
this.redis = redis;
|
|
365
|
+
this.prefix = options.prefix ?? "pwreset:";
|
|
366
|
+
}
|
|
367
|
+
prefix;
|
|
368
|
+
/**
|
|
369
|
+
* Store a password reset token.
|
|
370
|
+
*/
|
|
371
|
+
async store(tokenId, email, expiresAt) {
|
|
372
|
+
const tokenKey = `${this.prefix}${tokenId}`;
|
|
373
|
+
const emailKey = `${this.prefix}email:${email.toLowerCase()}`;
|
|
374
|
+
const ttlMs = Math.max(0, expiresAt.getTime() - Date.now());
|
|
375
|
+
if (ttlMs <= 0) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const data = JSON.stringify({ email, expiresAt: expiresAt.toISOString() });
|
|
379
|
+
const pipeline = this.redis.pipeline();
|
|
380
|
+
pipeline.psetex(tokenKey, ttlMs, data);
|
|
381
|
+
pipeline.sadd(emailKey, tokenId);
|
|
382
|
+
pipeline.pexpire(emailKey, ttlMs + 6e4);
|
|
383
|
+
await pipeline.exec();
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Find a token by its hash.
|
|
387
|
+
*/
|
|
388
|
+
async find(tokenId) {
|
|
389
|
+
const tokenKey = `${this.prefix}${tokenId}`;
|
|
390
|
+
const data = await this.redis.get(tokenKey);
|
|
391
|
+
if (!data) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
const parsed = JSON.parse(data);
|
|
396
|
+
return {
|
|
397
|
+
email: parsed.email,
|
|
398
|
+
expiresAt: new Date(parsed.expiresAt)
|
|
399
|
+
};
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Delete a token by its hash.
|
|
406
|
+
*/
|
|
407
|
+
async delete(tokenId) {
|
|
408
|
+
const tokenKey = `${this.prefix}${tokenId}`;
|
|
409
|
+
const data = await this.redis.get(tokenKey);
|
|
410
|
+
if (data) {
|
|
411
|
+
try {
|
|
412
|
+
const parsed = JSON.parse(data);
|
|
413
|
+
const emailKey = `${this.prefix}email:${parsed.email.toLowerCase()}`;
|
|
414
|
+
await this.redis.srem(emailKey, tokenId);
|
|
415
|
+
} catch {
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
await this.redis.del(tokenKey);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Delete all tokens for an email.
|
|
422
|
+
*/
|
|
423
|
+
async deleteForEmail(email) {
|
|
424
|
+
const emailKey = `${this.prefix}email:${email.toLowerCase()}`;
|
|
425
|
+
const tokenIds = await this.redis.smembers(emailKey);
|
|
426
|
+
if (tokenIds.length > 0) {
|
|
427
|
+
const tokenKeys = tokenIds.map((tokenId) => `${this.prefix}${tokenId}`);
|
|
428
|
+
await this.redis.del(...tokenKeys, emailKey);
|
|
429
|
+
} else {
|
|
430
|
+
await this.redis.del(emailKey);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Clear all tokens (for testing).
|
|
435
|
+
*/
|
|
436
|
+
async clear() {
|
|
437
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
438
|
+
if (keys.length > 0) {
|
|
439
|
+
await this.redis.del(...keys);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// src/redis/RedisEmailVerificationStore.ts
|
|
445
|
+
var RedisEmailVerificationStore = class {
|
|
446
|
+
constructor(redis, options = {}) {
|
|
447
|
+
this.redis = redis;
|
|
448
|
+
this.prefix = options.prefix ?? "emailverify:";
|
|
449
|
+
}
|
|
450
|
+
prefix;
|
|
451
|
+
/**
|
|
452
|
+
* Store an email verification token.
|
|
453
|
+
*/
|
|
454
|
+
async store(token) {
|
|
455
|
+
const tokenKey = `${this.prefix}${token.tokenId}`;
|
|
456
|
+
const emailKey = `${this.prefix}email:${token.email.toLowerCase()}`;
|
|
457
|
+
const ttlMs = Math.max(0, token.expiresAt.getTime() - Date.now());
|
|
458
|
+
if (ttlMs <= 0) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const data = JSON.stringify({
|
|
462
|
+
tokenId: token.tokenId,
|
|
463
|
+
email: token.email,
|
|
464
|
+
expiresAt: token.expiresAt.toISOString(),
|
|
465
|
+
createdAt: token.createdAt.toISOString()
|
|
466
|
+
});
|
|
467
|
+
const pipeline = this.redis.pipeline();
|
|
468
|
+
pipeline.psetex(tokenKey, ttlMs, data);
|
|
469
|
+
pipeline.sadd(emailKey, token.tokenId);
|
|
470
|
+
pipeline.pexpire(emailKey, ttlMs + 6e4);
|
|
471
|
+
await pipeline.exec();
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Find a token by its hash.
|
|
475
|
+
*/
|
|
476
|
+
async findByTokenId(tokenId) {
|
|
477
|
+
const tokenKey = `${this.prefix}${tokenId}`;
|
|
478
|
+
const data = await this.redis.get(tokenKey);
|
|
479
|
+
if (!data) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const parsed = JSON.parse(data);
|
|
484
|
+
return {
|
|
485
|
+
tokenId: parsed.tokenId,
|
|
486
|
+
email: parsed.email,
|
|
487
|
+
expiresAt: new Date(parsed.expiresAt),
|
|
488
|
+
createdAt: new Date(parsed.createdAt)
|
|
489
|
+
};
|
|
490
|
+
} catch {
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Delete a token by its hash.
|
|
496
|
+
*/
|
|
497
|
+
async delete(tokenId) {
|
|
498
|
+
const tokenKey = `${this.prefix}${tokenId}`;
|
|
499
|
+
const data = await this.redis.get(tokenKey);
|
|
500
|
+
if (data) {
|
|
501
|
+
try {
|
|
502
|
+
const parsed = JSON.parse(data);
|
|
503
|
+
const emailKey = `${this.prefix}email:${parsed.email.toLowerCase()}`;
|
|
504
|
+
await this.redis.srem(emailKey, tokenId);
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
await this.redis.del(tokenKey);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Delete all tokens for an email.
|
|
512
|
+
*/
|
|
513
|
+
async deleteForEmail(email) {
|
|
514
|
+
const emailKey = `${this.prefix}email:${email.toLowerCase()}`;
|
|
515
|
+
const tokenIds = await this.redis.smembers(emailKey);
|
|
516
|
+
if (tokenIds.length > 0) {
|
|
517
|
+
const tokenKeys = tokenIds.map((tokenId) => `${this.prefix}${tokenId}`);
|
|
518
|
+
await this.redis.del(...tokenKeys, emailKey);
|
|
519
|
+
} else {
|
|
520
|
+
await this.redis.del(emailKey);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Clear all tokens (for testing).
|
|
525
|
+
*/
|
|
526
|
+
async clear() {
|
|
527
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
528
|
+
if (keys.length > 0) {
|
|
529
|
+
await this.redis.del(...keys);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/redis/RedisOAuthStateStore.ts
|
|
535
|
+
var RedisOAuthStateStore = class {
|
|
536
|
+
constructor(redis, options = {}) {
|
|
537
|
+
this.redis = redis;
|
|
538
|
+
this.prefix = options.prefix ?? "oauthstate:";
|
|
539
|
+
}
|
|
540
|
+
prefix;
|
|
541
|
+
async store(stateHash, payload) {
|
|
542
|
+
const ttlMs = Math.max(0, payload.expiresAt.getTime() - Date.now());
|
|
543
|
+
if (ttlMs <= 0) {
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const data = JSON.stringify({
|
|
547
|
+
provider: payload.provider,
|
|
548
|
+
redirectTo: payload.redirectTo,
|
|
549
|
+
expiresAt: payload.expiresAt.toISOString()
|
|
550
|
+
});
|
|
551
|
+
await this.redis.psetex(`${this.prefix}${stateHash}`, ttlMs, data);
|
|
552
|
+
}
|
|
553
|
+
async find(stateHash) {
|
|
554
|
+
const data = await this.redis.get(`${this.prefix}${stateHash}`);
|
|
555
|
+
if (!data) {
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
try {
|
|
559
|
+
const parsed = JSON.parse(data);
|
|
560
|
+
const payload = {
|
|
561
|
+
provider: parsed.provider,
|
|
562
|
+
redirectTo: parsed.redirectTo,
|
|
563
|
+
expiresAt: new Date(parsed.expiresAt)
|
|
564
|
+
};
|
|
565
|
+
if (payload.expiresAt.getTime() <= Date.now()) {
|
|
566
|
+
await this.delete(stateHash);
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
return payload;
|
|
570
|
+
} catch {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async delete(stateHash) {
|
|
575
|
+
await this.redis.del(`${this.prefix}${stateHash}`);
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Clear all states (for testing).
|
|
579
|
+
*/
|
|
580
|
+
async clear() {
|
|
581
|
+
const keys = await scanKeys(this.redis, this.prefix + "*");
|
|
582
|
+
if (keys.length > 0) {
|
|
583
|
+
await this.redis.del(...keys);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
export {
|
|
588
|
+
Redis,
|
|
589
|
+
RedisApiTokenStore,
|
|
590
|
+
RedisEmailVerificationStore,
|
|
591
|
+
RedisOAuthStateStore,
|
|
592
|
+
RedisPasswordResetStore,
|
|
593
|
+
RedisRateLimitStore,
|
|
594
|
+
RedisSessionStore,
|
|
595
|
+
RedisSlidingWindowRateLimitStore,
|
|
596
|
+
createRedisClient
|
|
597
|
+
};
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { A as Application } from '../Application-
|
|
2
|
-
export { D as DevBannerOptions, G as GUREN_ASCII_ART, a as StartViteDevServerOptions, b as StartedViteDevServer, l as logDevServerBanner, s as startViteDevServer } from '../Application-
|
|
1
|
+
import { A as Application } from '../Application-BnsyCKXY.js';
|
|
2
|
+
export { D as DevBannerOptions, G as GUREN_ASCII_ART, a as StartViteDevServerOptions, b as StartedViteDevServer, l as logDevServerBanner, s as startViteDevServer } from '../Application-BnsyCKXY.js';
|
|
3
3
|
import 'hono';
|
|
4
|
-
import '../api-token-
|
|
4
|
+
import '../api-token-BSSCLlFW.js';
|
|
5
|
+
import '../AuthManager-SfhCNkAU.js';
|
|
5
6
|
import '@guren/orm';
|
|
6
7
|
import 'vite';
|
|
7
8
|
import '../EventManager-CmIoLt7r.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
logDevServerBanner,
|
|
4
4
|
parseImportMap,
|
|
5
5
|
startViteDevServer
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-NRLZJILN.js";
|
|
7
7
|
import {
|
|
8
8
|
hash
|
|
9
9
|
} from "../chunk-QQKTH5KX.js";
|
|
@@ -110,7 +110,9 @@ var DEFAULT_PREFIX = "/resources/js";
|
|
|
110
110
|
var DEFAULT_VENDOR_PATH = "/vendor/inertia-client.tsx";
|
|
111
111
|
var DEFAULT_JSX_RUNTIME = "https://esm.sh/react@19.0.0/jsx-dev-runtime?dev";
|
|
112
112
|
var require2 = createRequire(import.meta.url);
|
|
113
|
-
|
|
113
|
+
function resolveGurenInertiaClient() {
|
|
114
|
+
return require2.resolve("@guren/inertia-client/app");
|
|
115
|
+
}
|
|
114
116
|
function registerDevAssets(app, options) {
|
|
115
117
|
if (typeof Bun === "undefined") {
|
|
116
118
|
throw new Error("Bun runtime is required for dev asset serving.");
|
|
@@ -124,7 +126,7 @@ function registerDevAssets(app, options) {
|
|
|
124
126
|
const cssDir = options.cssDir ?? resolve2(resourcesDir, "css");
|
|
125
127
|
const cssRoute = options.cssRoute ?? deriveCssRoute(prefix);
|
|
126
128
|
const inertiaClientPath = options.inertiaClientPath ?? DEFAULT_VENDOR_PATH;
|
|
127
|
-
const inertiaClientSource = options.inertiaClientSource ??
|
|
129
|
+
const inertiaClientSource = options.inertiaClientSource ?? resolveGurenInertiaClient();
|
|
128
130
|
const jsxRuntimeUrl = options.jsxRuntimeUrl ?? DEFAULT_JSX_RUNTIME;
|
|
129
131
|
const resourcesJsDir = resolve2(resourcesDir, "js");
|
|
130
132
|
const reactImportPattern = /from\s+['"]react['"]/u;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guren/server",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -96,6 +96,10 @@
|
|
|
96
96
|
"./lambda": {
|
|
97
97
|
"types": "./dist/lambda/index.d.ts",
|
|
98
98
|
"default": "./dist/lambda/index.js"
|
|
99
|
+
},
|
|
100
|
+
"./redis": {
|
|
101
|
+
"types": "./dist/redis/index.d.ts",
|
|
102
|
+
"default": "./dist/redis/index.js"
|
|
99
103
|
}
|
|
100
104
|
},
|
|
101
105
|
"scripts": {
|
|
@@ -105,21 +109,21 @@
|
|
|
105
109
|
"test": "bun test"
|
|
106
110
|
},
|
|
107
111
|
"dependencies": {
|
|
108
|
-
"@guren/
|
|
109
|
-
"@guren/orm": "^1.0.0-rc.26",
|
|
112
|
+
"@guren/orm": "^1.0.0-rc.27",
|
|
110
113
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
111
114
|
"chalk": "^5.3.0",
|
|
112
115
|
"consola": "^3.4.2",
|
|
113
116
|
"figlet": "^1.7.0",
|
|
114
117
|
"hono": "^4.12.18",
|
|
115
118
|
"ioredis": "^5.9.1",
|
|
116
|
-
"nodemailer": "^
|
|
119
|
+
"nodemailer": "^9.0.1"
|
|
117
120
|
},
|
|
118
121
|
"peerDependencies": {
|
|
119
122
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
120
123
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
121
124
|
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
|
122
|
-
"vite": ">=7.0.0"
|
|
125
|
+
"vite": ">=7.0.0",
|
|
126
|
+
"@guren/inertia-client": "^1.0.0-rc.25"
|
|
123
127
|
},
|
|
124
128
|
"peerDependenciesMeta": {
|
|
125
129
|
"@aws-sdk/client-s3": {
|
|
@@ -130,6 +134,9 @@
|
|
|
130
134
|
},
|
|
131
135
|
"@aws-sdk/s3-request-presigner": {
|
|
132
136
|
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@guren/inertia-client": {
|
|
139
|
+
"optional": true
|
|
133
140
|
}
|
|
134
141
|
},
|
|
135
142
|
"devDependencies": {
|