@rebasepro/server-mongo 0.10.0 → 0.10.1-canary.14e53ae
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/auth/services.d.ts +21 -4
- package/dist/auth/services.d.ts.map +1 -1
- package/dist/index.es.js +88 -31
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -5
- package/src/auth/services.ts +114 -32
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-mongo",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.1-canary.14e53ae",
|
|
5
5
|
"description": "MongoDB backend for Rebase",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"mongodb": "^7.3.0",
|
|
42
42
|
"ws": "^8.21.0",
|
|
43
|
-
"@rebasepro/
|
|
44
|
-
"@rebasepro/
|
|
45
|
-
"@rebasepro/
|
|
43
|
+
"@rebasepro/utils": "0.10.1-canary.14e53ae",
|
|
44
|
+
"@rebasepro/common": "0.10.1-canary.14e53ae",
|
|
45
|
+
"@rebasepro/types": "0.10.1-canary.14e53ae"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/jest": "^30.0.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"vite": "^8.0.16"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@rebasepro/server": "0.10.
|
|
58
|
+
"@rebasepro/server": "0.10.1-canary.14e53ae"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@rebasepro/server": {
|
package/src/auth/services.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
RoleData,
|
|
13
13
|
CreateRoleData,
|
|
14
14
|
RefreshTokenInfo,
|
|
15
|
+
RefreshTokenSession,
|
|
15
16
|
PasswordResetTokenInfo,
|
|
16
17
|
MagicLinkTokenInfo,
|
|
17
18
|
UserIdentityData,
|
|
@@ -336,40 +337,89 @@ export class MongoRefreshTokenService {
|
|
|
336
337
|
return this.db.collection<MongoDoc>("rebase_refresh_tokens");
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
|
|
340
|
+
private toInfo(doc: MongoDoc): RefreshTokenInfo {
|
|
341
|
+
return {
|
|
342
|
+
id: doc.id,
|
|
343
|
+
uid: doc.uid,
|
|
344
|
+
tokenHash: doc.tokenHash,
|
|
345
|
+
expiresAt: new Date(doc.expiresAt),
|
|
346
|
+
createdAt: new Date(doc.createdAt),
|
|
347
|
+
userAgent: doc.userAgent,
|
|
348
|
+
ipAddress: doc.ipAddress,
|
|
349
|
+
sessionId: doc.sessionId,
|
|
350
|
+
rotatedAt: doc.rotatedAt ? new Date(doc.rotatedAt) : null,
|
|
351
|
+
revoked: Boolean(doc.revoked),
|
|
352
|
+
sessionStartedAt: new Date(doc.sessionStartedAt || doc.createdAt)
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async createToken(
|
|
357
|
+
uid: string,
|
|
358
|
+
tokenHash: string,
|
|
359
|
+
expiresAt: Date,
|
|
360
|
+
userAgent?: string,
|
|
361
|
+
ipAddress?: string,
|
|
362
|
+
session?: RefreshTokenSession
|
|
363
|
+
): Promise<void> {
|
|
340
364
|
const safeUserAgent = userAgent || "";
|
|
341
365
|
const safeIpAddress = ipAddress || "";
|
|
342
366
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
367
|
+
// No deleteMany first. Tokens of one sign-in accumulate under a shared
|
|
368
|
+
// sessionId and are pruned once nobody can still be holding them —
|
|
369
|
+
// evicting by (uid, userAgent, ipAddress) is what used to sign out a
|
|
370
|
+
// second browser profile behind the same address.
|
|
371
|
+
const now = new Date();
|
|
349
372
|
await this.collection.insertOne({
|
|
350
373
|
_id: new ObjectId().toString(),
|
|
351
374
|
id: new ObjectId().toString(),
|
|
352
375
|
uid,
|
|
353
376
|
tokenHash,
|
|
354
377
|
expiresAt,
|
|
355
|
-
createdAt:
|
|
378
|
+
createdAt: now,
|
|
356
379
|
userAgent: safeUserAgent,
|
|
357
|
-
ipAddress: safeIpAddress
|
|
380
|
+
ipAddress: safeIpAddress,
|
|
381
|
+
sessionId: session?.id ?? new ObjectId().toString(),
|
|
382
|
+
sessionStartedAt: session?.startedAt ?? now,
|
|
383
|
+
rotatedAt: null,
|
|
384
|
+
revoked: false
|
|
358
385
|
});
|
|
359
386
|
}
|
|
360
387
|
|
|
361
388
|
async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|
|
362
389
|
const doc = await this.collection.findOne({ tokenHash });
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
390
|
+
return doc ? this.toInfo(doc) : null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Superseded, not gone — see the Postgres service for why that matters. */
|
|
394
|
+
async markRotated(tokenHash: string): Promise<void> {
|
|
395
|
+
await this.collection.updateOne({ tokenHash }, { $set: { rotatedAt: new Date() } });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async revokeSession(sessionId: string): Promise<void> {
|
|
399
|
+
await this.collection.updateMany(
|
|
400
|
+
{ sessionId },
|
|
401
|
+
{ $set: { revoked: true, rotatedAt: new Date() } }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async prune(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
|
|
406
|
+
await this.collection.deleteMany({
|
|
407
|
+
uid,
|
|
408
|
+
$or: [
|
|
409
|
+
{ expiresAt: { $lt: new Date() } },
|
|
410
|
+
{ sessionId, rotatedAt: { $ne: null, $lt: supersededBefore } }
|
|
411
|
+
]
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async getTokensValidAfter(uid: string): Promise<Date | null> {
|
|
416
|
+
const user = await this.db.collection<MongoDoc>("rebase_users").findOne({ id: uid });
|
|
417
|
+
return user?.tokensValidAfter ? new Date(user.tokensValidAfter) : null;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async setTokensValidAfter(uid: string, at: Date): Promise<void> {
|
|
421
|
+
await this.db.collection<MongoDoc>("rebase_users")
|
|
422
|
+
.updateOne({ id: uid }, { $set: { tokensValidAfter: at } });
|
|
373
423
|
}
|
|
374
424
|
|
|
375
425
|
async deleteByHash(tokenHash: string): Promise<void> {
|
|
@@ -382,15 +432,7 @@ export class MongoRefreshTokenService {
|
|
|
382
432
|
|
|
383
433
|
async listForUser(uid: string): Promise<RefreshTokenInfo[]> {
|
|
384
434
|
const docs = await this.collection.find({ uid }).sort({ createdAt: 1 }).toArray();
|
|
385
|
-
return docs.map(doc => (
|
|
386
|
-
id: doc.id,
|
|
387
|
-
uid: doc.uid,
|
|
388
|
-
tokenHash: doc.tokenHash,
|
|
389
|
-
expiresAt: new Date(doc.expiresAt),
|
|
390
|
-
createdAt: new Date(doc.createdAt),
|
|
391
|
-
userAgent: doc.userAgent,
|
|
392
|
-
ipAddress: doc.ipAddress
|
|
393
|
-
}));
|
|
435
|
+
return docs.map(doc => this.toInfo(doc));
|
|
394
436
|
}
|
|
395
437
|
|
|
396
438
|
async deleteById(id: string, uid: string): Promise<void> {
|
|
@@ -459,8 +501,28 @@ export class MongoTokenRepository implements TokenRepository {
|
|
|
459
501
|
this.passwordResetTokenService = new MongoPasswordResetTokenService(db);
|
|
460
502
|
}
|
|
461
503
|
|
|
462
|
-
async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
|
|
463
|
-
await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
|
|
504
|
+
async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void> {
|
|
505
|
+
await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async markRefreshTokenRotated(tokenHash: string): Promise<void> {
|
|
509
|
+
await this.refreshTokenService.markRotated(tokenHash);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async revokeRefreshTokenSession(sessionId: string): Promise<void> {
|
|
513
|
+
await this.refreshTokenService.revokeSession(sessionId);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
|
|
517
|
+
await this.refreshTokenService.prune(uid, sessionId, supersededBefore);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async getTokensValidAfter(uid: string): Promise<Date | null> {
|
|
521
|
+
return this.refreshTokenService.getTokensValidAfter(uid);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async setTokensValidAfter(uid: string, at: Date): Promise<void> {
|
|
525
|
+
await this.refreshTokenService.setTokensValidAfter(uid, at);
|
|
464
526
|
}
|
|
465
527
|
|
|
466
528
|
async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|
|
@@ -631,8 +693,28 @@ export class MongoAuthRepository implements AuthRepository {
|
|
|
631
693
|
await this.roleService.deleteRole(id);
|
|
632
694
|
}
|
|
633
695
|
|
|
634
|
-
async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
|
|
635
|
-
await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
|
|
696
|
+
async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void> {
|
|
697
|
+
await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async markRefreshTokenRotated(tokenHash: string): Promise<void> {
|
|
701
|
+
await this.tokenRepository.markRefreshTokenRotated(tokenHash);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
async revokeRefreshTokenSession(sessionId: string): Promise<void> {
|
|
705
|
+
await this.tokenRepository.revokeRefreshTokenSession(sessionId);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
async pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void> {
|
|
709
|
+
await this.tokenRepository.pruneRefreshTokens(uid, sessionId, supersededBefore);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
async getTokensValidAfter(uid: string): Promise<Date | null> {
|
|
713
|
+
return this.tokenRepository.getTokensValidAfter(uid);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async setTokensValidAfter(uid: string, at: Date): Promise<void> {
|
|
717
|
+
await this.tokenRepository.setTokensValidAfter(uid, at);
|
|
636
718
|
}
|
|
637
719
|
|
|
638
720
|
async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
|