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

Unable to use Argon2id from "oslo/password" inside server action - Next.js #81

Open
srkuleo opened this issue Jul 6, 2024 · 6 comments

Comments

@srkuleo
Copy link

srkuleo commented Jul 6, 2024

Current version: 14.2.4

I've set up a pretty basic username/password auth. Page renders client side form, which calls upon client action inside which server action is awaited and based on what action return value is different UI is rendered (errors, confirmation toast, loading state, etc.).

Form code:

"use client";

import { useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { signUp, type AuthActionResponse } from "@/util/actions/auth";
import { showToast } from "../user/Toasts";
import { HideIcon, ShowIcon } from "../icons/user/preview";
import { InputFieldError } from "../user/InputFieldError";
import { SubmitFormButton } from "../user/FormButtons";

export const SignUpForm = () => {
  const [actionRes, setActionRes] = useState<AuthActionResponse>({});
  const [showPassword, setShowPassword] = useState(false);
  const [showConfirmPassword, setShowConfirmPassword] = useState(false);

  const formRef = useRef<HTMLFormElement>(null);

  function togglePasswordVisibility(field: "password" | "confirmPassword") {
    if (field === "password") {
      setShowPassword(!showPassword);
    } else {
      setShowConfirmPassword(!showConfirmPassword);
    }
  }

  async function clientSignUp(formData: FormData) {
    const res = await signUp(formData);

    if (res.status === "success-redirect" && res.message) {
      showToast(res.message, res.status, "/workouts", "View workouts");
      formRef.current?.reset();
    }

    setActionRes({ ...res });
  }

  return (
    <form ref={formRef} action={clientSignUp} className="flex flex-col gap-4">
      <input
        id="username"
        name="username"
        type="text"
        placeholder="Username"
        autoComplete="username"
        required
        className={twMerge(
          "input-field",
          "bg-white dark:bg-slate-800/50",
          actionRes.errors?.username && "ring-red-500 dark:ring-red-500",
        )}
      />
      <InputFieldError errorArr={actionRes.errors?.username} className="pl-1" />

      <input
        id="email"
        name="email"
        type="email"
        placeholder="Email"
        autoComplete="email"
        required
        className={twMerge(
          "input-field",
          "bg-white dark:bg-slate-800/50",
          actionRes.errors?.email && "ring-red-500 dark:ring-red-500",
        )}
      />
      <InputFieldError errorArr={actionRes.errors?.email} className="pl-1" />

      <div className="relative">
        <input
          id="password"
          name="password"
          type={showPassword ? "text" : "password"}
          placeholder="Password"
          autoComplete="new-password"
          required
          className={twMerge(
            "input-field",
            "w-full bg-white dark:bg-slate-800/50",
            actionRes.errors?.password && "ring-red-500 dark:ring-red-500",
          )}
        />
        <button
          type="button"
          onClick={() => togglePasswordVisibility("password")}
          className="absolute inset-y-0 right-0 px-3 py-2"
        >
          {showPassword ? (
            <HideIcon className="size-6" />
          ) : (
            <ShowIcon className="size-6" />
          )}
        </button>
      </div>
      <InputFieldError errorArr={actionRes.errors?.password} className="pl-1" />

      <div className="relative">
        <input
          id="confirmPassword"
          name="confirmPassword"
          type={showConfirmPassword ? "text" : "password"}
          placeholder="Confirm password"
          autoComplete="new-password"
          required
          className={twMerge(
            "input-field",
            "w-full bg-white dark:bg-slate-800/50",
            actionRes.errors?.confirmPassword &&
              "ring-red-500 dark:ring-red-500",
          )}
        />
        <button
          type="button"
          onClick={() => togglePasswordVisibility("confirmPassword")}
          className="absolute inset-y-0 right-0 px-3 py-2"
        >
          {showConfirmPassword ? (
            <HideIcon className="size-6" />
          ) : (
            <ShowIcon className="size-6" />
          )}
        </button>
      </div>
      <InputFieldError
        errorArr={actionRes.errors?.confirmPassword}
        className="pl-1"
      />

      {actionRes.status === "error" && (
        <InputFieldError message={actionRes.message} />
      )}

      <SubmitFormButton
        label="Sign Up"
        loading="Creating profile..."
        className="mt-6 rounded-full py-2.5"
      />
    </form>
  );
};

Action code:

export async function signUp(formData: FormData): Promise<AuthActionResponse> {
  const signUpRaw = signUpSchema.safeParse({
    username: formData.get("username"),
    email: formData.get("email"),
    password: formData.get("password"),
    confirmPassword: formData.get("confirmPassword"),
  });

  if (!signUpRaw.success) {
    return {
      status: "error",
      errors: signUpRaw.error.flatten().fieldErrors,
    };
  }

  const { username, email, password } = signUpRaw.data;

  try {
    const duplicateUsername = await db.query.users.findFirst({
      where: ilike(users.username, username.trim()),
      columns: {
        username: true,
      },
    });

    const duplicateEmail = await db.query.users.findFirst({
      where: eq(users.email, email.trim()),
      columns: {
        email: true,
      },
    });

    if (duplicateUsername?.username && duplicateEmail?.email) {
      return {
        status: "error",
        errors: {
          username: ["Username already exists."],
          email: ["This email is already taken."],
        },
      };
    } else if (duplicateUsername?.username) {
      return {
        status: "error",
        errors: {
          username: ["Username already exists."],
        },
      };
    } else if (duplicateEmail?.email) {
      return {
        status: "error",
        errors: {
          email: ["This email is already taken."],
        },
      };
    }
  } catch (error) {
    console.error(error);
    return {
      status: "error",
      message: "Database Error: Please try again.",
    };
  }

  const hashedPassword = await new Argon2id().hash(password);
  const userId = generateIdFromEntropySize(10);

  try {
    await db.insert(users).values({
      id: userId,
      username: username.trim(),
      email: email.trim(),
      hashedPassword: hashedPassword,
    });
  } catch (error) {
    console.error(error);
    return {
      status: "error",
      message: "Database Error: Profile was not created",
    };
  }

  const session = await lucia.createSession(userId, {});
  const sessionCookie = lucia.createSessionCookie(session.id);
  cookies().set(
    sessionCookie.name,
    sessionCookie.value,
    sessionCookie.attributes,
  );

  return {
    status: "success-redirect",
    message: "Profile created successfully",
  };
}

Essentially it throws this error when trying to hash the password via Argon2id class, or any other class provided by oslo/password (Bcrypt or Scrypt).

 ⨯ Error: Failed to load native binding
    at @node-rs/argon2 (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:110:18)
    at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
    at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
    at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
    at eval (./src/util/actions/auth.ts:16:71)
    at (action-browser)/./src/util/actions/auth.ts (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:688:1)
    at Function.__webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
digest: "2107397282"
Cause: [
  Error: Cannot find module './argon2.win32-x64-msvc.node'
  Require stack:
  - E:\react\noteset\node_modules\@node-rs\argon2\index.js
  - E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js
  - E:\react\noteset\node_modules\next\dist\server\require.js
  - E:\react\noteset\node_modules\next\dist\server\load-components.js
  - E:\react\noteset\node_modules\next\dist\build\utils.js
  - E:\react\noteset\node_modules\next\dist\server\dev\hot-middleware.js
  - E:\react\noteset\node_modules\next\dist\server\dev\hot-reloader-webpack.js
  - E:\react\noteset\node_modules\next\dist\server\lib\router-utils\setup-dev-bundler.js
  - E:\react\noteset\node_modules\next\dist\server\lib\router-server.js
  - E:\react\noteset\node_modules\next\dist\server\lib\start-server.js
      at Module._resolveFilename (node:internal/modules/cjs/loader:1145:15)
      at E:\react\noteset\node_modules\next\dist\server\require-hook.js:55:36
      at Module._load (node:internal/modules/cjs/loader:986:27)
      at Module.require (node:internal/modules/cjs/loader:1233:19)
      at mod.require (E:\react\noteset\node_modules\next\dist\server\require-hook.js:65:28)
      at require (node:internal/modules/helpers:179:18)
      at requireNative (E:\react\noteset\node_modules\@node-rs\argon2\index.js:91:16)
      at Object.<anonymous> (E:\react\noteset\node_modules\@node-rs\argon2\index.js:332:17)
      at Module._compile (node:internal/modules/cjs/loader:1358:14)
      at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
      at Module.load (node:internal/modules/cjs/loader:1208:32)
      at Module._load (node:internal/modules/cjs/loader:1024:12)
      at Module.require (node:internal/modules/cjs/loader:1233:19)
      at mod.require (E:\react\noteset\node_modules\next\dist\server\require-hook.js:65:28)
      at require (node:internal/modules/helpers:179:18)
      at @node-rs/argon2 (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:110:18)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./node_modules/oslo/dist/password/argon2id.js:5:73)
      at (action-browser)/./node_modules/oslo/dist/password/argon2id.js (E:\react\noteset\.next\server\vendor-chunks\oslo.js:260:1)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./node_modules/oslo/dist/password/index.js:7:70)
      at (action-browser)/./node_modules/oslo/dist/password/index.js (E:\react\noteset\.next\server\vendor-chunks\oslo.js:300:1)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./src/util/actions/auth.ts:16:71)
      at (action-browser)/./src/util/actions/auth.ts (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:688:1)
      at Function.__webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async endpoint (webpack-internal:///(action-browser)/./node_modules/next/dist/build/webpack/loaders/next-flight-action-entry-loader.js?actions=%5B%5B%22E%3A%5C%5Creact%5C%5Cnoteset%5C%5Csrc%5C%5Cutil%5C%5Cactions%5C%5Cauth.ts%22%2C%5B%22getAuth%22%2C%22logout%22%2C%22signUp%22%2C%22%24%24ACTION_0%22%2C%22login%22%5D%5D%5D&__client_imported__=true!:11:18)
      at async E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:39:418
      at async rw (E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:7978)
      at async r4 (E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:41:1251)
      at async doRender (E:\react\noteset\node_modules\next\dist\server\base-server.js:1438:30)
      at async cacheEntry.responseCache.get.routeKind (E:\react\noteset\node_modules\next\dist\server\base-server.js:1599:28)
      at async DevServer.renderToResponseWithComponentsImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:1507:28)
      at async DevServer.renderPageComponent (E:\react\noteset\node_modules\next\dist\server\base-server.js:1931:24)
      at async DevServer.renderToResponseImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:1969:32)
      at async DevServer.pipeImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:920:25)
      at async NextNodeServer.handleCatchallRenderRequest (E:\react\noteset\node_modules\next\dist\server\next-server.js:272:17)
      at async DevServer.handleRequestImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:816:17)
      at async E:\react\noteset\node_modules\next\dist\server\dev\next-dev-server.js:339:20
      at async Span.traceAsyncFn (E:\react\noteset\node_modules\next\dist\trace\trace.js:154:20)
      at async DevServer.handleRequest (E:\react\noteset\node_modules\next\dist\server\dev\next-dev-server.js:336:24)
      at async invokeRender (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:174:21)
      at async handleRequest (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:353:24)
      at async requestHandlerImpl (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:377:13)
      at async Server.requestListener (E:\react\noteset\node_modules\next\dist\server\lib\start-server.js:141:13) {
    code: 'MODULE_NOT_FOUND',
    requireStack: [
      'E:\\react\\noteset\\node_modules\\@node-rs\\argon2\\index.js',
      'E:\\react\\noteset\\.next\\server\\app\\(landingpage)\\sign-up\\page.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\require.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\load-components.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\build\\utils.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\dev\\hot-middleware.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\dev\\hot-reloader-webpack.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\lib\\router-utils\\setup-dev-bundler.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\lib\\router-server.js',
      'E:\\react\\noteset\\node_modules\\next\\dist\\server\\lib\\start-server.js'
    ]
  },
  Error: A dynamic link library (DLL) initialization routine failed.
  \\?\E:\react\noteset\node_modules\@node-rs\argon2-win32-x64-msvc\argon2.win32-x64-msvc.node
      at Module._extensions..node (node:internal/modules/cjs/loader:1454:18)
      at Module.load (node:internal/modules/cjs/loader:1208:32)
      at Module._load (node:internal/modules/cjs/loader:1024:12)
      at Module.require (node:internal/modules/cjs/loader:1233:19)
      at mod.require (E:\react\noteset\node_modules\next\dist\server\require-hook.js:65:28)
      at require (node:internal/modules/helpers:179:18)
      at requireNative (E:\react\noteset\node_modules\@node-rs\argon2\index.js:96:16)
      at Object.<anonymous> (E:\react\noteset\node_modules\@node-rs\argon2\index.js:332:17)
      at Module._compile (node:internal/modules/cjs/loader:1358:14)
      at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
      at Module.load (node:internal/modules/cjs/loader:1208:32)
      at Module._load (node:internal/modules/cjs/loader:1024:12)
      at Module.require (node:internal/modules/cjs/loader:1233:19)
      at mod.require (E:\react\noteset\node_modules\next\dist\server\require-hook.js:65:28)
      at require (node:internal/modules/helpers:179:18)
      at @node-rs/argon2 (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:110:18)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./node_modules/oslo/dist/password/argon2id.js:5:73)
      at (action-browser)/./node_modules/oslo/dist/password/argon2id.js (E:\react\noteset\.next\server\vendor-chunks\oslo.js:260:1)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./node_modules/oslo/dist/password/index.js:7:70)
      at (action-browser)/./node_modules/oslo/dist/password/index.js (E:\react\noteset\.next\server\vendor-chunks\oslo.js:300:1)
      at __webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at eval (webpack-internal:///(action-browser)/./src/util/actions/auth.ts:16:71)
      at (action-browser)/./src/util/actions/auth.ts (E:\react\noteset\.next\server\app\(landingpage)\sign-up\page.js:688:1)
      at Function.__webpack_require__ (E:\react\noteset\.next\server\webpack-runtime.js:33:42)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async endpoint (webpack-internal:///(action-browser)/./node_modules/next/dist/build/webpack/loaders/next-flight-action-entry-loader.js?actions=%5B%5B%22E%3A%5C%5Creact%5C%5Cnoteset%5C%5Csrc%5C%5Cutil%5C%5Cactions%5C%5Cauth.ts%22%2C%5B%22getAuth%22%2C%22logout%22%2C%22signUp%22%2C%22%24%24ACTION_0%22%2C%22login%22%5D%5D%5D&__client_imported__=true!:11:18)
      at async E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:39:418
      at async rw (E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:38:7978)
      at async r4 (E:\react\noteset\node_modules\next\dist\compiled\next-server\app-page.runtime.dev.js:41:1251)
      at async doRender (E:\react\noteset\node_modules\next\dist\server\base-server.js:1438:30)
      at async cacheEntry.responseCache.get.routeKind (E:\react\noteset\node_modules\next\dist\server\base-server.js:1599:28)
      at async DevServer.renderToResponseWithComponentsImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:1507:28)
      at async DevServer.renderPageComponent (E:\react\noteset\node_modules\next\dist\server\base-server.js:1931:24)
      at async DevServer.renderToResponseImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:1969:32)
      at async DevServer.pipeImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:920:25)
      at async NextNodeServer.handleCatchallRenderRequest (E:\react\noteset\node_modules\next\dist\server\next-server.js:272:17)
      at async DevServer.handleRequestImpl (E:\react\noteset\node_modules\next\dist\server\base-server.js:816:17)
      at async E:\react\noteset\node_modules\next\dist\server\dev\next-dev-server.js:339:20
      at async Span.traceAsyncFn (E:\react\noteset\node_modules\next\dist\trace\trace.js:154:20)
      at async DevServer.handleRequest (E:\react\noteset\node_modules\next\dist\server\dev\next-dev-server.js:336:24)
      at async invokeRender (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:174:21)
      at async handleRequest (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:353:24)
      at async requestHandlerImpl (E:\react\noteset\node_modules\next\dist\server\lib\router-server.js:377:13)
      at async Server.requestListener (E:\react\noteset\node_modules\next\dist\server\lib\start-server.js:141:13) {
    code: 'ERR_DLOPEN_FAILED'
  }
]

