#Using the Better Auth component in "use node" context

20 messages · Page 1 of 1 (latest)

covert vector
#

Coming from a different database, I'm in the process of migrating over to Convex (cloud, not self-hosted), and using Better Auth Convex component (local install) as auth solution.

Ideally I want to re-use the users's password hash, which are hashed using argon2id, this requires the node runtime instead of the default v8 runtime Convex uses.

When adding "use node" at the top of the /convex/auth.ts where I the betterAuth instance with the password hash override like so:

"use node";

export const createAuth = (ctx: GenericCtx<DataModel>, { optionsOnly } = { optionsOnly: false }) => {
  return betterAuth({
    ...
    emailAndPassword: {
      enabled: true,
      minPasswordLength: 12,
      password: {
        hash: async (password) => await argon2.hash(password),
        verify: async ({ hash, password }) => await argon2.verify(hash, password),
      },
    },
    ...
  });
};

this will causes the following errors (not only from node:crypto also node:util fs, path and os)

✘ [ERROR] Could not resolve "node:crypto"

    node_modules/argon2/argon2.cjs:1:49:
      1 │ const { randomBytes, timingSafeEqual } = require("node:crypto");
        ╵                                                  ~~~~~~~~~~~~~

  The package "node:crypto" wasn't found on the file system but is built into node. Are you trying
  to bundle for node? You can use "platform: 'node'" to do that, which will remove this error.

It looks like you are using Node APIs from a file without the "use node" directive.
Split out actions using Node.js APIs like this into a new file only containing actions that uses "use node" so these actions will run in a Node.js environment.
For more information see https://docs.convex.dev/functions/runtimes#nodejs-runtime

this essentially means I can't override the password hash?

stoic wadiBOT
#

Thanks for posting in #1088161997662724167.
Reminder: If you have a Convex Pro account, use the Convex Dashboard to file support tickets.

    - Provide context: What are you trying to achieve, what is the end-user interaction, what are you seeing? (full error message, command output, etc.)
    - Use [search.convex.dev](https://search.convex.dev) to search Docs, Stack, and Discord all at once.
    - Additionally, you can post your questions in the Convex Community's #1228095053885476985 channel to receive a response from AI.
    - Avoid tagging staff unless specifically instructed.

    Thank you!
covert vector
#

Using the Better Auth component in "use node" context

stable cloak
#

I don't think you can use use node in /convex/auth.ts. That defines auth for your whole application, so it can't be isolated to the node-compatible runtime.

Try creating a separate file with node convex actions for hashing and verifying passwords, then calling them via ctx.runAction in your auth file.

muted lion
covert vector
#

yeah that's what I figured, having "use node" at the top of /convex/auth.ts would basically mean that most Convex functions (doing auth checks) will need to run in node environment. this also isn't how the node runtime works for Convex, in the docs they explain that only actions will run in the node environment, and is not for queries or mutations. This is intended to be extract/separated into its own file with "use node" at the top, so that only those actions are run in the node environment.

Making me come to the conclusion: I can't override the password hashing (at least in its current implementation) for Better Auth as Convex component.
Or is it possible that I can run an action from within the /convex/auth.ts inside the betterAuth({...}) constructor? Hmm I'll try that

it's not that I'm using/importing crypto myself, it's used by the package argon2 that I import into /convex/auth.ts

muted lion
#

Yeah i understand that

#

Yeah scratch that

stable cloak
#

Yes, you can run actions from within the auth.ts file. For example:

betterAuth({
  ...
  emailAndPassword: {
      enabled: true,
      requireEmailVerification: false,
      sendResetPassword: async ({ user, url }) => {
        const actionCtx = ctx as ActionCtx;
        await actionCtx.scheduler.runAfter(
          0,
          internal.email.nodeActions.sendEmail,
          {
            to: user.email,
            type: "RESET_PWD",
            props: {
              resetUrl: url,
              userEmail: user.email,
              userName: user.name,
            },
          },
        );
      },
    },
  ...
});
#

In this case, it's a scheduled action. But ActionCtx will also expose runAction.

covert vector
#

I have created separate node functions for the hash and verify in /convex/password.ts like so:

"use node";

import argon2 from "argon2";
import { v } from "convex/values";
import { internalAction } from "@/convex/_generated/server";

export const hash = internalAction({
  args: { password: v.string() },
  returns: v.string(),
  handler: async (_, { password }) => await argon2.hash(password),
});

export const verify = internalAction({
  args: { hash: v.string(), password: v.string() },
  returns: v.boolean(),
  handler: async (_, { hash, password }) => await argon2.verify(hash, password),
});

but during auto deployment of functions (after saving the file) I get this error:

✖ Error: Unable to start push to https://[REDACTED].convex.cloud
✖ Error fetching POST  https://[REDACTED].convex.cloud/api/deploy2/start_push 400 Bad Request: InvalidModules: Hit an error while pushing:
Loading the pushed modules encountered the following
    error:
Uncaught Failed to analyze password.js: No native build was found for platform=linux arch=arm64 runtime=node abi=127 uv=1 armv=8 libc=glibc node=22.20.0
    loaded from: /var/task

    at v.resolve.v.path [as path] (../node_modules/node-gyp-build/node-gyp-build.js:59:62)
    at load (../node_modules/node-gyp-build/node-gyp-build.js:22:0)
    at <anonymous> (../node_modules/argon2/argon2.cjs:14:25)
    at <anonymous> (convex:/user/password.js:13:31)
    at <anonymous> (../convex/password.ts:2:19)

#

I guess there's a mismatch / misconfiguration for the node version? 🤔

#

could it be related to my convex.json, where I set the version to 22, so it matches the version i have on vercel?

{
  "$schema": "https://raw.githubusercontent.com/get-convex/convex-backend/refs/heads/main/npm-packages/convex/schemas/convex.schema.json",
  "codegen": {
    "fileType": "ts"
  },
  "node": {
    "nodeVersion": "22"
  }
}
#

man, I feel i'm in the depths of Convex 😅

#

brb lunch

stable cloak
covert vector
#

YES! That was the last missing piece! Thanks @stable cloak 🙌

#

Or well, at least the upload of the functions work, and no TS errors exist 😉

#

so for completeness, the resulting code for betterAuth config is as follows (see below), this is using the internalActions from /convex/password.ts (see above), and the convex.json setting "externalPackages": ["argon2"]

import { type GenericCtx } from "@convex-dev/better-auth";
import { requireActionCtx } from "@convex-dev/better-auth/utils";
import { betterAuth } from "better-auth";
import type { DataModel } from "./_generated/dataModel";

export const createAuth = (ctx: GenericCtx<DataModel>, { optionsOnly } = { optionsOnly: false }) => {
  return betterAuth({
    ...
    emailAndPassword: {
      enabled: true,
      minPasswordLength: 12,
      password: {
        hash: async (password) => await requireActionCtx(ctx).runAction(internal.password.hash, { password }),
        verify: async ({ hash, password }) => await requireActionCtx(ctx).runAction(internal.password.verify, { hash, password }),
      },
    },
    ...
  })
);