diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index a494c42cb..8523eb043 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -2,7 +2,7 @@ import type { PropsWithChildren } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; -import { useRouter } from "next/navigation"; +import { useRouter, useSearchParams } from "next/navigation"; import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { z } from "zod"; @@ -15,6 +15,8 @@ import { showErrorNotification, showSuccessNotification } from "@homarr/notifica import { useScopedI18n } from "@homarr/translation/client"; import { userSignInSchema } from "@homarr/validation/user"; +type Provider = "credentials" | "ldap" | "oidc"; + interface LoginFormProps { providers: string[]; oidcClientName: string; @@ -26,6 +28,8 @@ const extendedValidation = userSignInSchema.extend({ provider: z.enum(["credenti export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, callbackUrl }: LoginFormProps) => { const t = useScopedI18n("user"); + const searchParams = useSearchParams(); + const isError = searchParams.has("error"); const router = useRouter(); const [isPending, setIsPending] = useState(false); const form = useZodForm(extendedValidation, { @@ -39,22 +43,34 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c const credentialInputsVisible = providers.includes("credentials") || providers.includes("ldap"); const onSuccess = useCallback( - async (response: Awaited>) => { - if (response && (!response.ok || response.error)) { + async (provider: Provider, response: Awaited>) => { + if (!response.ok || response.error) { // eslint-disable-next-line @typescript-eslint/only-throw-error throw response.error; } + if (provider === "oidc") { + if (!response.url) { + showErrorNotification({ + title: t("action.login.notification.error.title"), + message: t("action.login.notification.error.message"), + autoClose: 10000, + }); + return; + } + + router.push(response.url); + return; + } + showSuccessNotification({ title: t("action.login.notification.success.title"), message: t("action.login.notification.success.message"), }); // Redirect to the callback URL if the response is defined and comes from a credentials provider (ldap or credentials). oidc is redirected automatically. - if (response) { - await revalidatePathActionAsync("/"); - router.push(callbackUrl); - } + await revalidatePathActionAsync("/"); + router.push(callbackUrl); }, [t, router, callbackUrl], ); @@ -70,14 +86,14 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c }, [t]); const signInAsync = useCallback( - async (provider: string, options?: Parameters[1]) => { + async (provider: Provider, options?: Parameters[1]) => { setIsPending(true); await signIn(provider, { ...options, redirect: false, callbackUrl: new URL(callbackUrl, window.location.href).href, }) - .then(onSuccess) + .then((response) => onSuccess(provider, response)) .catch(onError); }, [setIsPending, onSuccess, onError, callbackUrl], @@ -86,11 +102,12 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c const isLoginInProgress = useRef(false); useEffect(() => { + if (isError) return; if (isOidcAutoLoginEnabled && !isPending && !isLoginInProgress.current) { isLoginInProgress.current = true; void signInAsync("oidc"); } - }, [signInAsync, isOidcAutoLoginEnabled, isPending]); + }, [signInAsync, isOidcAutoLoginEnabled, isPending, isError]); return ( diff --git a/packages/auth/package.json b/packages/auth/package.json index 8011d7251..b500fb869 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -36,7 +36,7 @@ "cookies": "^0.9.1", "ldapts": "7.4.0", "next": "15.3.1", - "next-auth": "5.0.0-beta.25", + "next-auth": "5.0.0-beta.26", "pretty-print-error": "^1.1.2", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1a3a8f233..58085e01c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -676,8 +676,8 @@ importers: specifier: 15.3.1 version: 15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3) next-auth: - specifier: 5.0.0-beta.25 - version: 5.0.0-beta.25(next@15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3))(react@19.1.0) + specifier: 5.0.0-beta.26 + version: 5.0.0-beta.26(next@15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3))(react@19.1.0) pretty-print-error: specifier: ^1.1.2 version: 1.1.2(patch_hash=d1432e02330bdaf8359eb0e54528a74ed6b7e5cce6bb65c13310c82e34fd1e4d) @@ -2264,20 +2264,6 @@ packages: '@asamuzakjp/css-color@2.8.2': resolution: {integrity: sha512-RtWv9jFN2/bLExuZgFFZ0I3pWWeezAHGgrmjqGGWclATl1aDe3yhCUaI0Ilkp6OCk9zX7+FjvDasEX8Q9Rxc5w==} - '@auth/core@0.37.2': - resolution: {integrity: sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - '@auth/core@0.39.0': resolution: {integrity: sha512-jusviw/sUSfAh6S/wjY5tRmJOq0Itd3ImF+c/b4HB9DfmfChtcfVJTNJeqCeExeCG8oh4PBKRsMQJsn2W6NhFQ==} peerDependencies: @@ -4750,9 +4736,6 @@ packages: '@types/cookie@0.4.1': resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - '@types/cookies@0.9.0': resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==} @@ -5814,10 +5797,6 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -7545,9 +7524,6 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jose@5.9.3: - resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==} - jose@6.0.8: resolution: {integrity: sha512-EyUPtOKyTYq+iMOszO42eobQllaIjJnwkZ2U93aJzNyPibCy7CEvT9UQnaCVB51IAd49gbNdCew1c0LcLTCB2g==} @@ -8051,8 +8027,8 @@ packages: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} - next-auth@5.0.0-beta.25: - resolution: {integrity: sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==} + next-auth@5.0.0-beta.26: + resolution: {integrity: sha512-yAQLIP2x6FAM+GX6FTlQjoPph6msO/9HI3pjI1z1yws3VnvS77atetcxQOmCpxSLTO4jzvpQqPaBZMgRxDgsYg==} peerDependencies: '@simplewebauthn/browser': ^9.0.1 '@simplewebauthn/server': ^9.0.2 @@ -8297,9 +8273,6 @@ packages: nwsapi@2.2.16: resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} - oauth4webapi@3.0.0: - resolution: {integrity: sha512-Rw9SxQYuQX9J41VgM4rVNGtm1ng0Qcd8ndv7JmhmwqQ3hHBokX+WjV379IJhKk7bVPHefgvrDgHoO/rB2dY7YA==} - oauth4webapi@3.3.0: resolution: {integrity: sha512-ZlozhPlFfobzh3hB72gnBFLjXpugl/dljz1fJSRdqaV2r3D5dmi5lg2QWI0LmUYuazmE+b5exsloEv6toUtw9g==} @@ -8657,19 +8630,11 @@ packages: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} - preact-render-to-string@5.2.3: - resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} - peerDependencies: - preact: '>=10' - preact-render-to-string@6.5.11: resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: preact: '>=10' - preact@10.11.3: - resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} - preact@10.24.3: resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} @@ -8695,9 +8660,6 @@ packages: engines: {node: '>=14'} hasBin: true - pretty-format@3.8.0: - resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} - pretty-ms@8.0.0: resolution: {integrity: sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==} engines: {node: '>=14.16'} @@ -10662,16 +10624,6 @@ snapshots: '@csstools/css-tokenizer': 3.0.3 lru-cache: 11.0.2 - '@auth/core@0.37.2': - dependencies: - '@panva/hkdf': 1.2.1 - '@types/cookie': 0.6.0 - cookie: 0.7.1 - jose: 5.9.3 - oauth4webapi: 3.0.0 - preact: 10.11.3 - preact-render-to-string: 5.2.3(preact@10.11.3) - '@auth/core@0.39.0': dependencies: '@panva/hkdf': 1.2.1 @@ -13254,8 +13206,6 @@ snapshots: '@types/cookie@0.4.1': {} - '@types/cookie@0.6.0': {} - '@types/cookies@0.9.0': dependencies: '@types/connect': 3.4.38 @@ -14499,8 +14449,6 @@ snapshots: cookie-es@1.2.2: {} - cookie@0.7.1: {} - cookie@0.7.2: {} cookie@1.0.2: {} @@ -16500,8 +16448,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jose@5.9.3: {} - jose@6.0.8: {} jotai@2.12.3(@types/react@19.1.2)(react@19.1.0): @@ -16967,9 +16913,9 @@ snapshots: netmask@2.0.2: {} - next-auth@5.0.0-beta.25(next@15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3))(react@19.1.0): + next-auth@5.0.0-beta.26(next@15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3))(react@19.1.0): dependencies: - '@auth/core': 0.37.2 + '@auth/core': 0.39.0 next: 15.3.1(@babel/core@7.26.10)(@playwright/test@1.49.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.3) react: 19.1.0 @@ -17168,8 +17114,6 @@ snapshots: nwsapi@2.2.16: {} - oauth4webapi@3.0.0: {} - oauth4webapi@3.3.0: {} object-assign@4.1.1: {} @@ -17557,17 +17501,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact-render-to-string@5.2.3(preact@10.11.3): - dependencies: - preact: 10.11.3 - pretty-format: 3.8.0 - preact-render-to-string@6.5.11(preact@10.24.3): dependencies: preact: 10.24.3 - preact@10.11.3: {} - preact@10.24.3: {} prebuild-install@7.1.2: @@ -17596,8 +17533,6 @@ snapshots: prettier@3.5.3: {} - pretty-format@3.8.0: {} - pretty-ms@8.0.0: dependencies: parse-ms: 3.0.0