I have done few things to try to fix this like adding "@node-rs/argon2" as separate dependency and modifying next.config.js to look like this

const nextConfig = {
  webpack: (config) => {
    config.externals.push("@node-rs/argon2", "@node-rs/bcrypt");
    return config;
  },
};

but none of these solutions helped. Switching to Bcrypt as standalone dependency fixes the issue, but I would like to use Argon and would like to follow recommended Lucia setup.

Looking forward to your response and if you need anything else feel free to ask!

@mooxl
Copy link

mooxl commented Jul 26, 2024

Using "oslo": "^1.2.1" the same issue occurs with a standard Node.js server bundled using tsup to generate ESM.

tsup.config.ts

import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts"],
  clean: true,
  format: "esm",
  noExternal: [
    "@shift-desk/constants",
    "@shift-desk/database",
    "@shift-desk/importer",
    "@shift-desk/types",
  ],
  esbuildOptions: (options) => {
    options.external?.push("@node-rs/argon2", "@node-rs/bcrypt");
    return options;
  },
  tsconfig: "tsconfig.json",
});

output

CLI Building entry: src/index.ts
CLI Using tsconfig: tsconfig.json
CLI tsup v8.2.3
CLI Using tsup config: /path/apps/server/tsup.config.ts
CLI Target: es2022
CLI Cleaning output folder
ESM Build start
ESM dist/index.js 211.87 KB
ESM ⚡️ Build success in 39ms
cache bypass, force executing bdfbbfffe9f3ce47
> @shift-desk/server@0.0.0 preview /path/apps/server
> node ./dist/index.js

