@@ -33,6 +33,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
3333 // TODO
3434}
3535
36+ export async function invalidateAllSessions(userId : number ): Promise <void > {
37+ // TODO
38+ }
39+
3640export interface Session {
3741 id: string ;
3842 userId: number ;
@@ -63,7 +67,7 @@ export function generateSessionToken(): string {
6367
6468> You can use UUID v4 here but the RFC does not mandate that IDs are generated using a secure random source. Do not use libraries that are not clear on the source they use. Do not use other UUID versions as they do not offer the same entropy size as v4. Consider using [ ` Crypto.randomUUID() ` ] ( https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID ) .
6569
66- The session ID will be SHA-256 hash of the token. We'll set the expiration to 30 days.
70+ The session ID will be SHA-256 hash of the token. We'll set the expiration to 30 days. We'll also keep a list of sessions linked to each user.
6771
6872``` ts
6973import { redis } from " ./redis.js" ;
@@ -90,6 +94,8 @@ export async function createSession(token: string, userId: number): Promise<Sess
9094 EXAT: Math .floor (session .expiresAt / 1000 )
9195 }
9296 );
97+ await redis .sadd (` user_sessions:${userId } ` , sessionId );
98+
9399 return session ;
94100}
95101```
@@ -114,6 +120,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
114120 if (item === null ) {
115121 return null ;
116122 }
123+
117124 const result = JSON .parse (item );
118125 const session: Session = {
119126 id: result .id ,
@@ -122,6 +129,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
122129 };
123130 if (Date .now () >= session .expiresAt .getTime ()) {
124131 await redis .delete (` session:${sessionId } ` );
132+ await redis .srem (` user_sessions:${userId } ` , sessionId );
125133 return null ;
126134 }
127135 if (Date .now () >= session .expiresAt .getTime () - 1000 * 60 * 60 * 24 * 15 ) {
@@ -149,8 +157,25 @@ import { redis } from "./redis.js";
149157
150158// ...
151159
152- export async function invalidateSession(sessionId : string ): Promise <void > {
160+ export async function invalidateSession(sessionId : string , userId : number ): Promise <void > {
153161 await redis .delete (` session:${sessionId } ` );
162+ await redis .srem (` user_sessions:${userId } ` , sessionId );
163+ }
164+
165+ export async function invalidateAllSessions(userId : number ): Promise <void > {
166+ const sessionIds = await redis .smembers (` user_sessions:${userId } ` );
167+ if (sessionIds .length < 1 ) {
168+ return ;
169+ }
170+
171+ const pipeline = redis .pipeline ();
172+
173+ for (const sessionId of sessionIds ) {
174+ pipeline .unlink (` session:${sessionId } ` );
175+ }
176+ pipeline .unlink (` user_sessions:${userId } ` );
177+
178+ await pipeline .exec ();
154179}
155180```
156181
@@ -186,6 +211,8 @@ export async function createSession(token: string, userId: number): Promise<Sess
186211 EXAT: Math .floor (session .expiresAt / 1000 )
187212 }
188213 );
214+ await redis .sadd (` user_sessions:${userId } ` , sessionId );
215+
189216 return session ;
190217}
191218
@@ -195,6 +222,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
195222 if (item === null ) {
196223 return null ;
197224 }
225+
198226 const result = JSON .parse (item );
199227 const session: Session = {
200228 id: result .id ,
@@ -203,6 +231,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
203231 };
204232 if (Date .now () >= session .expiresAt .getTime ()) {
205233 await redis .delete (` session:${sessionId } ` );
234+ await redis .srem (` user_sessions:${userId } ` , sessionId );
206235 return null ;
207236 }
208237 if (Date .now () >= session .expiresAt .getTime () - 1000 * 60 * 60 * 24 * 15 ) {
@@ -222,8 +251,25 @@ export async function validateSessionToken(token: string): Promise<Session | nul
222251 return session ;
223252}
224253
225- export async function invalidateSession(sessionId : string ): Promise <void > {
254+ export async function invalidateSession(sessionId : string , userId : number ): Promise <void > {
226255 await redis .delete (` session:${sessionId } ` );
256+ await redis .srem (` user_sessions:${userId } ` , sessionId );
257+ }
258+
259+ export async function invalidateAllSessions(userId : number ): Promise <void > {
260+ const sessionIds = await redis .smembers (` user_sessions:${userId } ` );
261+ if (sessionIds .length < 1 ) {
262+ return ;
263+ }
264+
265+ const pipeline = redis .pipeline ();
266+
267+ for (const sessionId of sessionIds ) {
268+ pipeline .unlink (` session:${sessionId } ` );
269+ }
270+ pipeline .unlink (` user_sessions:${userId } ` );
271+
272+ await pipeline .exec ();
227273}
228274
229275export interface Session {
0 commit comments