Skip to content

feat(core): Use AdapterSession retruned from updateSession function #12864

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

Open
wants to merge 2 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
4 changes: 4 additions & 0 deletions packages/core/src/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ export interface Adapter {
/**
* Updates a session in the database and returns it.
*
* :::tip
* If an `AdapterSession` is returned, it will be used in place of the original session.
* :::
*
* See also [Database Session management](https://authjs.dev/guides/creating-a-database-adapter#database-session-management)
*/
updateSession?(
Expand Down
18 changes: 12 additions & 6 deletions packages/core/src/lib/actions/session.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { JWTSessionError, SessionTokenError } from "../../errors.js"
import { fromDate } from "../utils/date.js"

import type { Adapter } from "../../adapters.js"
import type { Adapter, AdapterSession } from "../../adapters.js"
import type { InternalOptions, ResponseInternal, Session } from "../../types.js"
import type { Cookie, SessionStore } from "../utils/cookie.js"

Expand Down Expand Up @@ -119,10 +119,14 @@ export async function session(
sessionUpdateAge * 1000

const newExpires = fromDate(sessionMaxAge)

let newAdapterSession: AdapterSession | null | undefined = null

// Trigger update of session expiry date and write to database, only
// if the session was last updated more than {sessionUpdateAge} ago
// if the session was last updated more than {sessionUpdateAge} ago.
// Use new `AdapterSession` if returned.
if (sessionIsDueToBeUpdatedDate <= Date.now()) {
await updateSession({
newAdapterSession = await updateSession({
sessionToken: sessionToken,
expires: newExpires,
})
Expand All @@ -133,7 +137,7 @@ export async function 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 },
session: { ...(newAdapterSession ? newAdapterSession : session), user },
user,
newSession,
...(isUpdate ? { trigger: "update" } : {}),
Expand All @@ -145,10 +149,12 @@ export async function session(
// Set cookie again to update expiry
response.cookies?.push({
name: options.cookies.sessionToken.name,
value: sessionToken,
value: newAdapterSession
? newAdapterSession.sessionToken
: sessionToken,
options: {
...options.cookies.sessionToken.options,
expires: newExpires,
expires: newAdapterSession ? newAdapterSession.expires : newExpires,
},
})

Expand Down
75 changes: 75 additions & 0 deletions packages/core/test/actions/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,5 +273,80 @@ describe("assert GET session action", () => {

assertNoCacheResponseHeaders(response)
})

it("should return a new session if returned from the adapter", async () => {
vi.spyOn(callbacks, "jwt")
vi.spyOn(callbacks, "session")
const updatedExpires = getExpires()
const currentExpires = getExpires(24 * 60 * 60 * 1000) // 1 day from now

const expectedSessionToken = randomString(32)
const expectedNewSessionToken = randomString(32)
const expectedUserId = randomString(32)
const expectedUser = {
id: expectedUserId,
name: "test",
email: "[email protected]",
image: "https://test.com/test.png",
emailVerified: null,
} satisfies AdapterUser

// const expectedUserId = randomString(32)
const memory = initMemory()
memory.users.set(expectedUserId, expectedUser)
memory.sessions.set(expectedSessionToken, {
sessionToken: expectedSessionToken,
userId: expectedUserId,
expires: currentExpires,
})

const adapter = MemoryAdapter(memory, expectedNewSessionToken)

const { response } = await makeAuthRequest({
action: "session",
cookies: {
[SESSION_COOKIE_NAME]: expectedSessionToken,
},
config: {
adapter,
},
})

const actualBodySession = await response.json()

let cookies = response.headers.getSetCookie().reduce((acc, cookie) => {
return { ...acc, ...parseCookie(cookie) }
}, {})
const actualSessionToken = cookies[SESSION_COOKIE_NAME]

expect(memory.users.get(expectedUserId)).toEqual(expectedUser)
expect(memory.sessions.get(expectedSessionToken)).toEqual({
sessionToken: expectedNewSessionToken,
userId: expectedUserId,
expires: updatedExpires,
})

expect(callbacks.session).toHaveBeenCalledWith({
newSession: undefined,
session: {
user: expectedUser,
expires: currentExpires,
sessionToken: expectedNewSessionToken,
userId: expectedUserId,
},
user: expectedUser,
})
expect(callbacks.jwt).not.toHaveBeenCalled()

expect(actualSessionToken).toEqual(expectedNewSessionToken)
expect(actualBodySession.user).toEqual({
image: expectedUser.image,
name: expectedUser.name,
email: expectedUser.email,
})
expect(actualBodySession.expires).toEqual(currentExpires.toISOString())

assertNoCacheResponseHeaders(response)
})
})
})
15 changes: 14 additions & 1 deletion packages/core/test/memory-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export function initMemory(): Memory {
}
}

export function MemoryAdapter(memory?: Memory): Adapter {
export function MemoryAdapter(
memory?: Memory,
newSessionToken: string | null = null
): Adapter {
const { users, accounts, sessions, verificationTokens } =
memory ?? initMemory()

Expand Down Expand Up @@ -169,6 +172,16 @@ export function MemoryAdapter(memory?: Memory): Adapter {
if (!currentSession) throw new Error("Session not found")

const updatedSession = { ...currentSession, ...session }
if (newSessionToken) {
// Delete old session token if a new one is provided
sessions.delete(session.sessionToken)
// Create a new session with the new session token
updatedSession.sessionToken = newSessionToken
sessions.set(newSessionToken, updatedSession)

return updatedSession
}

sessions.set(session.sessionToken, updatedSession)

return updatedSession
Expand Down