file:///path/apps/server/dist/index.js:11
  throw Error('Dynamic require of "' + x + '" is not supported');
        ^

Error: Dynamic require of "assert" is not supported
    at file:///path/apps/server/dist/index.js:11:9
    at ../../node_modules/.pnpm/argon2@0.40.3/node_modules/argon2/argon2.cjs (file:///path/apps/server/dist/index.js:380:18)
    at __require2 (file:///path/apps/server/dist/index.js:14:50)
    at file:///path/apps/server/dist/index.js:5727:22
    at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:316:24)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5)

Node.js v20.15.0
 ELIFECYCLE  Command failed with exit code 1.
ERROR: command finished with error: command (/Users/max.schmidt/progg/shiftdesk/apps/server) /Users/max.schmidt/.nvm/versions/node/v20.15.0/bin/pnpm run preview exited (1)

@isaacfink
Copy link

I am running into a similar issue with both Argon2 and Bcrypt when bundling with rollup (on a sveltekit project) here is the full error

error during build:
[commonjs--resolver] ../../node_modules/.pnpm/@node-rs+bcrypt-darwin-arm64@1.9.0/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node (1:0): Unexpected character '�' (Note that you need plugins to import files that are not JavaScript)
file: /Users/isaac/Desktop/projects/project/node_modules/.pnpm/@node-rs+bcrypt@1.9.0/node_modules/@node-rs/bcrypt/index.js:1:0

