Skip to content

Commit 79b1a34

Browse files
fix: make use of sessions
1 parent 708d72a commit 79b1a34

8 files changed

Lines changed: 142 additions & 3271 deletions

File tree

bun.lockb

-94.7 KB
Binary file not shown.

package-lock.json

Lines changed: 0 additions & 3257 deletions
This file was deleted.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"@simplewebauthn/server": "^13.1.0",
9090
"jose": "6.0.8",
9191
"oauth4webapi": "^3.1.4",
92-
"qs-esm": "7.0.2"
92+
"qs-esm": "7.0.2",
93+
"uuid": "11.1.0"
9394
}
9495
}

src/core/errors/apiErrors.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,9 @@ export class MissingOrInvalidVerification extends AuthAPIError {
105105
)
106106
}
107107
}
108+
109+
export class MissingCollection extends AuthAPIError {
110+
constructor() {
111+
super("Missing collection", ErrorKind.NotFound)
112+
}
113+
}

src/core/protocols/oauth/oauth_authentication.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import * as jose from "jose"
22
import type { JsonObject, PayloadRequest, TypeWithID } from "payload"
33
import { APP_COOKIE_SUFFIX } from "../../../constants.js"
4-
import { UserNotFoundAPIError } from "../../errors/apiErrors.js"
4+
import {
5+
MissingCollection,
6+
UserNotFoundAPIError,
7+
} from "../../errors/apiErrors.js"
58
import {
69
createSessionCookies,
710
invalidateOAuthCookies,
811
} from "../../utils/cookies.js"
912

