@rapidraptor/auth-server 0.2.0
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/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/firebase/admin.d.ts +18 -0
- package/dist/firebase/admin.d.ts.map +1 -0
- package/dist/firebase/admin.js +96 -0
- package/dist/firebase/admin.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/authMiddleware.d.ts +9 -0
- package/dist/middleware/authMiddleware.d.ts.map +1 -0
- package/dist/middleware/authMiddleware.js +241 -0
- package/dist/middleware/authMiddleware.js.map +1 -0
- package/dist/middleware/authMiddleware.test.d.ts +2 -0
- package/dist/middleware/authMiddleware.test.d.ts.map +1 -0
- package/dist/middleware/authMiddleware.test.js +691 -0
- package/dist/middleware/authMiddleware.test.js.map +1 -0
- package/dist/middleware/logoutHandler.d.ts +9 -0
- package/dist/middleware/logoutHandler.d.ts.map +1 -0
- package/dist/middleware/logoutHandler.js +54 -0
- package/dist/middleware/logoutHandler.js.map +1 -0
- package/dist/middleware/logoutHandler.test.d.ts +2 -0
- package/dist/middleware/logoutHandler.test.d.ts.map +1 -0
- package/dist/middleware/logoutHandler.test.js +103 -0
- package/dist/middleware/logoutHandler.test.js.map +1 -0
- package/dist/session/firestoreSync.d.ts +37 -0
- package/dist/session/firestoreSync.d.ts.map +1 -0
- package/dist/session/firestoreSync.js +88 -0
- package/dist/session/firestoreSync.js.map +1 -0
- package/dist/session/firestoreSync.test.d.ts +2 -0
- package/dist/session/firestoreSync.test.d.ts.map +1 -0
- package/dist/session/firestoreSync.test.js +142 -0
- package/dist/session/firestoreSync.test.js.map +1 -0
- package/dist/session/sessionCache.d.ts +37 -0
- package/dist/session/sessionCache.d.ts.map +1 -0
- package/dist/session/sessionCache.js +63 -0
- package/dist/session/sessionCache.js.map +1 -0
- package/dist/session/sessionCache.test.d.ts +2 -0
- package/dist/session/sessionCache.test.d.ts.map +1 -0
- package/dist/session/sessionCache.test.js +117 -0
- package/dist/session/sessionCache.test.js.map +1 -0
- package/dist/session/sessionService.d.ts +97 -0
- package/dist/session/sessionService.d.ts.map +1 -0
- package/dist/session/sessionService.js +311 -0
- package/dist/session/sessionService.js.map +1 -0
- package/dist/session/sessionService.test.d.ts +2 -0
- package/dist/session/sessionService.test.d.ts.map +1 -0
- package/dist/session/sessionService.test.js +426 -0
- package/dist/session/sessionService.test.js.map +1 -0
- package/dist/session/types.d.ts +7 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tokenVerifier/errors.d.ts +23 -0
- package/dist/tokenVerifier/errors.d.ts.map +1 -0
- package/dist/tokenVerifier/errors.js +34 -0
- package/dist/tokenVerifier/errors.js.map +1 -0
- package/dist/tokenVerifier/joseTokenVerifier.d.ts +24 -0
- package/dist/tokenVerifier/joseTokenVerifier.d.ts.map +1 -0
- package/dist/tokenVerifier/joseTokenVerifier.js +157 -0
- package/dist/tokenVerifier/joseTokenVerifier.js.map +1 -0
- package/dist/tokenVerifier/types.d.ts +41 -0
- package/dist/tokenVerifier/types.d.ts.map +1 -0
- package/dist/tokenVerifier/types.js +2 -0
- package/dist/tokenVerifier/types.js.map +1 -0
- package/dist/types/middleware.d.ts +33 -0
- package/dist/types/middleware.d.ts.map +1 -0
- package/dist/types/middleware.js +2 -0
- package/dist/types/middleware.js.map +1 -0
- package/dist/types/session.d.ts +7 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +2 -0
- package/dist/types/session.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory cache for fast session lookups
|
|
3
|
+
*/
|
|
4
|
+
export class SessionCache {
|
|
5
|
+
sessions;
|
|
6
|
+
constructor(_inactivityTimeout) {
|
|
7
|
+
// inactivityTimeout is kept for API compatibility but not used
|
|
8
|
+
// Expiration is determined by session.expiresAt
|
|
9
|
+
this.sessions = new Map();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get session from cache
|
|
13
|
+
*/
|
|
14
|
+
get(userId) {
|
|
15
|
+
return this.sessions.get(userId) || null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Store session in cache
|
|
19
|
+
*/
|
|
20
|
+
set(userId, session) {
|
|
21
|
+
this.sessions.set(userId, session);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check if session is expired
|
|
25
|
+
*/
|
|
26
|
+
isExpired(userId) {
|
|
27
|
+
const session = this.get(userId);
|
|
28
|
+
if (!session) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return new Date() > session.expiresAt;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Remove session from cache
|
|
35
|
+
*/
|
|
36
|
+
clear(userId) {
|
|
37
|
+
this.sessions.delete(userId);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Cleanup expired sessions from cache
|
|
41
|
+
*/
|
|
42
|
+
clearExpired() {
|
|
43
|
+
const now = new Date();
|
|
44
|
+
for (const [userId, session] of this.sessions.entries()) {
|
|
45
|
+
if (now > session.expiresAt) {
|
|
46
|
+
this.sessions.delete(userId);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get all cached session user IDs
|
|
52
|
+
*/
|
|
53
|
+
getAllUserIds() {
|
|
54
|
+
return Array.from(this.sessions.keys());
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Clear all sessions from cache
|
|
58
|
+
*/
|
|
59
|
+
clearAll() {
|
|
60
|
+
this.sessions.clear();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=sessionCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionCache.js","sourceRoot":"","sources":["../../src/session/sessionCache.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,OAAO,YAAY;IACf,QAAQ,CAAa;IAE7B,YAAY,kBAA0B;QACpC,+DAA+D;QAC/D,gDAAgD;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,GAAG,CAAC,MAAc,EAAE,OAAoB;QACtC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,MAAc;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,IAAI,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAc;QAClB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,YAAY;QACV,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YACxD,IAAI,GAAG,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionCache.test.d.ts","sourceRoot":"","sources":["../../src/session/sessionCache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { SessionCache } from './sessionCache.js';
|
|
3
|
+
describe('SessionCache', () => {
|
|
4
|
+
let cache;
|
|
5
|
+
const inactivityTimeout = 24 * 60 * 60 * 1000; // 24 hours
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
cache = new SessionCache(inactivityTimeout);
|
|
8
|
+
});
|
|
9
|
+
describe('get', () => {
|
|
10
|
+
it('should return null for non-existent session', () => {
|
|
11
|
+
expect(cache.get('user1')).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
it('should return session when it exists', () => {
|
|
14
|
+
const session = {
|
|
15
|
+
userId: 'user1',
|
|
16
|
+
createdAt: new Date(),
|
|
17
|
+
lastActivityAt: new Date(),
|
|
18
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
19
|
+
};
|
|
20
|
+
cache.set('user1', session);
|
|
21
|
+
expect(cache.get('user1')).toEqual(session);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
describe('set', () => {
|
|
25
|
+
it('should store session in cache', () => {
|
|
26
|
+
const session = {
|
|
27
|
+
userId: 'user1',
|
|
28
|
+
createdAt: new Date(),
|
|
29
|
+
lastActivityAt: new Date(),
|
|
30
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
31
|
+
};
|
|
32
|
+
cache.set('user1', session);
|
|
33
|
+
expect(cache.get('user1')).toEqual(session);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
describe('isExpired', () => {
|
|
37
|
+
it('should return true for non-existent session', () => {
|
|
38
|
+
expect(cache.isExpired('user1')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('should return false for valid session', () => {
|
|
41
|
+
const session = {
|
|
42
|
+
userId: 'user1',
|
|
43
|
+
createdAt: new Date(),
|
|
44
|
+
lastActivityAt: new Date(),
|
|
45
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
46
|
+
};
|
|
47
|
+
cache.set('user1', session);
|
|
48
|
+
expect(cache.isExpired('user1')).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it('should return true for expired session', () => {
|
|
51
|
+
const session = {
|
|
52
|
+
userId: 'user1',
|
|
53
|
+
createdAt: new Date(Date.now() - inactivityTimeout),
|
|
54
|
+
lastActivityAt: new Date(Date.now() - inactivityTimeout),
|
|
55
|
+
expiresAt: new Date(Date.now() - 1000), // Expired 1 second ago
|
|
56
|
+
};
|
|
57
|
+
cache.set('user1', session);
|
|
58
|
+
expect(cache.isExpired('user1')).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe('clear', () => {
|
|
62
|
+
it('should remove session from cache', () => {
|
|
63
|
+
const session = {
|
|
64
|
+
userId: 'user1',
|
|
65
|
+
createdAt: new Date(),
|
|
66
|
+
lastActivityAt: new Date(),
|
|
67
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
68
|
+
};
|
|
69
|
+
cache.set('user1', session);
|
|
70
|
+
cache.clear('user1');
|
|
71
|
+
expect(cache.get('user1')).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('clearExpired', () => {
|
|
75
|
+
it('should remove expired sessions', () => {
|
|
76
|
+
const expiredSession = {
|
|
77
|
+
userId: 'user1',
|
|
78
|
+
createdAt: new Date(Date.now() - inactivityTimeout),
|
|
79
|
+
lastActivityAt: new Date(Date.now() - inactivityTimeout),
|
|
80
|
+
expiresAt: new Date(Date.now() - 1000),
|
|
81
|
+
};
|
|
82
|
+
const validSession = {
|
|
83
|
+
userId: 'user2',
|
|
84
|
+
createdAt: new Date(),
|
|
85
|
+
lastActivityAt: new Date(),
|
|
86
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
87
|
+
};
|
|
88
|
+
cache.set('user1', expiredSession);
|
|
89
|
+
cache.set('user2', validSession);
|
|
90
|
+
cache.clearExpired();
|
|
91
|
+
expect(cache.get('user1')).toBeNull();
|
|
92
|
+
expect(cache.get('user2')).toEqual(validSession);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe('clearAll', () => {
|
|
96
|
+
it('should remove all sessions', () => {
|
|
97
|
+
const session1 = {
|
|
98
|
+
userId: 'user1',
|
|
99
|
+
createdAt: new Date(),
|
|
100
|
+
lastActivityAt: new Date(),
|
|
101
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
102
|
+
};
|
|
103
|
+
const session2 = {
|
|
104
|
+
userId: 'user2',
|
|
105
|
+
createdAt: new Date(),
|
|
106
|
+
lastActivityAt: new Date(),
|
|
107
|
+
expiresAt: new Date(Date.now() + inactivityTimeout),
|
|
108
|
+
};
|
|
109
|
+
cache.set('user1', session1);
|
|
110
|
+
cache.set('user2', session2);
|
|
111
|
+
cache.clearAll();
|
|
112
|
+
expect(cache.get('user1')).toBeNull();
|
|
113
|
+
expect(cache.get('user2')).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=sessionCache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionCache.test.js","sourceRoot":"","sources":["../../src/session/sessionCache.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,KAAmB,CAAC;IACxB,MAAM,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;IAE1D,UAAU,CAAC,GAAG,EAAE;QACd,KAAK,GAAG,IAAI,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;QACnB,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;YAC9C,MAAM,OAAO,GAAgB;gBAC3B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE;QACnB,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;YACvC,MAAM,OAAO,GAAgB;gBAC3B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;QACzB,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACrD,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;YAC/C,MAAM,OAAO,GAAgB;gBAC3B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAChD,MAAM,OAAO,GAAgB;gBAC3B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;gBACnD,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;gBACxD,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,uBAAuB;aAChE,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;YAC1C,MAAM,OAAO,GAAgB;gBAC3B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACrB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;YACxC,MAAM,cAAc,GAAgB;gBAClC,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;gBACnD,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;gBACxD,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;aACvC,CAAC;YACF,MAAM,YAAY,GAAgB;gBAChC,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;YACnC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YACjC,KAAK,CAAC,YAAY,EAAE,CAAC;YACrB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;QACxB,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,QAAQ,GAAgB;gBAC5B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,MAAM,QAAQ,GAAgB;gBAC5B,MAAM,EAAE,OAAO;gBACf,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,cAAc,EAAE,IAAI,IAAI,EAAE;gBAC1B,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;aACpD,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC7B,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;YAC7B,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Firestore } from 'firebase-admin/firestore';
|
|
2
|
+
import { SessionCache } from './sessionCache.js';
|
|
3
|
+
import { FirestoreSync } from './firestoreSync.js';
|
|
4
|
+
import { SessionValidationStatus } from '@rapidraptor/auth-shared';
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when attempting to create a session with a revoked token
|
|
7
|
+
*/
|
|
8
|
+
export declare class TokenRevokedError extends Error {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Main session management service
|
|
13
|
+
* Orchestrates cache and Firestore with cache-first strategy
|
|
14
|
+
*/
|
|
15
|
+
export declare class SessionService {
|
|
16
|
+
private cache;
|
|
17
|
+
private firestoreSync;
|
|
18
|
+
private firestore;
|
|
19
|
+
private inactivityTimeout;
|
|
20
|
+
private collectionName;
|
|
21
|
+
private logoutsCollectionName;
|
|
22
|
+
private logoutTtlMs;
|
|
23
|
+
constructor(cache: SessionCache, firestoreSync: FirestoreSync, firestore: Firestore, inactivityTimeout: number, collectionName?: string, logoutsCollectionName?: string, logoutTtlMs?: number);
|
|
24
|
+
/**
|
|
25
|
+
* Validate session and return detailed status (cache-first lookup with Firestore fallback)
|
|
26
|
+
* Returns explicit status instead of boolean to avoid requiring additional calls
|
|
27
|
+
* to determine why a session is invalid
|
|
28
|
+
*/
|
|
29
|
+
validateSession(userId: string): Promise<SessionValidationStatus>;
|
|
30
|
+
/**
|
|
31
|
+
* Check session validity (cache-first lookup with Firestore fallback)
|
|
32
|
+
* @deprecated Use validateSession() instead for more detailed status information
|
|
33
|
+
*/
|
|
34
|
+
isSessionValid(userId: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Check if session exists in Firestore (regardless of expiration)
|
|
37
|
+
* Also verifies data integrity (userId in document matches document ID)
|
|
38
|
+
*/
|
|
39
|
+
sessionExists(userId: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Ensure session exists (idempotent - creates if doesn't exist)
|
|
42
|
+
* Returns true if session was created, false if it already existed and is valid
|
|
43
|
+
* Handles data integrity issues by overwriting with a new session
|
|
44
|
+
*
|
|
45
|
+
* @param userId - The user ID
|
|
46
|
+
* @param tokenIssuedAt - Optional JWT token issued-at timestamp for revocation check
|
|
47
|
+
* @throws TokenRevokedError if token was issued before logout (when tokenIssuedAt is provided)
|
|
48
|
+
* @throws Error if session is expired (user must logout and login again)
|
|
49
|
+
*/
|
|
50
|
+
ensureSession(userId: string, tokenIssuedAt?: Date): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Create new session
|
|
53
|
+
*/
|
|
54
|
+
createSession(userId: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Update last activity timestamp
|
|
57
|
+
* Cache is updated immediately for fast reads, but Firestore write is throttled
|
|
58
|
+
*/
|
|
59
|
+
updateLastActivity(userId: string): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Clear session (logout)
|
|
62
|
+
* Also stores logout timestamp to prevent re-authentication with JWTs issued before logout
|
|
63
|
+
*/
|
|
64
|
+
clearSession(userId: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Check if JWT token was issued before logout
|
|
67
|
+
* Returns true if token was issued before logout timestamp
|
|
68
|
+
*/
|
|
69
|
+
wasTokenIssuedBeforeLogout(userId: string, tokenIssuedAt: Date): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Warmup cache from Firestore
|
|
72
|
+
* Loads all active sessions into cache on startup
|
|
73
|
+
* Also cleans up expired sessions (lazy deletion)
|
|
74
|
+
*/
|
|
75
|
+
warmupCache(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Parse Firestore document data into SessionInfo
|
|
78
|
+
*/
|
|
79
|
+
private parseFirestoreDocument;
|
|
80
|
+
/**
|
|
81
|
+
* Convert SessionInfo to Firestore document format
|
|
82
|
+
*/
|
|
83
|
+
private toFirestoreDocument;
|
|
84
|
+
/**
|
|
85
|
+
* Convert Firestore Timestamp to JavaScript Date
|
|
86
|
+
*/
|
|
87
|
+
private toDate;
|
|
88
|
+
/**
|
|
89
|
+
* Parse Firestore logout document data
|
|
90
|
+
*/
|
|
91
|
+
private parseLogoutDocument;
|
|
92
|
+
/**
|
|
93
|
+
* Convert logout info to Firestore document format
|
|
94
|
+
*/
|
|
95
|
+
private toLogoutDocument;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=sessionService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionService.d.ts","sourceRoot":"","sources":["../../src/session/sessionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAGnE;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,WAAW,CAAS;gBAG1B,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAE,SAAS,EACpB,iBAAiB,EAAE,MAAM,EACzB,cAAc,GAAE,MAAoD,EACpE,qBAAqB,GAAE,MAAmD,EAC1E,WAAW,GAAE,MAA+B;IAW9C;;;;OAIG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA6CvE;;;OAGG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKtD;;;OAGG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAarD;;;;;;;;;OASG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IA4B3E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBlD;;;OAGG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BvD;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBjD;;;OAGG;IACG,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAqBvF;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAqDlC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAS9B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAS3B;;OAEG;IACH,OAAO,CAAC,MAAM;IAOd;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAWzB"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { SessionValidationStatus } from '@rapidraptor/auth-shared';
|
|
2
|
+
import { DEFAULTS } from '@rapidraptor/auth-shared';
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when attempting to create a session with a revoked token
|
|
5
|
+
*/
|
|
6
|
+
export class TokenRevokedError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('Token was issued before logout');
|
|
9
|
+
this.name = 'TokenRevokedError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Main session management service
|
|
14
|
+
* Orchestrates cache and Firestore with cache-first strategy
|
|
15
|
+
*/
|
|
16
|
+
export class SessionService {
|
|
17
|
+
cache;
|
|
18
|
+
firestoreSync;
|
|
19
|
+
firestore;
|
|
20
|
+
inactivityTimeout;
|
|
21
|
+
collectionName;
|
|
22
|
+
logoutsCollectionName;
|
|
23
|
+
logoutTtlMs;
|
|
24
|
+
constructor(cache, firestoreSync, firestore, inactivityTimeout, collectionName = DEFAULTS.FIRESTORE_SESSIONS_COLLECTION_NAME, logoutsCollectionName = DEFAULTS.FIRESTORE_LOGOUTS_COLLECTION_NAME, logoutTtlMs = DEFAULTS.LOGOUT_TTL_MS) {
|
|
25
|
+
this.cache = cache;
|
|
26
|
+
this.firestoreSync = firestoreSync;
|
|
27
|
+
this.firestore = firestore;
|
|
28
|
+
this.inactivityTimeout = inactivityTimeout;
|
|
29
|
+
this.collectionName = collectionName;
|
|
30
|
+
this.logoutsCollectionName = logoutsCollectionName;
|
|
31
|
+
this.logoutTtlMs = logoutTtlMs;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate session and return detailed status (cache-first lookup with Firestore fallback)
|
|
35
|
+
* Returns explicit status instead of boolean to avoid requiring additional calls
|
|
36
|
+
* to determine why a session is invalid
|
|
37
|
+
*/
|
|
38
|
+
async validateSession(userId) {
|
|
39
|
+
// Check cache first
|
|
40
|
+
const cachedSession = this.cache.get(userId);
|
|
41
|
+
// Step 1: Check for userId mismatch (data integrity issue)
|
|
42
|
+
if (cachedSession && cachedSession.userId !== userId) {
|
|
43
|
+
// Data integrity issue - invalidate cache entry
|
|
44
|
+
this.cache.clear(userId);
|
|
45
|
+
return SessionValidationStatus.DATA_INTEGRITY_ERROR;
|
|
46
|
+
}
|
|
47
|
+
// Step 2: Check if cached session is valid
|
|
48
|
+
if (cachedSession && !this.cache.isExpired(userId)) {
|
|
49
|
+
// Cached session is valid and userId matches
|
|
50
|
+
return SessionValidationStatus.VALID;
|
|
51
|
+
}
|
|
52
|
+
// Cache miss or expired - check Firestore
|
|
53
|
+
const docRef = this.firestore.collection(this.collectionName).doc(userId);
|
|
54
|
+
const doc = await docRef.get();
|
|
55
|
+
if (!doc.exists) {
|
|
56
|
+
return SessionValidationStatus.NOT_FOUND;
|
|
57
|
+
}
|
|
58
|
+
// Parse Firestore document
|
|
59
|
+
const data = doc.data();
|
|
60
|
+
const session = this.parseFirestoreDocument(data);
|
|
61
|
+
// Verify session userId matches requested userId (data integrity check)
|
|
62
|
+
if (session.userId !== userId) {
|
|
63
|
+
// Data integrity issue - session document userId doesn't match document ID
|
|
64
|
+
return SessionValidationStatus.DATA_INTEGRITY_ERROR;
|
|
65
|
+
}
|
|
66
|
+
// Check expiration
|
|
67
|
+
if (new Date() > session.expiresAt) {
|
|
68
|
+
return SessionValidationStatus.EXPIRED;
|
|
69
|
+
}
|
|
70
|
+
// Update cache
|
|
71
|
+
this.cache.set(userId, session);
|
|
72
|
+
return SessionValidationStatus.VALID;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check session validity (cache-first lookup with Firestore fallback)
|
|
76
|
+
* @deprecated Use validateSession() instead for more detailed status information
|
|
77
|
+
*/
|
|
78
|
+
async isSessionValid(userId) {
|
|
79
|
+
const status = await this.validateSession(userId);
|
|
80
|
+
return status === SessionValidationStatus.VALID;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if session exists in Firestore (regardless of expiration)
|
|
84
|
+
* Also verifies data integrity (userId in document matches document ID)
|
|
85
|
+
*/
|
|
86
|
+
async sessionExists(userId) {
|
|
87
|
+
const docRef = this.firestore.collection(this.collectionName).doc(userId);
|
|
88
|
+
const doc = await docRef.get();
|
|
89
|
+
if (!doc.exists) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// Verify session userId matches requested userId (data integrity check)
|
|
93
|
+
const data = doc.data();
|
|
94
|
+
return data.userId === userId;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Ensure session exists (idempotent - creates if doesn't exist)
|
|
98
|
+
* Returns true if session was created, false if it already existed and is valid
|
|
99
|
+
* Handles data integrity issues by overwriting with a new session
|
|
100
|
+
*
|
|
101
|
+
* @param userId - The user ID
|
|
102
|
+
* @param tokenIssuedAt - Optional JWT token issued-at timestamp for revocation check
|
|
103
|
+
* @throws TokenRevokedError if token was issued before logout (when tokenIssuedAt is provided)
|
|
104
|
+
* @throws Error if session is expired (user must logout and login again)
|
|
105
|
+
*/
|
|
106
|
+
async ensureSession(userId, tokenIssuedAt) {
|
|
107
|
+
// Check if token was issued before logout (if tokenIssuedAt provided)
|
|
108
|
+
if (tokenIssuedAt) {
|
|
109
|
+
const wasIssuedBeforeLogout = await this.wasTokenIssuedBeforeLogout(userId, tokenIssuedAt);
|
|
110
|
+
if (wasIssuedBeforeLogout) {
|
|
111
|
+
throw new TokenRevokedError();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Check session validation status
|
|
115
|
+
const status = await this.validateSession(userId);
|
|
116
|
+
if (status === SessionValidationStatus.VALID) {
|
|
117
|
+
return false; // Session already existed and is valid
|
|
118
|
+
}
|
|
119
|
+
// If session is expired, don't recreate it - user must logout and relogin
|
|
120
|
+
if (status === SessionValidationStatus.EXPIRED) {
|
|
121
|
+
throw new Error('Session has expired. Please logout and login again.');
|
|
122
|
+
}
|
|
123
|
+
// Session doesn't exist (NOT_FOUND) or has data integrity issues - create/overwrite it
|
|
124
|
+
// Note: For DATA_INTEGRITY_ERROR, we recreate the session to fix the corruption
|
|
125
|
+
// createSession is idempotent (uses set() which overwrites)
|
|
126
|
+
await this.createSession(userId);
|
|
127
|
+
return true; // Session was created
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create new session
|
|
131
|
+
*/
|
|
132
|
+
async createSession(userId) {
|
|
133
|
+
const now = new Date();
|
|
134
|
+
const expiresAt = new Date(now.getTime() + this.inactivityTimeout);
|
|
135
|
+
const session = {
|
|
136
|
+
userId,
|
|
137
|
+
createdAt: now,
|
|
138
|
+
lastActivityAt: now,
|
|
139
|
+
expiresAt,
|
|
140
|
+
};
|
|
141
|
+
// Update cache immediately
|
|
142
|
+
this.cache.set(userId, session);
|
|
143
|
+
// Write to Firestore immediately (no throttle on creation)
|
|
144
|
+
const docRef = this.firestore.collection(this.collectionName).doc(userId);
|
|
145
|
+
await docRef.set(this.toFirestoreDocument(session));
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Update last activity timestamp
|
|
149
|
+
* Cache is updated immediately for fast reads, but Firestore write is throttled
|
|
150
|
+
*/
|
|
151
|
+
async updateLastActivity(userId) {
|
|
152
|
+
// Load and validate session (handles cache + Firestore fallback)
|
|
153
|
+
const isValid = await this.isSessionValid(userId);
|
|
154
|
+
if (!isValid) {
|
|
155
|
+
return; // Session doesn't exist or is expired
|
|
156
|
+
}
|
|
157
|
+
// Session is guaranteed to be in cache and valid at this point
|
|
158
|
+
const session = this.cache.get(userId);
|
|
159
|
+
if (!session) {
|
|
160
|
+
// Should not happen, but handle gracefully
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Update cache immediately (fast path)
|
|
164
|
+
const updatedSession = {
|
|
165
|
+
...session,
|
|
166
|
+
lastActivityAt: new Date(),
|
|
167
|
+
expiresAt: new Date(Date.now() + this.inactivityTimeout),
|
|
168
|
+
};
|
|
169
|
+
this.cache.set(userId, updatedSession);
|
|
170
|
+
// Queue Firestore write (throttled - may not write immediately)
|
|
171
|
+
this.firestoreSync.queueWrite(userId, updatedSession);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear session (logout)
|
|
175
|
+
* Also stores logout timestamp to prevent re-authentication with JWTs issued before logout
|
|
176
|
+
*/
|
|
177
|
+
async clearSession(userId) {
|
|
178
|
+
// Clear cache
|
|
179
|
+
this.cache.clear(userId);
|
|
180
|
+
// Store logout timestamp to prevent re-authentication with old JWTs
|
|
181
|
+
// This addresses the JWT limitation: JWTs cannot be revoked, but we can track
|
|
182
|
+
// when a user logged out and reject tokens issued before that time
|
|
183
|
+
const now = new Date();
|
|
184
|
+
const expiresAt = new Date(now.getTime() + this.logoutTtlMs);
|
|
185
|
+
const logoutRef = this.firestore.collection(this.logoutsCollectionName).doc(userId);
|
|
186
|
+
await logoutRef.set(this.toLogoutDocument({
|
|
187
|
+
userId,
|
|
188
|
+
loggedOutAt: now,
|
|
189
|
+
expiresAt,
|
|
190
|
+
}));
|
|
191
|
+
// Delete session document from Firestore
|
|
192
|
+
const docRef = this.firestore.collection(this.collectionName).doc(userId);
|
|
193
|
+
await docRef.delete();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if JWT token was issued before logout
|
|
197
|
+
* Returns true if token was issued before logout timestamp
|
|
198
|
+
*/
|
|
199
|
+
async wasTokenIssuedBeforeLogout(userId, tokenIssuedAt) {
|
|
200
|
+
// Check logout timestamp
|
|
201
|
+
const logoutRef = this.firestore.collection(this.logoutsCollectionName).doc(userId);
|
|
202
|
+
const doc = await logoutRef.get();
|
|
203
|
+
if (!doc.exists) {
|
|
204
|
+
return false; // No logout recorded - token is valid
|
|
205
|
+
}
|
|
206
|
+
const data = doc.data();
|
|
207
|
+
if (!data) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
// Parse logout document
|
|
211
|
+
const logoutInfo = this.parseLogoutDocument(data);
|
|
212
|
+
// Check if token was issued BEFORE logout
|
|
213
|
+
return tokenIssuedAt < logoutInfo.loggedOutAt;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Warmup cache from Firestore
|
|
217
|
+
* Loads all active sessions into cache on startup
|
|
218
|
+
* Also cleans up expired sessions (lazy deletion)
|
|
219
|
+
*/
|
|
220
|
+
async warmupCache() {
|
|
221
|
+
const collection = this.firestore.collection(this.collectionName);
|
|
222
|
+
const now = new Date();
|
|
223
|
+
// Query active sessions
|
|
224
|
+
const snapshot = await collection.where('expiresAt', '>', now).get();
|
|
225
|
+
// Load into cache
|
|
226
|
+
for (const doc of snapshot.docs) {
|
|
227
|
+
const data = doc.data();
|
|
228
|
+
// SECURITY: Verify session userId matches document ID (data integrity check)
|
|
229
|
+
// Skip sessions with mismatched userId (data corruption)
|
|
230
|
+
if (data.userId !== doc.id) {
|
|
231
|
+
console.warn(`Skipping session with mismatched userId: document ID=${doc.id}, data.userId=${data.userId}`);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const session = this.parseFirestoreDocument(data);
|
|
235
|
+
this.cache.set(session.userId, session);
|
|
236
|
+
}
|
|
237
|
+
// Cleanup expired sessions (lazy deletion)
|
|
238
|
+
const expiredSnapshot = await collection.where('expiresAt', '<=', now).get();
|
|
239
|
+
if (expiredSnapshot.empty) {
|
|
240
|
+
return; // No expired sessions to clean up
|
|
241
|
+
}
|
|
242
|
+
// Delete expired sessions in batches (Firestore batch limit is 500)
|
|
243
|
+
const batchSize = 500;
|
|
244
|
+
const expiredDocs = expiredSnapshot.docs;
|
|
245
|
+
let deletedCount = 0;
|
|
246
|
+
for (let i = 0; i < expiredDocs.length; i += batchSize) {
|
|
247
|
+
const batch = this.firestore.batch();
|
|
248
|
+
const batchDocs = expiredDocs.slice(i, i + batchSize);
|
|
249
|
+
for (const doc of batchDocs) {
|
|
250
|
+
batch.delete(doc.ref);
|
|
251
|
+
deletedCount++;
|
|
252
|
+
}
|
|
253
|
+
await batch.commit();
|
|
254
|
+
}
|
|
255
|
+
if (deletedCount > 0) {
|
|
256
|
+
console.info(`Cleaned up ${deletedCount} expired session(s) during cache warmup`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Parse Firestore document data into SessionInfo
|
|
261
|
+
*/
|
|
262
|
+
parseFirestoreDocument(data) {
|
|
263
|
+
return {
|
|
264
|
+
userId: data.userId,
|
|
265
|
+
createdAt: this.toDate(data.createdAt),
|
|
266
|
+
lastActivityAt: this.toDate(data.lastActivityAt),
|
|
267
|
+
expiresAt: this.toDate(data.expiresAt),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Convert SessionInfo to Firestore document format
|
|
272
|
+
*/
|
|
273
|
+
toFirestoreDocument(session) {
|
|
274
|
+
return {
|
|
275
|
+
userId: session.userId,
|
|
276
|
+
createdAt: session.createdAt,
|
|
277
|
+
lastActivityAt: session.lastActivityAt,
|
|
278
|
+
expiresAt: session.expiresAt,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Convert Firestore Timestamp to JavaScript Date
|
|
283
|
+
*/
|
|
284
|
+
toDate(timestamp) {
|
|
285
|
+
if (timestamp instanceof Date) {
|
|
286
|
+
return timestamp;
|
|
287
|
+
}
|
|
288
|
+
return timestamp.toDate();
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Parse Firestore logout document data
|
|
292
|
+
*/
|
|
293
|
+
parseLogoutDocument(data) {
|
|
294
|
+
return {
|
|
295
|
+
userId: data.userId,
|
|
296
|
+
loggedOutAt: this.toDate(data.loggedOutAt),
|
|
297
|
+
expiresAt: this.toDate(data.expiresAt),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Convert logout info to Firestore document format
|
|
302
|
+
*/
|
|
303
|
+
toLogoutDocument(logoutInfo) {
|
|
304
|
+
return {
|
|
305
|
+
userId: logoutInfo.userId,
|
|
306
|
+
loggedOutAt: logoutInfo.loggedOutAt,
|
|
307
|
+
expiresAt: logoutInfo.expiresAt,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
//# sourceMappingURL=sessionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionService.js","sourceRoot":"","sources":["../../src/session/sessionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionService.test.d.ts","sourceRoot":"","sources":["../../src/session/sessionService.test.ts"],"names":[],"mappings":""}
|