1: ����
       ���__TEXT�__text...
   ^
2: �H__LINKEDIT@
�/Users/runner/work/node-rs/node-rs/target/aarch6...

It seems like an issue with the rust solutions, we probably need a pure js solution (even though it's slow) in order to get around bundlers

@BradNut
Copy link

BradNut commented Sep 24, 2024

@isaacfink were you able to find a solution for SvelteKit?

@creadevv
Copy link

creadevv commented Oct 5, 2024

We really need this. Deployments on SST V3 also break. Very annoying!

@creadevv
Copy link

creadevv commented Oct 7, 2024

I am running into a similar issue with both Argon2 and Bcrypt when bundling with rollup (on a sveltekit project) here is the full error

error during build:
[commonjs--resolver] ../../node_modules/.pnpm/@node-rs+bcrypt-darwin-arm64@1.9.0/node_modules/@node-rs/bcrypt-darwin-arm64/bcrypt.darwin-arm64.node (1:0): Unexpected character '�' (Note that you need plugins to import files that are not JavaScript)
file: /Users/isaac/Desktop/projects/project/node_modules/.pnpm/@node-rs+bcrypt@1.9.0/node_modules/@node-rs/bcrypt/index.js:1:0

1: ����
       ���__TEXT�__text...
   ^
2: �H__LINKEDIT@
�/Users/runner/work/node-rs/node-rs/target/aarch6...

It seems like an issue with the rust solutions, we probably need a pure js solution (even though it's slow) in order to get around bundlers

+1

Having problems during my SST V3 deployments. Vercel works, but I don't want to host there. So If there is no solution, dropping Lucia/Oslo is the only option.

@codepunkt
Copy link

Same here.
Luckily scrypt is in node:crypto, but Argon2Id isn't 👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants