Use hash comparison for pw

This commit is contained in:
shamoon
2026-07-13 10:18:56 -07:00
parent 4f49c9c768
commit 32e7036b8a
2 changed files with 30 additions and 5 deletions
@@ -140,6 +140,28 @@ describe("pages/api/auth/[...nextauth]", () => {
expect(provider.type).toBe("credentials"); expect(provider.type).toBe("credentials");
expect(typeof provider.authorize).toBe("function"); expect(typeof provider.authorize).toBe("function");
expect(mod.default.options.useSecureCookies).toBe(true); expect(mod.default.options.useSecureCookies).toBe(true);
await expect(provider.options.authorize({ password: "secret" })).resolves.toEqual({
id: "homepage",
name: "Homepage",
});
await expect(provider.options.authorize({ password: "wrong" })).resolves.toBeNull();
await expect(provider.options.authorize({ password: 123 })).resolves.toBeNull();
});
it("compares multibyte passwords without throwing on unequal byte lengths", async () => {
process.env.HOMEPAGE_AUTH_ENABLED = "true";
process.env.HOMEPAGE_AUTH_PASSWORD = "é";
process.env.HOMEPAGE_AUTH_SECRET = "auth-secret";
process.env.HOMEPAGE_EXTERNAL_URL = "https://homepage.example";
const mod = await import("pages/api/auth/[...nextauth]");
const [provider] = mod.default.options.providers;
await expect(provider.options.authorize({ password: "a" })).resolves.toBeNull();
await expect(provider.options.authorize({ password: "é" })).resolves.toEqual({
id: "homepage",
name: "Homepage",
});
}); });
it("supports trusted HTTP deployments without Secure cookies", async () => { it("supports trusted HTTP deployments without Secure cookies", async () => {
+8 -5
View File
@@ -1,4 +1,4 @@
import { timingSafeEqual } from "node:crypto"; import { createHash, timingSafeEqual } from "node:crypto";
import NextAuth from "next-auth"; import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials"; import CredentialsProvider from "next-auth/providers/credentials";
@@ -12,6 +12,9 @@ const clientSecret = process.env.HOMEPAGE_OIDC_CLIENT_SECRET;
const homepageAuthSecret = process.env.HOMEPAGE_AUTH_SECRET; const homepageAuthSecret = process.env.HOMEPAGE_AUTH_SECRET;
const homepageExternalUrl = process.env.HOMEPAGE_EXTERNAL_URL; const homepageExternalUrl = process.env.HOMEPAGE_EXTERNAL_URL;
const homepageAuthPassword = process.env.HOMEPAGE_AUTH_PASSWORD; const homepageAuthPassword = process.env.HOMEPAGE_AUTH_PASSWORD;
const homepageAuthPasswordDigest = homepageAuthPassword
? createHash("sha256").update(homepageAuthPassword, "utf8").digest()
: null;
// Map HOMEPAGE_* envs to what NextAuth expects // Map HOMEPAGE_* envs to what NextAuth expects
if (!process.env.NEXTAUTH_SECRET && homepageAuthSecret) { if (!process.env.NEXTAUTH_SECRET && homepageAuthSecret) {
@@ -97,12 +100,12 @@ if (authEnabled) {
password: { label: "Password", type: "password" }, password: { label: "Password", type: "password" },
}, },
async authorize(credentials) { async authorize(credentials) {
const provided = credentials?.password ?? ""; const provided = credentials?.password;
const expected = homepageAuthPassword ?? ""; if (!homepageAuthPasswordDigest || typeof provided !== "string") {
if (!expected || provided.length !== expected.length) {
return null; return null;
} }
const isMatch = timingSafeEqual(Buffer.from(provided), Buffer.from(expected)); const providedDigest = createHash("sha256").update(provided, "utf8").digest();
const isMatch = timingSafeEqual(providedDigest, homepageAuthPasswordDigest);
if (!isMatch) { if (!isMatch) {
return null; return null;
} }