@rapidraptor/auth-server 0.2.5 → 1.0.2

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.
@@ -9,8 +9,9 @@ export declare class TokenRevokedError extends Error {
9
9
  constructor();
10
10
  }
11
11
  /**
12
- * Main session management service
13
- * Orchestrates cache and Firestore with cache-first strategy
12
+ * Main session management service. Sessions are stored in Firestore as user_sessions/{sessionId}
13
+ * (sessionId is a UUID). Lookup is by userId (from JWT sub); cache is keyed by userId.
14
+ * Uses cache-first strategy with Firestore fallback.
14
15
  */
15
16
  export declare class SessionService {
16
17
  private cache;
@@ -21,6 +22,13 @@ export declare class SessionService {
21
22
  private logoutsCollectionName;
22
23
  private logoutTtlMs;
23
24
  constructor(cache: SessionCache, firestoreSync: FirestoreSync, firestore: Firestore, inactivityTimeout: number, collectionName?: string, logoutsCollectionName?: string, logoutTtlMs?: number);
25
+ /**
26
+ * Find active session for user.
27
+ * Sessions are stored as user_sessions/{sessionId}; we query by userId (from JWT sub) since
28
+ * sessionId is not sent by the client. orderBy lastActivityAt desc ensures a deterministic
29
+ * result when multiple active sessions exist for the same user (e.g. race or bug).
30
+ */
31
+ private findActiveSessionByUserId;
24
32
  /**
25
33
  * Validate session and return detailed status (cache-first lookup with Firestore fallback)
26
34
  * Returns explicit status instead of boolean to avoid requiring additional calls
@@ -33,64 +41,67 @@ export declare class SessionService {
33
41
  */
34
42
  isSessionValid(userId: string): Promise<boolean>;
35
43
  /**
36
- * Check if session exists in Firestore (regardless of expiration)
37
- * Also verifies data integrity (userId in document matches document ID)
44
+ * Check if a session document exists in Firestore for this user (regardless of expiration).
45
+ * Uses a query by userId since documents are keyed by sessionId.
38
46
  */
39
47
  sessionExists(userId: string): Promise<boolean>;
40
48
  /**
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
49
+ * Ensure a session exists for the user (idempotent). Creates a new session only when none
50
+ * exists or there is a data integrity issue. Returns true if a session was created, false if
51
+ * one already existed and is valid.
44
52
  *
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)
53
+ * @param userId - User ID (from JWT sub)
54
+ * @param tokenIssuedAt - Optional JWT iat; if provided, we reject tokens issued before logout
55
+ * @throws TokenRevokedError if token was issued before logout
56
+ * @throws Error if session is expired (user must logout and log in again; we do not auto-recreate)
49
57
  */
50
58
  ensureSession(userId: string, tokenIssuedAt?: Date): Promise<boolean>;
51
59
  /**
52
- * Create new session
60
+ * Create a new session. Session ID is an independent UUID (not derived from userId), so each
61
+ * login gets a distinct session and logout/re-login works correctly.
53
62
  */
54
63
  createSession(userId: string): Promise<void>;
55
64
  /**
56
- * Update last activity timestamp
57
- * Cache is updated immediately for fast reads, but Firestore write is throttled
65
+ * Update last activity timestamp and extend expiration. Cache is updated immediately for
66
+ * fast reads; Firestore write is queued and throttled (see FirestoreSync).
58
67
  */
59
68
  updateLastActivity(userId: string): Promise<void>;
60
69
  /**
61
- * Clear session (logout)
62
- * Also stores logout timestamp to prevent re-authentication with JWTs issued before logout
70
+ * Clear session (logout). Removes all session documents for this user and records the logout
71
+ * so tokens issued before this time can be rejected (see wasTokenIssuedBeforeLogout).
63
72
  */
64
73
  clearSession(userId: string): Promise<void>;
65
74
  /**
66
- * Check if JWT token was issued before logout
67
- * Returns true if token was issued before logout timestamp
75
+ * Check if the JWT was issued before the user's last logout.
76
+ * Logout records are stored at user_logouts/{userId} (one per user). We use this to reject
77
+ * tokens that were issued before logout, since JWTs cannot be revoked directly.
78
+ * Returns true if token was issued before logout (token should be rejected).
68
79
  */
69
80
  wasTokenIssuedBeforeLogout(userId: string, tokenIssuedAt: Date): Promise<boolean>;
70
81
  /**
71
- * Warmup cache from Firestore
72
- * Loads all active sessions into cache on startup
73
- * Also cleans up expired sessions (lazy deletion)
82
+ * Warmup cache from Firestore on startup. Loads all non-expired sessions into the in-memory
83
+ * cache (keyed by userId). Document IDs in user_sessions are sessionIds; we cache by userId
84
+ * for lookup. Also performs lazy deletion of expired session documents.
74
85
  */
75
86
  warmupCache(): Promise<void>;
76
87
  /**
77
- * Parse Firestore document data into SessionInfo
88
+ * Parse Firestore session document (timestamp fields may be Firestore Timestamp or Date) into SessionInfo.
78
89
  */
79
90
  private parseFirestoreDocument;
80
91
  /**
81
- * Convert SessionInfo to Firestore document format
92
+ * Convert SessionInfo to Firestore document format for user_sessions collection.
82
93
  */
83
94
  private toFirestoreDocument;
84
95
  /**
85
- * Convert Firestore Timestamp to JavaScript Date
96
+ * Normalize Firestore Timestamp or Date to JavaScript Date.
86
97
  */
87
98
  private toDate;
88
99
  /**
89
- * Parse Firestore logout document data
100
+ * Parse Firestore logout document (user_logouts collection) into typed fields.
90
101
  */
91
102
  private parseLogoutDocument;
92
103
  /**
93
- * Convert logout info to Firestore document format
104
+ * Convert logout info to Firestore document format for user_logouts collection.
94
105
  */
95
106
  private toLogoutDocument;
96
107
  }
@@ -1 +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"}
1
+ {"version":3,"file":"sessionService.d.ts","sourceRoot":"","sources":["../../src/session/sessionService.ts"],"names":[],"mappings":"AACA,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;;;;GAIG;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;;;;;OAKG;YACW,yBAAyB;IAgBvC;;;;OAIG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAuCvE;;;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;IAcrD;;;;;;;;;OASG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAuB3E;;;OAGG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlD;;;OAGG;IACG,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAsBvD;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BjD;;;;;OAKG;IACG,0BAA0B,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBvF;;;;OAIG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAgDlC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAU9B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,MAAM;IAOd;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAWzB"}
@@ -1,3 +1,4 @@
1
+ import { randomUUID } from 'crypto';
1
2
  import { SessionValidationStatus } from '@rapidraptor/auth-shared';
2
3
  import { DEFAULTS } from '@rapidraptor/auth-shared';
3
4
  /**
@@ -10,8 +11,9 @@ export class TokenRevokedError extends Error {
10
11
  }
11
12
  }
12
13
  /**
13
- * Main session management service
14
- * Orchestrates cache and Firestore with cache-first strategy
14
+ * Main session management service. Sessions are stored in Firestore as user_sessions/{sessionId}
15
+ * (sessionId is a UUID). Lookup is by userId (from JWT sub); cache is keyed by userId.
16
+ * Uses cache-first strategy with Firestore fallback.
15
17
  */
16
18
  export class SessionService {
17
19
  cache;
@@ -30,44 +32,59 @@ export class SessionService {
30
32
  this.logoutsCollectionName = logoutsCollectionName;
31
33
  this.logoutTtlMs = logoutTtlMs;
32
34
  }
35
+ /**
36
+ * Find active session for user.
37
+ * Sessions are stored as user_sessions/{sessionId}; we query by userId (from JWT sub) since
38
+ * sessionId is not sent by the client. orderBy lastActivityAt desc ensures a deterministic
39
+ * result when multiple active sessions exist for the same user (e.g. race or bug).
40
+ */
41
+ async findActiveSessionByUserId(userId) {
42
+ const snapshot = await this.firestore
43
+ .collection(this.collectionName)
44
+ .where('userId', '==', userId)
45
+ .where('expiresAt', '>', new Date())
46
+ .orderBy('lastActivityAt', 'desc')
47
+ .limit(1)
48
+ .get();
49
+ if (snapshot.empty) {
50
+ return null;
51
+ }
52
+ const data = snapshot.docs[0].data();
53
+ return this.parseFirestoreDocument(data);
54
+ }
33
55
  /**
34
56
  * Validate session and return detailed status (cache-first lookup with Firestore fallback)
35
57
  * Returns explicit status instead of boolean to avoid requiring additional calls
36
58
  * to determine why a session is invalid
37
59
  */
38
60
  async validateSession(userId) {
39
- // Check cache first
61
+ // Cache is keyed by userId (one session per user in cache)
40
62
  const cachedSession = this.cache.get(userId);
41
63
  // Step 1: Check for userId mismatch (data integrity issue)
42
64
  if (cachedSession && cachedSession.userId !== userId) {
43
- // Data integrity issue - invalidate cache entry
44
65
  this.cache.clear(userId);
45
66
  return SessionValidationStatus.DATA_INTEGRITY_ERROR;
46
67
  }
47
- // Step 2: Check if cached session is valid
68
+ // Step 2: Check if cached session is valid and not expired
48
69
  if (cachedSession && !this.cache.isExpired(userId)) {
49
- // Cached session is valid and userId matches
50
70
  return SessionValidationStatus.VALID;
51
71
  }
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) {
72
+ // Step 2b: Cached session exists but is expired return EXPIRED so caller can require re-login
73
+ if (cachedSession && this.cache.isExpired(userId)) {
74
+ return SessionValidationStatus.EXPIRED;
75
+ }
76
+ // Step 3: Cache miss — query Firestore by userId (sessions live under user_sessions/{sessionId})
77
+ const session = await this.findActiveSessionByUserId(userId);
78
+ if (!session) {
56
79
  return SessionValidationStatus.NOT_FOUND;
57
80
  }
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
81
  if (session.userId !== userId) {
63
- // Data integrity issue - session document userId doesn't match document ID
64
82
  return SessionValidationStatus.DATA_INTEGRITY_ERROR;
65
83
  }
66
- // Check expiration
67
84
  if (new Date() > session.expiresAt) {
68
85
  return SessionValidationStatus.EXPIRED;
69
86
  }
70
- // Update cache
87
+ // Repopulate cache for future requests
71
88
  this.cache.set(userId, session);
72
89
  return SessionValidationStatus.VALID;
73
90
  }
@@ -80,161 +97,154 @@ export class SessionService {
80
97
  return status === SessionValidationStatus.VALID;
81
98
  }
82
99
  /**
83
- * Check if session exists in Firestore (regardless of expiration)
84
- * Also verifies data integrity (userId in document matches document ID)
100
+ * Check if a session document exists in Firestore for this user (regardless of expiration).
101
+ * Uses a query by userId since documents are keyed by sessionId.
85
102
  */
86
103
  async sessionExists(userId) {
87
- const docRef = this.firestore.collection(this.collectionName).doc(userId);
88
- const doc = await docRef.get();
89
- if (!doc.exists) {
104
+ const snapshot = await this.firestore
105
+ .collection(this.collectionName)
106
+ .where('userId', '==', userId)
107
+ .limit(1)
108
+ .get();
109
+ if (snapshot.empty) {
90
110
  return false;
91
111
  }
92
- // Verify session userId matches requested userId (data integrity check)
93
- const data = doc.data();
112
+ const data = snapshot.docs[0].data();
94
113
  return data.userId === userId;
95
114
  }
96
115
  /**
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
116
+ * Ensure a session exists for the user (idempotent). Creates a new session only when none
117
+ * exists or there is a data integrity issue. Returns true if a session was created, false if
118
+ * one already existed and is valid.
100
119
  *
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)
120
+ * @param userId - User ID (from JWT sub)
121
+ * @param tokenIssuedAt - Optional JWT iat; if provided, we reject tokens issued before logout
122
+ * @throws TokenRevokedError if token was issued before logout
123
+ * @throws Error if session is expired (user must logout and log in again; we do not auto-recreate)
105
124
  */
106
125
  async ensureSession(userId, tokenIssuedAt) {
107
- // Check if token was issued before logout (if tokenIssuedAt provided)
108
126
  if (tokenIssuedAt) {
109
127
  const wasIssuedBeforeLogout = await this.wasTokenIssuedBeforeLogout(userId, tokenIssuedAt);
110
128
  if (wasIssuedBeforeLogout) {
111
129
  throw new TokenRevokedError();
112
130
  }
113
131
  }
114
- // Check session validation status
115
132
  const status = await this.validateSession(userId);
116
133
  if (status === SessionValidationStatus.VALID) {
117
- return false; // Session already existed and is valid
134
+ return false;
118
135
  }
119
- // If session is expired, don't recreate it - user must logout and relogin
120
136
  if (status === SessionValidationStatus.EXPIRED) {
121
137
  throw new Error('Session has expired. Please logout and login again.');
122
138
  }
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)
139
+ // NOT_FOUND or DATA_INTEGRITY_ERROR: create a new session (new sessionId)
126
140
  await this.createSession(userId);
127
- return true; // Session was created
141
+ return true;
128
142
  }
129
143
  /**
130
- * Create new session
144
+ * Create a new session. Session ID is an independent UUID (not derived from userId), so each
145
+ * login gets a distinct session and logout/re-login works correctly.
131
146
  */
132
147
  async createSession(userId) {
148
+ const sessionId = randomUUID();
133
149
  const now = new Date();
134
150
  const expiresAt = new Date(now.getTime() + this.inactivityTimeout);
135
151
  const session = {
152
+ sessionId,
136
153
  userId,
137
154
  createdAt: now,
138
155
  lastActivityAt: now,
139
156
  expiresAt,
140
157
  };
141
- // Update cache immediately
158
+ // Cache is keyed by userId for lookup; document in Firestore is keyed by sessionId
142
159
  this.cache.set(userId, session);
143
- // Write to Firestore immediately (no throttle on creation)
144
- const docRef = this.firestore.collection(this.collectionName).doc(userId);
160
+ const docRef = this.firestore.collection(this.collectionName).doc(sessionId);
145
161
  await docRef.set(this.toFirestoreDocument(session));
146
162
  }
147
163
  /**
148
- * Update last activity timestamp
149
- * Cache is updated immediately for fast reads, but Firestore write is throttled
164
+ * Update last activity timestamp and extend expiration. Cache is updated immediately for
165
+ * fast reads; Firestore write is queued and throttled (see FirestoreSync).
150
166
  */
151
167
  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
168
+ const status = await this.validateSession(userId);
169
+ if (status !== SessionValidationStatus.VALID) {
170
+ return; // Session doesn't exist or is expired; nothing to update
156
171
  }
157
- // Session is guaranteed to be in cache and valid at this point
158
172
  const session = this.cache.get(userId);
159
173
  if (!session) {
160
- // Should not happen, but handle gracefully
161
174
  return;
162
175
  }
163
- // Update cache immediately (fast path)
164
176
  const updatedSession = {
165
177
  ...session,
166
178
  lastActivityAt: new Date(),
167
179
  expiresAt: new Date(Date.now() + this.inactivityTimeout),
168
180
  };
169
181
  this.cache.set(userId, updatedSession);
170
- // Queue Firestore write (throttled - may not write immediately)
182
+ // Firestore write uses session.sessionId as document ID; throttled per user
171
183
  this.firestoreSync.queueWrite(userId, updatedSession);
172
184
  }
173
185
  /**
174
- * Clear session (logout)
175
- * Also stores logout timestamp to prevent re-authentication with JWTs issued before logout
186
+ * Clear session (logout). Removes all session documents for this user and records the logout
187
+ * so tokens issued before this time can be rejected (see wasTokenIssuedBeforeLogout).
176
188
  */
177
189
  async clearSession(userId) {
178
- // Clear cache
179
190
  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
191
  const now = new Date();
184
192
  const expiresAt = new Date(now.getTime() + this.logoutTtlMs);
193
+ // Logout record is keyed by userId (per-user, not per-session); used for token revocation check
185
194
  const logoutRef = this.firestore.collection(this.logoutsCollectionName).doc(userId);
186
195
  await logoutRef.set(this.toLogoutDocument({
187
196
  userId,
188
197
  loggedOutAt: now,
189
198
  expiresAt,
190
199
  }));
191
- // Delete session document from Firestore
192
- const docRef = this.firestore.collection(this.collectionName).doc(userId);
193
- await docRef.delete();
200
+ // Sessions are stored as user_sessions/{sessionId}; query by userId and delete each document
201
+ const snapshot = await this.firestore
202
+ .collection(this.collectionName)
203
+ .where('userId', '==', userId)
204
+ .get();
205
+ for (const doc of snapshot.docs) {
206
+ await doc.ref.delete();
207
+ }
194
208
  }
195
209
  /**
196
- * Check if JWT token was issued before logout
197
- * Returns true if token was issued before logout timestamp
210
+ * Check if the JWT was issued before the user's last logout.
211
+ * Logout records are stored at user_logouts/{userId} (one per user). We use this to reject
212
+ * tokens that were issued before logout, since JWTs cannot be revoked directly.
213
+ * Returns true if token was issued before logout (token should be rejected).
198
214
  */
199
215
  async wasTokenIssuedBeforeLogout(userId, tokenIssuedAt) {
200
- // Check logout timestamp
201
216
  const logoutRef = this.firestore.collection(this.logoutsCollectionName).doc(userId);
202
217
  const doc = await logoutRef.get();
203
218
  if (!doc.exists) {
204
- return false; // No logout recorded - token is valid
219
+ return false; // No logout recorded token is acceptable
205
220
  }
206
221
  const data = doc.data();
207
222
  if (!data) {
208
223
  return false;
209
224
  }
210
- // Parse logout document
211
225
  const logoutInfo = this.parseLogoutDocument(data);
212
- // Check if token was issued BEFORE logout
213
226
  return tokenIssuedAt < logoutInfo.loggedOutAt;
214
227
  }
215
228
  /**
216
- * Warmup cache from Firestore
217
- * Loads all active sessions into cache on startup
218
- * Also cleans up expired sessions (lazy deletion)
229
+ * Warmup cache from Firestore on startup. Loads all non-expired sessions into the in-memory
230
+ * cache (keyed by userId). Document IDs in user_sessions are sessionIds; we cache by userId
231
+ * for lookup. Also performs lazy deletion of expired session documents.
219
232
  */
220
233
  async warmupCache() {
221
234
  const collection = this.firestore.collection(this.collectionName);
222
235
  const now = new Date();
223
- // Query active sessions
224
236
  const snapshot = await collection.where('expiresAt', '>', now).get();
225
- // Load into cache
226
237
  for (const doc of snapshot.docs) {
227
238
  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}`);
239
+ // Document ID must match sessionId in payload (data integrity)
240
+ if (data.sessionId !== doc.id) {
241
+ console.warn(`Skipping session with mismatched sessionId: document ID=${doc.id}, data.sessionId=${data.sessionId}`);
232
242
  continue;
233
243
  }
234
244
  const session = this.parseFirestoreDocument(data);
235
245
  this.cache.set(session.userId, session);
236
246
  }
237
- // Cleanup expired sessions (lazy deletion)
247
+ // Lazy cleanup: delete expired session documents in batches
238
248
  const expiredSnapshot = await collection.where('expiresAt', '<=', now).get();
239
249
  if (expiredSnapshot.empty) {
240
250
  return; // No expired sessions to clean up
@@ -257,10 +267,11 @@ export class SessionService {
257
267
  }
258
268
  }
259
269
  /**
260
- * Parse Firestore document data into SessionInfo
270
+ * Parse Firestore session document (timestamp fields may be Firestore Timestamp or Date) into SessionInfo.
261
271
  */
262
272
  parseFirestoreDocument(data) {
263
273
  return {
274
+ sessionId: data.sessionId,
264
275
  userId: data.userId,
265
276
  createdAt: this.toDate(data.createdAt),
266
277
  lastActivityAt: this.toDate(data.lastActivityAt),
@@ -268,10 +279,11 @@ export class SessionService {
268
279
  };
269
280
  }
270
281
  /**
271
- * Convert SessionInfo to Firestore document format
282
+ * Convert SessionInfo to Firestore document format for user_sessions collection.
272
283
  */
273
284
  toFirestoreDocument(session) {
274
285
  return {
286
+ sessionId: session.sessionId,
275
287
  userId: session.userId,
276
288
  createdAt: session.createdAt,
277
289
  lastActivityAt: session.lastActivityAt,
@@ -279,7 +291,7 @@ export class SessionService {
279
291
  };
280
292
  }
281
293
  /**
282
- * Convert Firestore Timestamp to JavaScript Date
294
+ * Normalize Firestore Timestamp or Date to JavaScript Date.
283
295
  */
284
296
  toDate(timestamp) {
285
297
  if (timestamp instanceof Date) {
@@ -288,7 +300,7 @@ export class SessionService {
288
300
  return timestamp.toDate();
289
301
  }
290
302
  /**
291
- * Parse Firestore logout document data
303
+ * Parse Firestore logout document (user_logouts collection) into typed fields.
292
304
  */
293
305
  parseLogoutDocument(data) {
294
306
  return {
@@ -298,7 +310,7 @@ export class SessionService {
298
310
  };
299
311
  }
300
312
  /**
301
- * Convert logout info to Firestore document format
313
+ * Convert logout info to Firestore document format for user_logouts collection.
302
314
  */
303
315
  toLogoutDocument(logoutInfo) {
304
316
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"sessionService.js","sourceRoot":"","sources":["../../src/session/sessionService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sessionService.js","sourceRoot":"","sources":["../../src/session/sessionService.ts"],"names":[],"mappings":"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"}