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

Session Management Bug for Neo4jAdapter #9933

Open
csmcallister opened this issue Feb 6, 2024 · 0 comments · May be fixed by #9934
Open

Session Management Bug for Neo4jAdapter #9933

csmcallister opened this issue Feb 6, 2024 · 0 comments · May be fixed by #9934
Labels
adapters Changes related to the core code concerning database adapters bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@csmcallister
Copy link

csmcallister commented Feb 6, 2024

Adapter type

@auth/neo4j-adapter

Environment

System:
OS: Linux 5.15 Ubuntu 22.04.3 LTS 22.04.3 LTS (Jammy Jellyfish)
CPU: (12) x64 AMD Ryzen 5 3600X 6-Core Processor
Memory: 13.23 GB / 15.58 GB
Container: Yes
Shell: 5.1.16 - /bin/bash
Binaries:
Node: 20.11.0 - ~/.nvm/versions/node/v20.11.0/bin/node
npm: 10.2.4 - ~/.nvm/versions/node/v20.11.0/bin/npm
pnpm: 8.15.1 - ~/.nvm/versions/node/v20.11.0/bin/pnpm
npmPackages:
@auth/neo4j-adapter: ^1.3.1 => 1.3.1
next: ^14.1.0 => 14.1.0
next-auth: beta => 5.0.0-beta.5
react: ^18.2.0 => 18.2.0

Reproduction URL

https://github.com/csmcallister/next-auth-example-neo4j-adapter-bug

Describe the issue

This is related to #5849, but perhaps distinct because it's occurring with @auth/neo4j-adapter and next-auth 5 (beta). After cloning the example app and adding in the neo4j adapter, I am able to login (using Google as a provider) and hit each of the example pages (RSC, client, protected API and middleware) to see that I'm successfully logged in. However, both the client and RSC pages raise the following error:

[auth][error] AdapterError: Read more at https://errors.authjs.dev#adaptererror
[auth][cause]: Neo4jError: You cannot begin a transaction on a session with an open transaction; either run from within the transaction or use a different session.
    at new Neo4jError (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/error.js:74:16)
    at newError (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/error.js:106:12)
    at Session._beginTransaction (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/session.js:404:40)
    at eval (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/session.js:531:26)
    at TransactionExecutor.eval (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:280:37)
    at step (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:117:23)
    at Object.eval [as next] (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:58:20)
    at eval (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:36:71)
    at new Promise (<anonymous>)
    at __awaiter (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:18:12)
    at TransactionExecutor._executeTransactionInsidePromise (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:268:16)
    at eval (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:230:19)
    at new Promise (<anonymous>)
    at TransactionExecutor.execute (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/internal/transaction-executor.js:229:16)
    at Session._runTransaction (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/session.js:530:42)
    at Session.writeTransaction (webpack-internal:///(rsc)/./node_modules/neo4j-driver-core/lib/session.js:526:21)
    at write (webpack-internal:///(rsc)/./node_modules/@auth/neo4j-adapter/index.js:293:42)
    at getSessionAndUser (webpack-internal:///(rsc)/./node_modules/@auth/neo4j-adapter/index.js:203:34)
    at acc.<computed> (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/init.js:170:30)
    at Module.session (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/actions/session.js:84:36)
    at AuthInternal (webpack-internal:///(rsc)/./node_modules/@auth/core/lib/index.js:50:77)
    at async Auth (webpack-internal:///(rsc)/./node_modules/@auth/core/index.js:123:29)
    at async UserButton (webpack-internal:///(rsc)/./components/user-button.tsx:19:21)
[auth][details]: {}

As you can see, const session = await auth() in UserButton is causing the error. This is because const session = await auth() is also called within app/server-example/page.tsx as well as app/client-example/page.tsx. Comment out either of those calls in the pages, so that there's just one const session = await auth() occurring, and the error disappears. Only problem is, I'd like to be able to call const session = await auth() in both UserButton.tsx as well as within my pages. As the error message states, there's an issue with the session lifecycle within the Neo4jAdapter. As the neo4j docs state, "Session creation is a lightweight operation, so sessions can be created and destroyed without significant cost. Always close sessions when you are done with them.".

I suspected the session wasn't being closed and then recreated since the adapter is passed in as Neo4jAdapter(session) instead of as Neo4jAdapter(driver). Looking into node_modules/@auth/neo4j-adapter/index.js, I could see this is the case:

export function Neo4jAdapter(session: Session): Adapter {
  const { read, write } = client(session)
  ...

Above, the session is passed to the client function, which manages the transactions. However, the session never gets closed. Editing the .js code locally, I was able to resolve the error by:

  1. Making the driver the argument to Neo4jAdapter (i.e., export function Neo4jAdapter(session) becomes export function Neo4jAdapter(driver))
  2. Updating auth.ts to pass the driver to Neo4jAdapter:
export const config = {
  theme: {
    logo: "https://next-auth.js.org/img/logo/logo-sm.png",
  },
  adapter: Neo4jAdapter(driver)  // <--- here
  1. Updating the client function to accept the driver instead of a session as its argument. That way we can create new lightweight sessions with each read/write transaction. I also ensured that the read and write functions close the session:
function client(driver) {
    return {
        /** Reads values from the database */
        async read(statement, values) {
            const session = driver.session()
            try {
                const result = await session.executeRead((tx) => tx.run(statement, values));
                return format.from(result?.records[0]?.get(0)) ?? null;
            } finally {
                session.close();
            }
        },
        /**
         * Reads/writes values from/to the database.
         * Properties are available under `$data`
         */
        async write(statement, values) {
            const session = driver.session()
            try {
                const result = await session.executeWrite((tx) => tx.run(statement, { data: format.to(values) }));
                return format.from(result?.records[0]?.toObject());
            } finally {
                session.close();
            }
        },
    };
}

Note that I also replaced writeTransaction and readTransaction with executeWrite and executeRead, respectively, since the former methods are marked for deprecation. The difference is immaterial to this bug.

  1. Scope down instantiation the read/write functions within Neo4jAdapter, e.g.,
export function Neo4jAdapter(driver) {
    return {
        async createUser(data) {
            const { write } = client(driver)  // scope client (and session) instantiation to each adapter method
            const user = { ...data, id: crypto.randomUUID() };
            await write(`CREATE (u:User $data)`, user);
            return user;
        },
        async getUser(id) {
            const { read } = client(driver) // scope client (and session) instantiation to each adapter method
            return await read(`MATCH (u:User { id: $id }) RETURN u{.*}`, {
                id,
            });
        },
        ...

After doing the above (don't forget to rm -rf .next when running the dev server to see these effects in action), the error message goes away and the demo app runs swimmingly.

How to reproduce

See the linked repo. It will demonstrate the bug and you can modify the adapter library within node_modules as documented above to resolve it.

Expected behavior

The adapter doesn't throw this error.

@csmcallister csmcallister added adapters Changes related to the core code concerning database adapters bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Feb 6, 2024
csmcallister added a commit to csmcallister/next-auth that referenced this issue Feb 6, 2024
…antiating Neo4jAdapter nextauthjs#9933

By making the neo4j driver as opposed to a single session the argument for Neo4jAdapter, we can ensure each database transaction has its own session and avoid race conditions where a transaction occurs while another, past transaction is still open.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
adapters Changes related to the core code concerning database adapters bug Something isn't working triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant