Files
homarr/packages/auth/configuration.ts
T
Thomas Camlong 27325326da fix(auth): resolve credentials login SQLITE_CONSTRAINT_NOTNULL and relax password rules
Session creation failed with NOT NULL constraint on expires column due to
invalid date propagation from duration parsing. Override adapter.createSession
with validated insert, harden signIn callback, fix createDurationSchema for
numeric-only values, and guard events.ts group insert.

Also relaxes password complexity to suggestions-only (min 8 chars enforced),
extracts shared UserCreatePasswordFields component, and fixes login form
passing extra fields to signIn.
2026-05-21 16:30:07 +02:00

123 lines
3.7 KiB
TypeScript

import type { ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
import { cookies } from "next/headers";
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { createLogger } from "@homarr/core/infrastructure/logs";
import { db } from "@homarr/db";
import type { SupportedAuthProvider } from "@homarr/definitions";
import { createAdapter } from "./adapter";
import { createSessionCallback } from "./callbacks";
import { env } from "./env";
import { createSignInEventHandler } from "./events";
import { createCredentialsConfiguration, createLdapConfiguration } from "./providers/credentials/credentials-provider";
import { EmptyNextAuthProvider } from "./providers/empty/empty-provider";
import { filterProviders } from "./providers/filter-providers";
import { OidcProvider } from "./providers/oidc/oidc-provider";
import { createRedirectUri } from "./redirect";
import { expireDateAfter, generateSessionToken, sessionTokenCookieName } from "./session";
const logger = createLogger({ module: "authConfiguration" });
// See why it's unknown in the [...nextauth]/route.ts file
export const createConfiguration = (
provider: SupportedAuthProvider | "unknown",
headers: ReadonlyHeaders | null,
useSecureCookies: boolean,
) => {
const adapter = createAdapter(db, provider);
return NextAuth({
logger: {
error: (error) => {
// Remove the big error message for failed login attempts
// as it is not useful for the user.
if (error.name === "CredentialsSignin") {
logger.warn("The login attempt of a user was not successful.");
return;
}
logger.error(error);
},
},
trustHost: true,
cookies: {
sessionToken: {
name: sessionTokenCookieName,
},
},
adapter,
providers: filterProviders([
Credentials(createCredentialsConfiguration(db)),
Credentials(createLdapConfiguration(db)),
EmptyNextAuthProvider(),
OidcProvider(headers),
]),
callbacks: {
session: createSessionCallback(db),
// eslint-disable-next-line no-restricted-syntax
signIn: async ({ user }) => {
/**
* For credentials provider only jwt is supported by default
* so we have to create the session and set the cookie manually.
*/
if (provider !== "credentials" && provider !== "ldap") {
return true;
}
if (!adapter.createSession || !user.id) {
return false;
}
const expires = expireDateAfter(env.AUTH_SESSION_EXPIRY_TIME);
const sessionToken = generateSessionToken();
if (Number.isNaN(expires.getTime())) {
logger.error("Invalid session expiry date", { expirySeconds: env.AUTH_SESSION_EXPIRY_TIME });
return false;
}
await adapter.createSession({
sessionToken,
expires,
userId: user.id,
});
(await cookies()).set(sessionTokenCookieName, sessionToken, {
path: "/",
expires: expires,
httpOnly: true,
sameSite: "lax",
secure: useSecureCookies,
});
return true;
},
},
events: {
signIn: createSignInEventHandler(db),
},
redirectProxyUrl: createRedirectUri(headers, "/api/auth"),
session: {
strategy: "database",
maxAge: env.AUTH_SESSION_EXPIRY_TIME,
generateSessionToken,
},
pages: {
signIn: "/auth/login",
error: "/auth/login",
},
jwt: {
// eslint-disable-next-line no-restricted-syntax
async encode() {
const cookie = (await cookies()).get(sessionTokenCookieName)?.value;
return cookie ?? "";
},
decode() {
return null;
},
},
});
};