-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
session.ts
160 lines (134 loc) · 4.99 KB
/
session.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { JWTSessionError, SessionTokenError } from "../../errors.js"
import { fromDate } from "../utils/date.js"
import type { Adapter } from "../../adapters.js"
import type { InternalOptions, ResponseInternal, Session } from "../../types.js"
import type { Cookie, SessionStore } from "../utils/cookie.js"
/** Return a session object filtered via `callbacks.session` */
export async function session(
options: InternalOptions,
sessionStore: SessionStore,
cookies: Cookie[],
isUpdate?: boolean,
newSession?: any
): Promise<ResponseInternal<Session | null>> {
const {
adapter,
jwt,
events,
callbacks,
logger,
session: { strategy: sessionStrategy, maxAge: sessionMaxAge },
} = options
const response: ResponseInternal<Session | null> = {
body: null,
headers: { "Content-Type": "application/json" },
cookies,
}
const sessionToken = sessionStore.value
if (!sessionToken) return response
if (sessionStrategy === "jwt") {
try {
const salt = options.cookies.sessionToken.name
const payload = await jwt.decode({ ...jwt, token: sessionToken, salt })
if (!payload) throw new Error("Invalid JWT")
// @ts-expect-error
const token = await callbacks.jwt({
token: payload,
...(isUpdate && { trigger: "update" }),
session: newSession,
})
const newExpires = fromDate(sessionMaxAge)
if (token !== null) {
// By default, only exposes a limited subset of information to the client
// as needed for presentation purposes (e.g. "you are logged in as...").
const session = {
user: { name: token.name, email: token.email, image: token.picture },
expires: newExpires.toISOString(),
}
// @ts-expect-error
const newSession = await callbacks.session({ session, token })
// Return session payload as response
response.body = newSession
// Refresh JWT expiry by re-signing it, with an updated expiry date
const newToken = await jwt.encode({ ...jwt, token, salt })
// Set cookie, to also update expiry date on cookie
const sessionCookies = sessionStore.chunk(newToken, {
expires: newExpires,
})
response.cookies?.push(...sessionCookies)
await events.session?.({ session: newSession, token })
} else {
response.cookies?.push(...sessionStore.clean())
}
} catch (e) {
logger.error(new JWTSessionError(e as Error))
// If the JWT is not verifiable remove the broken session cookie(s).
response.cookies?.push(...sessionStore.clean())
}
return response
}
// Retrieve session from database
try {
const { getSessionAndUser, deleteSession, updateSession } =
adapter as Required<Adapter>
let userAndSession = await getSessionAndUser(sessionToken)
// If session has expired, clean up the database
if (
userAndSession &&
userAndSession.session.expires.valueOf() < Date.now()
) {
await deleteSession(sessionToken)
userAndSession = null
}
if (userAndSession) {
const { user, session } = userAndSession
const sessionUpdateAge = options.session.updateAge
// Calculate last updated date to throttle write updates to database
// Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge
// e.g. ({expiry date} - 30 days) + 1 hour
const sessionIsDueToBeUpdatedDate =
session.expires.valueOf() -
sessionMaxAge * 1000 +
sessionUpdateAge * 1000
const newExpires = fromDate(sessionMaxAge)
// Trigger update of session expiry date and write to database, only
// if the session was last updated more than {sessionUpdateAge} ago
if (sessionIsDueToBeUpdatedDate <= Date.now()) {
await updateSession({
sessionToken: sessionToken,
expires: newExpires,
})
}
// Pass Session through to the session callback
const sessionPayload = await callbacks.session({
// TODO: user already passed below,
// remove from session object in https://github.com/nextauthjs/next-auth/pull/9702
// @ts-expect-error
session: { ...session, user },
user,
newSession,
...(isUpdate ? { trigger: "update" } : {}),
})
// Return session payload as response
response.body = sessionPayload
// Set cookie again to update expiry
response.cookies?.push({
name: options.cookies.sessionToken.name,
value: sessionToken,
options: {
...options.cookies.sessionToken.options,
expires: newExpires,
},
})
// @ts-expect-error
await events.session?.({ session: sessionPayload })
} else if (sessionToken) {
// If `sessionToken` was found set but it's not valid for a session then
// remove the sessionToken cookie from browser.
response.cookies?.push(...sessionStore.clean())
}
} catch (e) {
logger.error(new SessionTokenError(e as Error))
}
return response
}