Auth: log failed attempts

This commit is contained in:
shamoon
2026-08-07 08:13:57 -07:00
parent 550441b455
commit d8b2a33d5b
3 changed files with 31 additions and 0 deletions
@@ -148,6 +148,28 @@ describe("pages/api/auth/[...nextauth]", () => {
await expect(provider.options.authorize({ password: 123 })).resolves.toBeNull();
});
it("logs failed password sign-in attempts without recording client-supplied data", async () => {
process.env.HOMEPAGE_AUTH_ENABLED = "true";
process.env.HOMEPAGE_AUTH_PASSWORD = "secret";
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 provider.options.authorize({ password: "wrong" });
await provider.options.authorize({ password: 123 });
expect(warnMock).toHaveBeenCalledTimes(2);
expect(warnMock).toHaveBeenCalledWith("Failed password sign-in attempt");
// the attempted password must never reach the logs
expect(JSON.stringify(warnMock.mock.calls)).not.toContain("wrong");
warnMock.mockClear();
await provider.options.authorize({ password: "secret" });
expect(warnMock).not.toHaveBeenCalled();
});
it("compares multibyte passwords without throwing on unequal byte lengths", async () => {
process.env.HOMEPAGE_AUTH_ENABLED = "true";
process.env.HOMEPAGE_AUTH_PASSWORD = "é";
+7
View File
@@ -65,6 +65,11 @@ if (authEnabled) {
}
}
// Give fail2ban / CrowdSec etc something to match on
function logFailedPasswordSignIn() {
createLogger("nextauth").warn("Failed password sign-in attempt");
}
let providers = [];
if (authEnabled) {
if (hasOidcConfig) {
@@ -104,11 +109,13 @@ if (authEnabled) {
async authorize(credentials) {
const provided = credentials?.password;
if (!homepageAuthPasswordDigest || typeof provided !== "string") {
logFailedPasswordSignIn();
return null;
}
const providedDigest = createHash("sha256").update(provided, "utf8").digest();
const isMatch = timingSafeEqual(providedDigest, homepageAuthPasswordDigest);
if (!isMatch) {
logFailedPasswordSignIn();
return null;
}
return {