Skip to content

Commit 591ae81

Browse files
Fix db inconsistencies (#1798)
1 parent 84ac2e7 commit 591ae81

File tree

7 files changed

+94
-70
lines changed

7 files changed

+94
-70
lines changed

pages/sessions/basic-api/drizzle-orm.md

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ import type { InferSelectModel } from "drizzle-orm";
2222
const connection = await mysql.createConnection();
2323
const db = drizzle(connection);
2424

25+
// your user table
2526
export const userTable = mysqlTable("user", {
26-
id: int("id").primaryKey().autoincrement()
27+
id: int("id").primaryKey().autoincrement(),
28+
username: varchar("username", {
29+
length: 31
30+
}).notNull()
2731
});
2832

29-
export const sessionTable = mysqlTable("session", {
33+
export const userSessionTable = mysqlTable("user_session", {
3034
id: varchar("id", {
3135
length: 255
3236
}).primaryKey(),
@@ -37,7 +41,7 @@ export const sessionTable = mysqlTable("session", {
3741
});
3842

3943
export type User = InferSelectModel<typeof userTable>;
40-
export type Session = InferSelectModel<typeof sessionTable>;
44+
export type Session = InferSelectModel<typeof userSessionTable>;
4145
```
4246

4347
### PostgreSQL
@@ -52,11 +56,13 @@ import type { InferSelectModel } from "drizzle-orm";
5256
const pool = new pg.Pool();
5357
const db = drizzle(pool);
5458

59+
// your user table
5560
export const userTable = pgTable("user", {
56-
id: serial("id").primaryKey()
61+
id: serial("id").primaryKey(),
62+
username: text("username").notNull()
5763
});
5864

59-
export const sessionTable = pgTable("session", {
65+
export const userSessionTable = pgTable("user_session", {
6066
id: text("id").primaryKey(),
6167
userId: integer("user_id")
6268
.notNull()
@@ -68,7 +74,7 @@ export const sessionTable = pgTable("session", {
6874
});
6975

7076
export type User = InferSelectModel<typeof userTable>;
71-
export type Session = InferSelectModel<typeof sessionTable>;
77+
export type Session = InferSelectModel<typeof userSessionTable>;
7278
```
7379

7480
### SQLite
@@ -83,11 +89,13 @@ import type { InferSelectModel } from "drizzle-orm";
8389
const sqliteDB = sqlite(":memory:");
8490
const db = drizzle(sqliteDB);
8591

92+
// your user table
8693
export const userTable = sqliteTable("user", {
87-
id: integer("id").primaryKey()
94+
id: integer("id").primaryKey(),
95+
username: text("username").notNull()
8896
});
8997

90-
export const sessionTable = sqliteTable("session", {
98+
export const userSessionTable = sqliteTable("user_session", {
9199
id: text("id").primaryKey(),
92100
userId: integer("user_id")
93101
.notNull()
@@ -98,7 +106,7 @@ export const sessionTable = sqliteTable("session", {
98106
});
99107

100108
export type User = InferSelectModel<typeof userTable>;
101-
export type Session = InferSelectModel<typeof sessionTable>;
109+
export type Session = InferSelectModel<typeof userSessionTable>;
102110
```
103111

104112
## Install dependencies
@@ -167,7 +175,7 @@ export function generateSessionToken(): string {
167175
The session ID will be SHA-256 hash of the token. We'll set the expiration to 30 days.
168176

169177
```ts
170-
import { db, userTable, sessionTable } from "./db.js";
178+
import { db, userTable, userSessionTable } from "./db.js";
171179
import { eq } from "drizzle-orm";
172180
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
173181
import { sha256 } from "@oslojs/crypto/sha2";
@@ -181,7 +189,7 @@ export async function createSession(token: string, userId: number): Promise<Sess
181189
userId,
182190
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)
183191
};
184-
await db.insert(sessionTable).values(session);
192+
await db.insert(userSessionTable).values(session);
185193
return session;
186194
}
187195
```
@@ -196,7 +204,7 @@ We'll also extend the session expiration when it's close to expiration. This ens
196204
For convenience, we'll return both the session and user object tied to the session ID.
197205

198206
```ts
199-
import { db, userTable, sessionTable } from "./db.js";
207+
import { db, userTable, userSessionTable } from "./db.js";
200208
import { eq } from "drizzle-orm";
201209
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
202210
import { sha256 } from "@oslojs/crypto/sha2";
@@ -206,26 +214,26 @@ import { sha256 } from "@oslojs/crypto/sha2";
206214
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
207215
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
208216
const result = await db
209-
.select({ user: userTable, session: sessionTable })
210-
.from(sessionTable)
211-
.innerJoin(userTable, eq(sessionTable.userId, userTable.id))
212-
.where(eq(sessionTable.id, sessionId));
217+
.select({ user: userTable, session: userSessionTable })
218+
.from(userSessionTable)
219+
.innerJoin(userTable, eq(userSessionTable.userId, userTable.id))
220+
.where(eq(userSessionTable.id, sessionId));
213221
if (result.length < 1) {
214222
return { session: null, user: null };
215223
}
216224
const { user, session } = result[0];
217225
if (Date.now() >= session.expiresAt.getTime()) {
218-
await db.delete(sessionTable).where(eq(sessionTable.id, session.id));
226+
await db.delete(userSessionTable).where(eq(userSessionTable.id, session.id));
219227
return { session: null, user: null };
220228
}
221229
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
222230
session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
223231
await db
224-
.update(sessionTable)
232+
.update(userSessionTable)
225233
.set({
226234
expiresAt: session.expiresAt
227235
})
228-
.where(eq(sessionTable.id, session.id));
236+
.where(eq(userSessionTable.id, session.id));
229237
}
230238
return { session, user };
231239
}
@@ -234,24 +242,24 @@ export async function validateSessionToken(token: string): Promise<SessionValida
234242
Finally, invalidate sessions by simply deleting it from the database.
235243

236244
```ts
237-
import { db, userTable, sessionTable } from "./db.js";
245+
import { db, userTable, userSessionTable } from "./db.js";
238246
import { eq } from "drizzle-orm";
239247

240248
// ...
241249

242250
export async function invalidateSession(sessionId: string): Promise<void> {
243-
await db.delete(sessionTable).where(eq(sessionTable.id, sessionId));
251+
await db.delete(userSessionTable).where(eq(userSessionTable.id, sessionId));
244252
}
245253

246254
export async function invalidateAllSessions(userId: number): Promise<void> {
247-
await db.delete(sessionTable).where(eq(sessionTable.userId, userId));
255+
await db.delete(userSessionTable).where(eq(userSessionTable.userId, userId));
248256
}
249257
```
250258

251259
Here's the full code:
252260

253261
```ts
254-
import { db, userTable, sessionTable } from "./db.js";
262+
import { db, userTable, userSessionTable } from "./db.js";
255263
import { eq } from "drizzle-orm";
256264
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
257265
import { sha256 } from "@oslojs/crypto/sha2";
@@ -272,43 +280,43 @@ export async function createSession(token: string, userId: number): Promise<Sess
272280
userId,
273281
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)
274282
};
275-
await db.insert(sessionTable).values(session);
283+
await db.insert(userSessionTable).values(session);
276284
return session;
277285
}
278286

279287
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
280288
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
281289
const result = await db
282-
.select({ user: userTable, session: sessionTable })
283-
.from(sessionTable)
284-
.innerJoin(userTable, eq(sessionTable.userId, userTable.id))
285-
.where(eq(sessionTable.id, sessionId));
290+
.select({ user: userTable, session: userSessionTable })
291+
.from(userSessionTable)
292+
.innerJoin(userTable, eq(userSessionTable.userId, userTable.id))
293+
.where(eq(userSessionTable.id, sessionId));
286294
if (result.length < 1) {
287295
return { session: null, user: null };
288296
}
289297
const { user, session } = result[0];
290298
if (Date.now() >= session.expiresAt.getTime()) {
291-
await db.delete(sessionTable).where(eq(sessionTable.id, session.id));
299+
await db.delete(userSessionTable).where(eq(userSessionTable.id, session.id));
292300
return { session: null, user: null };
293301
}
294302
if (Date.now() >= session.expiresAt.getTime() - 1000 * 60 * 60 * 24 * 15) {
295303
session.expiresAt = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30);
296304
await db
297-
.update(sessionTable)
305+
.update(userSessionTable)
298306
.set({
299307
expiresAt: session.expiresAt
300308
})
301-
.where(eq(sessionTable.id, session.id));
309+
.where(eq(userSessionTable.id, session.id));
302310
}
303311
return { session, user };
304312
}
305313

306314
export async function invalidateSession(sessionId: string): Promise<void> {
307-
await db.delete(sessionTable).where(eq(sessionTable.id, sessionId));
315+
await db.delete(userSessionTable).where(eq(userSessionTable.id, sessionId));
308316
}
309317

310318
export async function invalidateAllSessions(userId: number): Promise<void> {
311-
await db.delete(sessionTable).where(eq(sessionTable.userId, userId));
319+
await db.delete(userSessionTable).where(eq(userSessionTable.userId, userId));
312320
}
313321

314322
export type SessionValidationResult =

pages/sessions/basic-api/mysql.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Users will use a session token linked to a session instead of the ID directly. T
1111
Create a session table with a field for a text ID, user ID, and expiration.
1212

1313
```
14+
-- your user table
1415
CREATE TABLE user (
1516
id INT PRIMARY KEY AUTO_INCREMENT,
1617
username VARCHAR(255) NOT NULL UNIQUE
@@ -142,6 +143,7 @@ import { sha256 } from "@oslojs/crypto/sha2";
142143

143144
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
144145
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
146+
// TODO: update query to match your user table
145147
const row = await db.queryOne(
146148
"SELECT user_session.id, user_session.user_id, user_session.expires_at, user.id FROM user_session INNER JOIN user ON user.id = user_session.user_id WHERE id = ?",
147149
sessionId
@@ -221,6 +223,7 @@ export async function createSession(token: string, userId: number): Promise<Sess
221223

222224
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
223225
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
226+
// TODO: update query to match your user table
224227
const row = await db.queryOne(
225228
"SELECT user_session.id, user_session.user_id, user_session.expires_at, user.id FROM user_session INNER JOIN user ON user.id = user_session.user_id WHERE id = ?",
226229
sessionId

pages/sessions/basic-api/postgresql.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Users will use a session token linked to a session instead of the ID directly. T
1111
Create a session table with a field for a text ID, user ID, and expiration.
1212

1313
```
14+
-- your user table
15+
-- "app_user" instead of "user" to avoid name collisions
1416
CREATE TABLE app_user (
1517
id SERIAL PRIMARY KEY,
1618
username TEXT NOT NULL UNIQUE
@@ -142,6 +144,7 @@ import { sha256 } from "@oslojs/crypto/sha2";
142144

143145
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
144146
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
147+
// TODO: update query to match your user table
145148
const row = await db.queryOne(
146149
"SELECT user_session.id, user_session.user_id, user_session.expires_at, app_user.id FROM user_session INNER JOIN user ON app_user.id = user_session.user_id WHERE id = ?",
147150
sessionId
@@ -221,6 +224,7 @@ export async function createSession(token: string, userId: number): Promise<Sess
221224

222225
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
223226
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
227+
// TODO: update query to match your user table
224228
const row = await db.queryOne(
225229
"SELECT user_session.id, user_session.user_id, user_session.expires_at, app_user.id FROM user_session INNER JOIN user ON app_user.id = user_session.user_id WHERE id = ?",
226230
sessionId

pages/sessions/basic-api/prisma.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ Users will use a session token linked to a session instead of the ID directly. T
88

99
## Declare your schema
1010

11-
Create a session model with a field for a text ID, user ID, and expiration.
11+
Create a session model with a field for a text ID, user ID, and expiration. Add a relation field to your user table.
1212

1313
```
14+
// your user table
1415
model User {
1516
id Int @id @default(autoincrement())
17+
username String
1618
sessions Session[]
1719
}
1820
19-
model Session {
21+
model UserSession {
2022
id String @id
2123
userId Int
2224
expiresAt DateTime
@@ -40,17 +42,17 @@ Here's what our API will look like. What each method does should be pretty self
4042
If you just need the full code without the explanation, skip to the end of this section.
4143

4244
```ts
43-
import type { User, Session } from "@prisma/client";
45+
import type { User, UserSession } from "@prisma/client";
4446

4547
export function generateSessionToken(): string {
4648
// TODO
4749
}
4850

49-
export async function createSession(token: string, userId: number): Promise<Session> {
51+
export async function createSession(token: string, userId: number): Promise<UserSession> {
5052
// TODO
5153
}
5254

53-
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
55+
export async function validateSessionToken(token: string): Promise<UserSessionValidationResult> {
5456
// TODO
5557
}
5658

@@ -62,8 +64,8 @@ export async function invalidateAllSessions(userId: number): Promise<void> {
6264
// TODO
6365
}
6466

65-
export type SessionValidationResult =
66-
| { session: Session; user: User }
67+
export type UserSessionValidationResult =
68+
| { session: UserSession; user: User }
6769
| { session: null; user: null };
6870
```
6971

@@ -99,9 +101,9 @@ import { sha256 } from "@oslojs/crypto/sha2";
99101

100102
// ...
101103

102-
export async function createSession(token: string, userId: number): Promise<Session> {
104+
export async function createSession(token: string, userId: number): Promise<UserSession> {
103105
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
104-
const session: Session = {
106+
const session: UserSession = {
105107
id: sessionId,
106108
userId,
107109
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)
@@ -129,7 +131,7 @@ import { sha256 } from "@oslojs/crypto/sha2";
129131

130132
// ...
131133

132-
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
134+
export async function validateSessionToken(token: string): Promise<UserSessionValidationResult> {
133135
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
134136
const result = await prisma.session.findUnique({
135137
where: {
@@ -189,7 +191,7 @@ import { prisma } from "./db.js";
189191
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from "@oslojs/encoding";
190192
import { sha256 } from "@oslojs/crypto/sha2";
191193

192-
import type { User, Session } from "@prisma/client";
194+
import type { User, UserSession } from "@prisma/client";
193195

194196
export function generateSessionToken(): string {
195197
const bytes = new Uint8Array(20);
@@ -198,9 +200,9 @@ export function generateSessionToken(): string {
198200
return token;
199201
}
200202

201-
export async function createSession(token: string, userId: number): Promise<Session> {
203+
export async function createSession(token: string, userId: number): Promise<UserSession> {
202204
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
203-
const session: Session = {
205+
const session: UserSession = {
204206
id: sessionId,
205207
userId,
206208
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)
@@ -211,7 +213,7 @@ export async function createSession(token: string, userId: number): Promise<Sess
211213
return session;
212214
}
213215

214-
export async function validateSessionToken(token: string): Promise<SessionValidationResult> {
216+
export async function validateSessionToken(token: string): Promise<UserSessionValidationResult> {
215217
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
216218
const result = await prisma.session.findUnique({
217219
where: {
@@ -255,8 +257,8 @@ export async function invalidateAllSessions(userId: number): Promise<void> {
255257
});
256258
}
257259

258-
export type SessionValidationResult =
259-
| { session: Session; user: User }
260+
export type UserSessionValidationResult =
261+
| { session: UserSession; user: User }
260262
| { session: null; user: null };
261263
```
262264

0 commit comments

Comments
 (0)