diff --git a/.auri/$d2qojx1e.md b/.auri/$d2qojx1e.md new file mode 100644 index 000000000..91dd5ee21 --- /dev/null +++ b/.auri/$d2qojx1e.md @@ -0,0 +1,6 @@ +--- +package: "lucia" # package name +type: "patch" # "major", "minor", "patch" +--- + +Update dependencies \ No newline at end of file diff --git a/packages/lucia/package.json b/packages/lucia/package.json index 4ab619483..0874d6bdc 100644 --- a/packages/lucia/package.json +++ b/packages/lucia/package.json @@ -53,5 +53,9 @@ "@types/node": "^18.6.2", "prettier": "^2.3.0", "vitest": "^0.33.0" + }, + "dependencies": { + "@noble/hashes": "1.3.2", + "nanoid": "5.0.1" } } diff --git a/packages/lucia/src/auth/index.ts b/packages/lucia/src/auth/index.ts index df624b579..e951528c9 100644 --- a/packages/lucia/src/auth/index.ts +++ b/packages/lucia/src/auth/index.ts @@ -1,7 +1,7 @@ import { DEFAULT_SESSION_COOKIE_NAME, createSessionCookie } from "./cookie.js"; import { logError } from "../utils/log.js"; import { generateScryptHash, validateScryptHash } from "../utils/crypto.js"; -import { generateRandomString } from "../utils/nanoid.js"; +import { generateRandomString } from "../utils/crypto.js"; import { LuciaError } from "./error.js"; import { parseCookie } from "../utils/cookie.js"; import { isValidDatabaseSession } from "./session.js"; diff --git a/packages/lucia/src/scrypt/MODULE_LICENSE b/packages/lucia/src/scrypt/MODULE_LICENSE deleted file mode 100644 index 9297a046d..000000000 --- a/packages/lucia/src/scrypt/MODULE_LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022 Paul Miller (https://paulmillr.com) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the “Software”), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/packages/lucia/src/scrypt/index.test.ts b/packages/lucia/src/scrypt/index.test.ts deleted file mode 100644 index fd6ccbeb2..000000000 --- a/packages/lucia/src/scrypt/index.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { expect, test } from "vitest"; -import scrypt from "./index.js"; -import crypto from "node:crypto"; -import { generateRandomString } from "../utils/nanoid.js"; - -test("scrypt() output matches crypto", async () => { - const password = generateRandomString(16); - const salt = generateRandomString(16); - const scryptHash = await scrypt( - new TextEncoder().encode(password), - new TextEncoder().encode(salt), - { - N: 16384, - r: 16, - p: 1, - dkLen: 64 - } - ); - const cryptoHash = new Uint8Array( - crypto.scryptSync(password, salt, 64, { - N: 16384, - p: 1, - r: 16, - maxmem: 128 * 16384 * 16 * 2 - }).buffer - ); - expect(cryptoHash).toStrictEqual(scryptHash); -}); diff --git a/packages/lucia/src/scrypt/index.ts b/packages/lucia/src/scrypt/index.ts deleted file mode 100644 index df144feef..000000000 --- a/packages/lucia/src/scrypt/index.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { pbkdf2 } from "./pbkdf.js"; - -/** - * Scrypt KDF from RFC 7914. - */ -export default async ( - password: Uint8Array, - salt: Uint8Array, - options: ScryptOptions -): Promise => { - const { N, r, p } = options; - const dkLen = options.dkLen ?? 32; - const maxmem = 1024 ** 3 + 1024; - const blockSize = 128 * r; - const blockSize32 = blockSize / 4; - if ( - N <= 1 || - (N & (N - 1)) !== 0 || - N >= 2 ** (blockSize / 8) || - N > 2 ** 32 - ) { - throw new Error( - "Scrypt: N must be larger than 1, a power of 2, less than 2^(128 * r / 8) and less than 2^32" - ); - } - if (p < 0 || p > ((2 ** 32 - 1) * 32) / blockSize) { - throw new Error( - "Scrypt: p must be a positive integer less than or equal to ((2^32 - 1) * 32) / (128 * r)" - ); - } - if (dkLen < 0 || dkLen > (2 ** 32 - 1) * 32) { - throw new Error( - "Scrypt: dkLen should be positive integer less than or equal to (2^32 - 1) * 32" - ); - } - const memUsed = blockSize * (N + p); - if (memUsed > maxmem) { - throw new Error( - `Scrypt: parameters too large, ${memUsed} (128 * r * (N + p)) > ${maxmem} (maxmem)` - ); - } - const B = await pbkdf2(password, salt, { c: 1, dkLen: blockSize * p }); - const B32 = u32(B); - const V = u32(new Uint8Array(blockSize * N)); - const tmp = u32(new Uint8Array(blockSize)); - for (let pi = 0; pi < p; pi++) { - const Pi = blockSize32 * pi; - for (let i = 0; i < blockSize32; i++) V[i] = B32[Pi + i]; // V[0] = B[i] - for (let i = 0, pos = 0; i < N - 1; i++) { - BlockMix(V, pos, V, (pos += blockSize32), r); // V[i] = BlockMix(V[i-1]); - } - BlockMix(V, (N - 1) * blockSize32, B32, Pi, r); // Process last element - for (let i = 0; i < N; i++) { - // First u32 of the last 64-byte block (u32 is LE) - const j = B32[Pi + blockSize32 - 16] % N; // j = Integrify(X) % iterations - for (let k = 0; k < blockSize32; k++) - tmp[k] = B32[Pi + k] ^ V[j * blockSize32 + k]; // tmp = B ^ V[j] - BlockMix(tmp, 0, B32, Pi, r); // B = BlockMix(B ^ V[j]) - } - } - const res = await pbkdf2(password, B, { c: 1, dkLen }); - B.fill(0); - V.fill(0); - tmp.fill(0); - return res; -}; - -const rotl = (a: number, b: number): number => (a << b) | (a >>> (32 - b)); - -const XorAndSalsa = ( - prev: Uint32Array, - pi: number, - input: Uint32Array, - ii: number, - out: Uint32Array, - oi: number -): void => { - const y00 = prev[pi++] ^ input[ii++], - y01 = prev[pi++] ^ input[ii++]; - const y02 = prev[pi++] ^ input[ii++], - y03 = prev[pi++] ^ input[ii++]; - const y04 = prev[pi++] ^ input[ii++], - y05 = prev[pi++] ^ input[ii++]; - const y06 = prev[pi++] ^ input[ii++], - y07 = prev[pi++] ^ input[ii++]; - const y08 = prev[pi++] ^ input[ii++], - y09 = prev[pi++] ^ input[ii++]; - const y10 = prev[pi++] ^ input[ii++], - y11 = prev[pi++] ^ input[ii++]; - const y12 = prev[pi++] ^ input[ii++], - y13 = prev[pi++] ^ input[ii++]; - const y14 = prev[pi++] ^ input[ii++], - y15 = prev[pi++] ^ input[ii++]; - let x00 = y00, - x01 = y01, - x02 = y02, - x03 = y03, - x04 = y04, - x05 = y05, - x06 = y06, - x07 = y07, - x08 = y08, - x09 = y09, - x10 = y10, - x11 = y11, - x12 = y12, - x13 = y13, - x14 = y14, - x15 = y15; - for (let i = 0; i < 8; i += 2) { - x04 ^= rotl((x00 + x12) | 0, 7); - x08 ^= rotl((x04 + x00) | 0, 9); - x12 ^= rotl((x08 + x04) | 0, 13); - x00 ^= rotl((x12 + x08) | 0, 18); - x09 ^= rotl((x05 + x01) | 0, 7); - x13 ^= rotl((x09 + x05) | 0, 9); - x01 ^= rotl((x13 + x09) | 0, 13); - x05 ^= rotl((x01 + x13) | 0, 18); - x14 ^= rotl((x10 + x06) | 0, 7); - x02 ^= rotl((x14 + x10) | 0, 9); - x06 ^= rotl((x02 + x14) | 0, 13); - x10 ^= rotl((x06 + x02) | 0, 18); - x03 ^= rotl((x15 + x11) | 0, 7); - x07 ^= rotl((x03 + x15) | 0, 9); - x11 ^= rotl((x07 + x03) | 0, 13); - x15 ^= rotl((x11 + x07) | 0, 18); - x01 ^= rotl((x00 + x03) | 0, 7); - x02 ^= rotl((x01 + x00) | 0, 9); - x03 ^= rotl((x02 + x01) | 0, 13); - x00 ^= rotl((x03 + x02) | 0, 18); - x06 ^= rotl((x05 + x04) | 0, 7); - x07 ^= rotl((x06 + x05) | 0, 9); - x04 ^= rotl((x07 + x06) | 0, 13); - x05 ^= rotl((x04 + x07) | 0, 18); - x11 ^= rotl((x10 + x09) | 0, 7); - x08 ^= rotl((x11 + x10) | 0, 9); - x09 ^= rotl((x08 + x11) | 0, 13); - x10 ^= rotl((x09 + x08) | 0, 18); - x12 ^= rotl((x15 + x14) | 0, 7); - x13 ^= rotl((x12 + x15) | 0, 9); - x14 ^= rotl((x13 + x12) | 0, 13); - x15 ^= rotl((x14 + x13) | 0, 18); - } - out[oi++] = (y00 + x00) | 0; - out[oi++] = (y01 + x01) | 0; - out[oi++] = (y02 + x02) | 0; - out[oi++] = (y03 + x03) | 0; - out[oi++] = (y04 + x04) | 0; - out[oi++] = (y05 + x05) | 0; - out[oi++] = (y06 + x06) | 0; - out[oi++] = (y07 + x07) | 0; - out[oi++] = (y08 + x08) | 0; - out[oi++] = (y09 + x09) | 0; - out[oi++] = (y10 + x10) | 0; - out[oi++] = (y11 + x11) | 0; - out[oi++] = (y12 + x12) | 0; - out[oi++] = (y13 + x13) | 0; - out[oi++] = (y14 + x14) | 0; - out[oi++] = (y15 + x15) | 0; -}; - -const BlockMix = ( - input: Uint32Array, - ii: number, - out: Uint32Array, - oi: number, - r: number -): void => { - let head = oi + 0; - let tail = oi + 16 * r; - for (let i = 0; i < 16; i++) out[tail + i] = input[ii + (2 * r - 1) * 16 + i]; - for (let i = 0; i < r; i++, head += 16, ii += 16) { - XorAndSalsa(out, tail, input, ii, out, head); - if (i > 0) tail += 16; - XorAndSalsa(out, head, input, (ii += 16), out, tail); - } -}; - -const u32 = (arr: Uint8Array): Uint32Array => { - return new Uint32Array( - arr.buffer, - arr.byteOffset, - Math.floor(arr.byteLength / 4) - ); -}; - -type ScryptOptions = { - N: number; - r: number; - p: number; - dkLen?: number; - maxmem?: number; -}; diff --git a/packages/lucia/src/scrypt/pbkdf.ts b/packages/lucia/src/scrypt/pbkdf.ts deleted file mode 100644 index 7aac91652..000000000 --- a/packages/lucia/src/scrypt/pbkdf.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const pbkdf2 = async ( - password: Uint8Array, - salt: Uint8Array, - options: { - c: number; - dkLen: number; - } -): Promise => { - const pwKey = await crypto.subtle.importKey( - "raw", - password, - "PBKDF2", - false, - ["deriveBits"] - ); - const keyBuffer = await crypto.subtle.deriveBits( - { - name: "PBKDF2", - hash: "SHA-256", - salt, - iterations: options.c - }, - pwKey, - options.dkLen * 8 - ); - return new Uint8Array(keyBuffer); -}; diff --git a/packages/lucia/src/utils/crypto.test.ts b/packages/lucia/src/utils/crypto.test.ts index 6441bfe91..c8350d392 100644 --- a/packages/lucia/src/utils/crypto.test.ts +++ b/packages/lucia/src/utils/crypto.test.ts @@ -2,9 +2,9 @@ import { test, expect } from "vitest"; import { convertUint8ArrayToHex, generateScryptHash, - validateScryptHash + validateScryptHash, + generateRandomString } from "./crypto.js"; -import { generateRandomString } from "./nanoid.js"; test("convertUint8ArrayToHex() output matches Buffer.toString()", async () => { const testUint8Array = crypto.getRandomValues(new Uint8Array(16)); diff --git a/packages/lucia/src/utils/crypto.ts b/packages/lucia/src/utils/crypto.ts index 415ad74f7..fac60cc7f 100644 --- a/packages/lucia/src/utils/crypto.ts +++ b/packages/lucia/src/utils/crypto.ts @@ -1,6 +1,12 @@ import { LuciaError } from "../auth/error.js"; -import scrypt from "../scrypt/index.js"; -import { generateRandomString } from "./nanoid.js"; +import { scryptAsync as scrypt } from "@noble/hashes/scrypt"; +import { customAlphabet } from "nanoid"; + +export const generateRandomString = (length: number, alphabet?: string) => { + const DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyz1234567890"; + const customNanoid = customAlphabet(alphabet ?? DEFAULT_ALPHABET); + return customNanoid(length); +}; export const generateScryptHash = async (s: string): Promise => { const salt = generateRandomString(16); diff --git a/packages/lucia/src/utils/index.ts b/packages/lucia/src/utils/index.ts index 248032e8b..bad1f8515 100644 --- a/packages/lucia/src/utils/index.ts +++ b/packages/lucia/src/utils/index.ts @@ -1,8 +1,8 @@ -export { generateRandomString } from "./nanoid.js"; export { serializeCookie, parseCookie } from "./cookie.js"; export { isWithinExpiration } from "./date.js"; export { generateScryptHash as generateLuciaPasswordHash, - validateScryptHash as validateLuciaPasswordHash + validateScryptHash as validateLuciaPasswordHash, + generateRandomString } from "./crypto.js"; export { joinAdapters as __experimental_joinAdapters } from "./adapter.js"; diff --git a/packages/lucia/src/utils/nanoid.ts b/packages/lucia/src/utils/nanoid.ts deleted file mode 100644 index 7c5577a65..000000000 --- a/packages/lucia/src/utils/nanoid.ts +++ /dev/null @@ -1,31 +0,0 @@ -// code copied from Nanoid: -// https://github.com/ai/nanoid/blob/9b748729f8ad5409503b508b65958636e55bd87a/index.browser.js -// nanoid uses Node dependencies on default bundler settings - -const getRandomValues = (bytes: number): Uint8Array => { - return crypto.getRandomValues(new Uint8Array(bytes)); -}; - -const DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyz1234567890"; - -export const generateRandomString = ( - size: number, - alphabet = DEFAULT_ALPHABET -): string => { - const mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1; - const step = -~((1.6 * mask * size) / alphabet.length); - - let bytes = getRandomValues(step); - let id = ""; - let index = 0; - - while (id.length !== size) { - id += alphabet[bytes[index] & mask] ?? ""; - index += 1; - if (index > bytes.length) { - bytes = getRandomValues(step); - index = 0; - } - } - return id; -};