fix(auth): bind current local registry and CORS
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
import { argon2, timingSafeEqual } from "node:crypto";
|
||||
|
||||
const MAXIMUM_PHC_BYTES = 256;
|
||||
const MAXIMUM_MEMORY_KIB = 256 * 1024;
|
||||
const MAXIMUM_PASSES = 10;
|
||||
const MAXIMUM_PARALLELISM = 4;
|
||||
const MINIMUM_SALT_BYTES = 16;
|
||||
const MAXIMUM_SALT_BYTES = 64;
|
||||
const MINIMUM_KEY_BYTES = 16;
|
||||
const MAXIMUM_KEY_BYTES = 64;
|
||||
const ARGON2_MEMORY_KIB = 65_536;
|
||||
const ARGON2_PASSES = 3;
|
||||
const ARGON2_PARALLELISM = 1;
|
||||
const ARGON2_SALT_BYTES = 16;
|
||||
const ARGON2_KEY_BYTES = 32;
|
||||
const MINIMUM_PASSWORD_BYTES = 12;
|
||||
const MAXIMUM_PASSWORD_BYTES = 1024;
|
||||
|
||||
@@ -19,6 +17,13 @@ interface Argon2Parameters {
|
||||
digest: Buffer;
|
||||
}
|
||||
|
||||
/** Internal-only signal for unavailable native Argon2 work; never expose its cause. */
|
||||
export class LocalPasswordVerificationError extends Error {
|
||||
constructor() {
|
||||
super("local_password_verification_failed");
|
||||
}
|
||||
}
|
||||
|
||||
function parseDecimal(value: string, maximum: number): number | undefined {
|
||||
if (!/^\d+$/.test(value) || (value.length > 1 && value[0] === "0") || value.length > 10) return undefined;
|
||||
const parsed = Number(value);
|
||||
@@ -46,13 +51,13 @@ function parsePHC(encoded: string): Argon2Parameters | undefined {
|
||||
|
||||
const parameterParts = parts[3].split(",");
|
||||
if (parameterParts.length !== 3 || !parameterParts[0].startsWith("m=") || !parameterParts[1].startsWith("t=") || !parameterParts[2].startsWith("p=")) return undefined;
|
||||
const memory = parseDecimal(parameterParts[0].slice(2), MAXIMUM_MEMORY_KIB);
|
||||
const passes = parseDecimal(parameterParts[1].slice(2), MAXIMUM_PASSES);
|
||||
const parallelism = parseDecimal(parameterParts[2].slice(2), MAXIMUM_PARALLELISM);
|
||||
if (memory === undefined || memory < 8 || passes === undefined || passes === 0 || parallelism === undefined || parallelism === 0 || memory < 8 * parallelism) return undefined;
|
||||
const memory = parseDecimal(parameterParts[0].slice(2), ARGON2_MEMORY_KIB);
|
||||
const passes = parseDecimal(parameterParts[1].slice(2), ARGON2_PASSES);
|
||||
const parallelism = parseDecimal(parameterParts[2].slice(2), ARGON2_PARALLELISM);
|
||||
if (memory !== ARGON2_MEMORY_KIB || passes !== ARGON2_PASSES || parallelism !== ARGON2_PARALLELISM) return undefined;
|
||||
|
||||
const salt = decodeRawBase64(parts[4], MINIMUM_SALT_BYTES, MAXIMUM_SALT_BYTES);
|
||||
const digest = decodeRawBase64(parts[5], MINIMUM_KEY_BYTES, MAXIMUM_KEY_BYTES);
|
||||
const salt = decodeRawBase64(parts[4], ARGON2_SALT_BYTES, ARGON2_SALT_BYTES);
|
||||
const digest = decodeRawBase64(parts[5], ARGON2_KEY_BYTES, ARGON2_KEY_BYTES);
|
||||
if (!salt || !digest) {
|
||||
salt?.fill(0);
|
||||
digest?.fill(0);
|
||||
@@ -92,6 +97,7 @@ function deriveArgon2(message: Buffer, parameters: Argon2Parameters): Promise<Bu
|
||||
}
|
||||
settled = true;
|
||||
if (error || !derived) {
|
||||
derived?.fill(0);
|
||||
reject(error ?? new Error("argon2_failed"));
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +141,7 @@ export async function verifyPassword(password: string, encoded: string): Promise
|
||||
derived = await deriveArgon2(message, parameters);
|
||||
return derived.length === parameters.digest.length && timingSafeEqual(derived, parameters.digest);
|
||||
} catch {
|
||||
return false;
|
||||
throw new LocalPasswordVerificationError();
|
||||
} finally {
|
||||
message.fill(0);
|
||||
derived?.fill(0);
|
||||
|
||||
Reference in New Issue
Block a user