Skip to content

Commit

Permalink
feat: Implement invalidateAllSessions function in Basic API (#1767)
Browse files Browse the repository at this point in the history
Co-authored-by: pilcrowOnPaper <pilcrowonpaper@gmail.com>
  • Loading branch information
hurby24 and pilcrowonpaper authored Feb 12, 2025
1 parent 999c797 commit f4333e2
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 4 deletions.
14 changes: 13 additions & 1 deletion pages/sessions/basic-api/drizzle-orm.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down Expand Up @@ -230,14 +234,18 @@ export async function validateSessionToken(token: string): Promise<SessionValida
Finally, invalidate sessions by simply deleting it from the database.

```ts
import { eq } from "drizzle-orm";
import { db, userTable, sessionTable } from "./db.js";
import { eq } from "drizzle-orm";

// ...

export async function invalidateSession(sessionId: string): Promise<void> {
await db.delete(sessionTable).where(eq(sessionTable.id, sessionId));
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.delete(sessionTable).where(eq(sessionTable.userId, userId));
}
```

Here's the full code:
Expand Down Expand Up @@ -299,6 +307,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
await db.delete(sessionTable).where(eq(sessionTable.id, sessionId));
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.delete(sessionTable).where(eq(sessionTable.userId, userId));
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
12 changes: 12 additions & 0 deletions pages/sessions/basic-api/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down Expand Up @@ -179,6 +183,10 @@ import { db } from "./db.js";
export async function invalidateSession(sessionId: string): Promise<void> {
await db.execute("DELETE FROM user_session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}
```

Here's the full code:
Expand Down Expand Up @@ -247,6 +255,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
await db.execute("DELETE FROM user_session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
12 changes: 12 additions & 0 deletions pages/sessions/basic-api/postgresql.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down Expand Up @@ -179,6 +183,10 @@ import { db } from "./db.js";
export async function invalidateSession(sessionId: string): Promise<void> {
await db.execute("DELETE FROM user_session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}
```

Here's the full code:
Expand Down Expand Up @@ -247,6 +255,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
await db.execute("DELETE FROM user_session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
20 changes: 20 additions & 0 deletions pages/sessions/basic-api/prisma.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down Expand Up @@ -168,6 +172,14 @@ import { prisma } from "./db.js";
export async function invalidateSession(sessionId: string): Promise<void> {
await prisma.session.delete({ where: { id: sessionId } });
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await prisma.session.deleteMany({
where: {
userId: userId
}
});
}
```

Here's the full code:
Expand Down Expand Up @@ -235,6 +247,14 @@ export async function invalidateSession(sessionId: string): Promise<void> {
await prisma.session.delete({ where: { id: sessionId } });
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await prisma.session.deleteMany({
where: {
userId: userId
}
});
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
52 changes: 49 additions & 3 deletions pages/sessions/basic-api/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ export async function invalidateSession(sessionId: string): Promise<void> {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export interface Session {
id: string;
userId: number;
Expand Down Expand Up @@ -63,7 +67,7 @@ export function generateSessionToken(): string {

> 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).
The session ID will be SHA-256 hash of the token. We'll set the expiration to 30 days.
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.

```ts
import { redis } from "./redis.js";
Expand All @@ -90,6 +94,8 @@ export async function createSession(token: string, userId: number): Promise<Sess
EXAT: Math.floor(session.expiresAt / 1000)
}
);
await redis.sadd(`user_sessions:${userId}`, sessionId);

return session;
}
```
Expand All @@ -114,6 +120,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
if (item === null) {
return null;
}

const result = JSON.parse(item);
const session: Session = {
id: result.id,
Expand All @@ -122,6 +129,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
};
if (Date.now() >= session.expiresAt.getTime()) {
await redis.delete(`session:${sessionId}`);
await redis.srem(`user_sessions:${userId}`, sessionId);
return null;
}
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
Expand Down Expand Up @@ -149,8 +157,25 @@ import { redis } from "./redis.js";

// ...

export async function invalidateSession(sessionId: string): Promise<void> {
export async function invalidateSession(sessionId: string, userId: number): Promise<void> {
await redis.delete(`session:${sessionId}`);
await redis.srem(`user_sessions:${userId}`, sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
const sessionIds = await redis.smembers(`user_sessions:${userId}`);
if (sessionIds.length < 1) {
return;
}

const pipeline = redis.pipeline();

for (const sessionId of sessionIds) {
pipeline.unlink(`session:${sessionId}`);
}
pipeline.unlink(`user_sessions:${userId}`);

await pipeline.exec();
}
```

Expand Down Expand Up @@ -186,6 +211,8 @@ export async function createSession(token: string, userId: number): Promise<Sess
EXAT: Math.floor(session.expiresAt / 1000)
}
);
await redis.sadd(`user_sessions:${userId}`, sessionId);

return session;
}

Expand All @@ -195,6 +222,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
if (item === null) {
return null;
}

const result = JSON.parse(item);
const session: Session = {
id: result.id,
Expand All @@ -203,6 +231,7 @@ export async function validateSessionToken(token: string): Promise<Session | nul
};
if (Date.now() >= session.expiresAt.getTime()) {
await redis.delete(`session:${sessionId}`);
await redis.srem(`user_sessions:${userId}`, sessionId);
return null;
}
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
Expand All @@ -222,8 +251,25 @@ export async function validateSessionToken(token: string): Promise<Session | nul
return session;
}

export async function invalidateSession(sessionId: string): Promise<void> {
export async function invalidateSession(sessionId: string, userId: number): Promise<void> {
await redis.delete(`session:${sessionId}`);
await redis.srem(`user_sessions:${userId}`, sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
const sessionIds = await redis.smembers(`user_sessions:${userId}`);
if (sessionIds.length < 1) {
return;
}

const pipeline = redis.pipeline();

for (const sessionId of sessionIds) {
pipeline.unlink(`session:${sessionId}`);
}
pipeline.unlink(`user_sessions:${userId}`);

await pipeline.exec();
}

export interface Session {
Expand Down
12 changes: 12 additions & 0 deletions pages/sessions/basic-api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export function invalidateSession(sessionId: string): void {
// TODO
}

export async function invalidateAllSessions(userId: number): Promise<void> {
// TODO
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down Expand Up @@ -179,6 +183,10 @@ import { db } from "./db.js";
export function invalidateSession(sessionId: string): void {
db.execute("DELETE FROM session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}
```

Here's the full code:
Expand Down Expand Up @@ -247,6 +255,10 @@ export function invalidateSession(sessionId: string): void {
db.execute("DELETE FROM session WHERE id = ?", sessionId);
}

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute("DELETE FROM user_session WHERE user_id = ?", userId);
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down

0 comments on commit f4333e2

Please sign in to comment.