Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement invalidateAllSessions function in Basic API #1767

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 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 @@ -240,11 +244,26 @@ export async function invalidateSession(sessionId: string): Promise<void> {
}
```

Additionally, we can invalidate all sessions for a specific user by checking their user ID and expiration times.

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

// ...

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

Here's the full code:

```ts
import { db, userTable, sessionTable } from "./db.js";
import { eq } from "drizzle-orm";
import { and, gt, eq } from "drizzle-orm";
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";

Expand Down Expand Up @@ -299,6 +318,12 @@ export async function invalidateSession(sessionId: string): void {
await db.delete(sessionTable).where(eq(sessionTable.id, sessionId));
}

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

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
28 changes: 28 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 @@ -181,6 +185,22 @@ export async function invalidateSession(sessionId: string): Promise<void> {
}
```

Additionally, we can invalidate all sessions for a specific user by checking their user ID and expiration times.

```ts
import { db } from "./db.js";

// ...

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

Here's the full code:

```ts
Expand Down Expand Up @@ -247,6 +267,14 @@ 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 = ? AND expires_at > ?",
userId,
new Date()
);
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
28 changes: 28 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 @@ -181,6 +185,22 @@ export async function invalidateSession(sessionId: string): Promise<void> {
}
```

Additionally, we can invalidate all sessions for a specific user by checking their user ID and expiration times.

```ts
import { db } from "./db.js";

// ...

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

Here's the full code:

```ts
Expand Down Expand Up @@ -247,6 +267,14 @@ 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 = ? AND expires_at > ?",
userId,
new Date()
);
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
34 changes: 34 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 @@ -170,6 +174,25 @@ export async function invalidateSession(sessionId: string): Promise<void> {
}
```

Additionally, we can invalidate all sessions for a specific user by checking their user ID and expiration times.

```ts
import { prisma } from "./db.js";

// ...

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

Here's the full code:

```ts
Expand Down Expand Up @@ -235,6 +258,17 @@ 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,
expiresAt: {
gt: new Date(),
},
},
});
}

export type SessionValidationResult =
| { session: Session; user: User }
| { session: null; user: null };
Expand Down
70 changes: 66 additions & 4 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 @@ -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 @@ -108,12 +114,19 @@ import { sha256 } from "@oslojs/crypto/sha2";

// ...

export async function validateSessionToken(token: string): Promise<Session | null> {
export async function validateSessionToken(token: string, userId: number): Promise<Session | null> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const item = await redis.get(`session:${sessionId}`);
if (item === null) {
return null;
}

const isSessionValidForUser = await redis.sismember(`user_sessions:${userId}`, sessionId);
if (isSessionValidForUser !== 1) {
await redis.delete(`session:${sessionId}`);
return null;
}

const result = JSON.parse(item);
const session: Session = {
id: result.id,
Expand All @@ -122,6 +135,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 +163,31 @@ 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);
}
```

Additionally, we can invalidate all sessions for a specific user by retrieving all session IDs associated with the user from Redis and then deleting them in a batch operation.

```ts
import { redis } from "./redis.js";

// ...

export async function invalidateAllSessions(userId: number): Promise<void> {
const sessionIds = await redis.smembers(`user_sessions:${userId}`);
if (sessionIds.length === 0) 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,15 +223,24 @@ 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;
}

export async function validateSessionToken(token: string): Promise<Session | null> {
export async function validateSessionToken(token: string, userId: number): Promise<Session | null> {
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
const item = await redis.get(`session:${sessionId}`);
if (item === null) {
return null;
}

const isSessionValidForUser = await redis.sismember(`user_sessions:${userId}`, sessionId);
if (isSessionValidForUser !== 1) {
await redis.delete(`session:${sessionId}`);
return null;
}

const result = JSON.parse(item);
const session: Session = {
id: result.id,
Expand All @@ -203,6 +249,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 +269,23 @@ 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 === 0) 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
29 changes: 29 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 @@ -181,6 +185,23 @@ export function invalidateSession(sessionId: string): void {
}
```

Additionally, we can invalidate all sessions for a specific user by checking their user ID and expiration times.

```ts
import { db } from "./db.js";

// ...

export async function invalidateAllSessions(userId: number): Promise<void> {
await db.execute(
"DELETE FROM user_session WHERE user_id = ? AND expires_at > ?",
userId,
Math.floor(Date.now() / 1000)
);
}
```


Here's the full code:

```ts
Expand Down Expand Up @@ -247,6 +268,14 @@ 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 = ? AND expires_at > ?",
userId,
Math.floor(Date.now() / 1000)
);
}

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