13+
import { v4 as uuid } from "uuid"
14+
import { removeExpiredSessions } from "../../utils/session.js"
1015
export async function OAuthAuthentication(
1116
pluginType: string,
1217
collections: {
@@ -106,15 +111,49 @@ export async function OAuthAuthentication(
106111

107112
let cookies: string[] = []
108113

114+
const collectionConfig = payload.config.collections.find(
115+
(collection) => collection.slug === collections.usersCollection,
116+
)
117+
if (!collectionConfig) {
118+
return new MissingCollection()
119+
}
120+
121+
const sessionID = collectionConfig?.auth.useSessions ? uuid() : null
122+
123+
if (collectionConfig?.auth.useSessions) {
124+
const now = new Date()
125+
const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000
126+
const expiresAt = new Date(now.getTime() + tokenExpInMs)
127+
const session = { id: sessionID, createdAt: now, expiresAt }
128+
if (!userRecord["sessions"]?.length) {
129+
userRecord["sessions"] = [session]
130+
} else {
131+
userRecord.sessions = removeExpiredSessions(userRecord.sessions)
132+
userRecord.sessions.push(session)
133+
}
134+
await payload.db.updateOne({
135+
id: userRecord.id,
136+
collection: collections.usersCollection,
137+
data: userRecord,
138+
req: request,
139+
returning: false,
140+
})
141+
}
109142
const cookieName = useAdmin
110143
? `${payload.config.cookiePrefix}-token`
111144
: `__${pluginType}-${APP_COOKIE_SUFFIX}`
112145
cookies = [
113-
...(await createSessionCookies(cookieName, secret, {
114-
id: userRecord.id,
115-
email: email,
116-
collection: collections.usersCollection,
117-
})),
146+
...(await createSessionCookies(
147+
cookieName,
148+
secret,
149+
{
150+
id: userRecord.id,
151+
email: email,
152+
sid: sessionID,
153+
collection: collections.usersCollection,
154+
},
155+
useAdmin ? collectionConfig?.auth.tokenExpiration : undefined,
156+
)),
118157
]
119158
cookies = invalidateOAuthCookies(cookies)
120159
const successRedirectionURL = new URL(

src/core/protocols/password.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
EmailAlreadyExistError,
44
InvalidCredentials,
55
InvalidRequestBodyError,
6+
MissingCollection,
67
MissingOrInvalidVerification,
78
UnauthorizedAPIRequest,
89
UserNotFoundAPIError,
@@ -16,17 +17,27 @@ import {
1617
invalidateOAuthCookies,
1718
verifySessionCookie,
1819
} from "../utils/cookies.js"
20+
import { v4 as uuid } from "uuid"
21+
import { removeExpiredSessions } from "../utils/session.js"
1922

2023
const redirectWithSession = async (
2124
cookieName: string,
2225
path: string,
2326
secret: string,
24-
fields: Record<string, string | number>,
27+
fields: Record<string, string | number | null>,
2528
request: PayloadRequest,
29+
tokenExpiration?: number,
2630
) => {
2731
let cookies = []
2832

29-
cookies = [...(await createSessionCookies(cookieName, secret, fields))]
33+
cookies = [
34+
...(await createSessionCookies(
35+
cookieName,
36+
secret,
37+
fields,
38+
tokenExpiration,
39+
)),
40+
]
3041
cookies = invalidateOAuthCookies(cookies)
3142
const successRedirectionURL = new URL(`${request.origin}${path}`)
3243
const res = new Response(null, {
@@ -91,12 +102,42 @@ export const PasswordSignin = async (
91102
return new InvalidCredentials()
92103
}
93104

105+
const collectionConfig = payload.config.collections.find(
106+
(collection) => collection.slug === internal.usersCollectionSlug,
107+
)
108+
if (!collectionConfig) {
109+
return new MissingCollection()
110+
}
111+
112+
const sessionID = collectionConfig?.auth.useSessions ? uuid() : null
113+
114+
if (collectionConfig?.auth.useSessions) {
115+
const now = new Date()
116+
const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000
117+
const expiresAt = new Date(now.getTime() + tokenExpInMs)
118+
const session = { id: sessionID, createdAt: now, expiresAt }
119+
if (!userRecord["sessions"]?.length) {
120+
userRecord["sessions"] = [session]
121+
} else {
122+
userRecord.sessions = removeExpiredSessions(userRecord.sessions)
123+
userRecord.sessions.push(session)
124+
}
125+
await payload.db.updateOne({
126+
id: userRecord.id,
127+
collection: internal.usersCollectionSlug,
128+
data: userRecord,
129+
req: request,
130+
returning: false,
131+
})
132+
}
133+
94134
const cookieName = useAdmin
95135
? `${payload.config.cookiePrefix}-token`
96136
: `__${pluginType}-${APP_COOKIE_SUFFIX}`
97137
const signinFields = {
98138
id: userRecord.id,
99139
email,
140+
sid: sessionID,
100141
collection: internal.usersCollectionSlug,
101142
}
102143
return await redirectWithSession(
@@ -105,6 +146,7 @@ export const PasswordSignin = async (
105146
secret,
106147
signinFields,
107148
request,
149+
useAdmin ? collectionConfig.auth.tokenExpiration : undefined,
108150
)
109151
}
110152

@@ -164,12 +206,42 @@ export const PasswordSignup = async (
164206
})
165207

166208
if (body.allowAutoSignin) {
209+
const collectionConfig = payload.config.collections.find(
210+
(collection) => collection.slug === internal.usersCollectionSlug,
211+
)
212+
if (!collectionConfig) {
213+
return new MissingCollection()
214+
}
215+
216+
const sessionID = collectionConfig?.auth.useSessions ? uuid() : null
217+
218+
if (collectionConfig?.auth.useSessions) {
219+
const now = new Date()
220+
const tokenExpInMs = collectionConfig.auth.tokenExpiration * 1000
221+
const expiresAt = new Date(now.getTime() + tokenExpInMs)
222+
const session = { id: sessionID, createdAt: now, expiresAt }
223+
if (!userRecord["sessions"]?.length) {
224+
userRecord["sessions"] = [session]
225+
} else {
226+
userRecord.sessions = removeExpiredSessions(userRecord.sessions)
227+
userRecord.sessions.push(session)
228+
}
229+
await payload.db.updateOne({
230+
id: userRecord.id,
231+
collection: internal.usersCollectionSlug,
232+
data: userRecord,
233+
req: request,
234+
returning: false,
235+
})
236+
}
237+
167238
const cookieName = useAdmin
168239
? `${payload.config.cookiePrefix}-token`
169240
: `__${pluginType}-${APP_COOKIE_SUFFIX}`
170241
const signinFields = {
171242
id: userRecord.id,
172243
email,
244+
sid: sessionID,
173245
collection: internal.usersCollectionSlug,
174246
}
175247
return await redirectWithSession(
@@ -178,6 +250,7 @@ export const PasswordSignup = async (
178250
secret,
179251
signinFields,
180252
request,
253+
useAdmin ? collectionConfig.auth.tokenExpiration : undefined,
181254
)
182255
}
183256

src/core/utils/cookies.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ export async function createSessionCookies(
55
name: string,
66
secret: string,
77
fieldsToSign: Record<string, unknown>,
8+
expiration?: number,
89
) {
9-
const tokenExpiration = getCookieExpiration({
10-
seconds: 7200,
11-
})
10+
const tokenExpiration =
11+
expiration ??
12+
getCookieExpiration({
13+
seconds: 7200,
14+
}).getTime()
1215

1316
const secretKey = new TextEncoder().encode(secret)
1417
const issuedAt = Math.floor(Date.now() / 1000)
15-
const exp = issuedAt + tokenExpiration.getTime()
18+
const exp = issuedAt + tokenExpiration
1619
const token = await new jwt.SignJWT(fieldsToSign)
1720
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
1821
.setIssuedAt(issuedAt)
@@ -21,7 +24,7 @@ export async function createSessionCookies(
2124

2225
const cookies: string[] = []
2326
cookies.push(
24-
`${name}=${token};Path=/;HttpOnly;Secure;SameSite=lax;Expires=${tokenExpiration.toUTCString()}`,
27+
`${name}=${token};Path=/;HttpOnly;SameSite=lax;Expires=${getCookieExpiration({ seconds: expiration! }).toUTCString()}`,
2528
)
2629
return cookies
2730
}

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,9 @@ export type ProvidersConfig =
143143
| PasswordProviderConfig
144144

145145
export type AuthenticationStrategy = "Cookie"
146+
147+
export type UserSession = {
148+
createdAt: Date | string
149+
expiresAt: Date | string
150+
id: string
151+
}

0 commit comments

Comments
 (0)