From 9c22252e0be318eb0b58c35c54bb5f91fabb0419 Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Mon, 12 May 2025 21:22:56 +0200 Subject: [PATCH] refactor: dynamically generated login page --- src/app/login/page.tsx | 14 +++++---- src/auth.ts | 47 +++++++++++++++++++++++++----- src/components/labeled-input.tsx | 9 +++--- src/components/user/login-form.tsx | 22 +++++++++++++- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 714e996..1786e82 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,4 +1,4 @@ -import { auth } from '@/auth'; +import { auth, providerMap } from '@/auth'; import SSOLogin from '@/components/user/sso-login-button'; import LoginForm from '@/components/user/login-form'; import { redirect } from 'next/navigation'; @@ -35,11 +35,15 @@ export default async function LoginPage() { -
+ {providerMap.length > 0 &&
} - {process.env.AUTH_AUTHENTIK_ISSUER && ( - - )} + {providerMap.map((provider) => ( + + ))}
diff --git a/src/auth.ts b/src/auth.ts index b3ed869..09a5065 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,16 +1,49 @@ import NextAuth from 'next-auth'; + +import type { Provider } from 'next-auth/providers'; +import Credentials from 'next-auth/providers/credentials'; + import Authentik from 'next-auth/providers/authentik'; +const providers: Provider[] = [ + !process.env.DISABLE_PASSWORD_LOGIN && + Credentials({ + credentials: { password: { label: 'Password', type: 'password' } }, + authorize(c) { + if (c.password !== 'password') return null; + return { + id: 'test', + name: 'Test User', + email: 'test@example.com', + }; + }, + }), + process.env.AUTH_AUTHENTIK_ID && Authentik, +].filter(Boolean) as Provider[]; + +export const providerMap = providers + .map((provider) => { + if (typeof provider === 'function') { + const providerData = provider(); + return { id: providerData.id, name: providerData.name }; + } else { + return { id: provider.id, name: provider.name }; + } + }) + .filter((provider) => provider.id !== 'credentials'); + export const { handlers, signIn, signOut, auth } = NextAuth({ - providers: [process.env.AUTH_AUTHENTIK_ISSUER ? Authentik : null].filter( - (x) => x !== null, - ), - callbacks: { - authorized: async ({ auth }) => { - return !!auth?.user; - }, + providers, + session: { + strategy: 'jwt', }, pages: { signIn: '/login', + signOut: '/logout', + }, + callbacks: { + authorized({ auth }) { + return !!auth?.user; + }, }, }); diff --git a/src/components/labeled-input.tsx b/src/components/labeled-input.tsx index 7b4768a..7a416d4 100644 --- a/src/components/labeled-input.tsx +++ b/src/components/labeled-input.tsx @@ -6,23 +6,24 @@ export default function LabeledInput({ label, placeholder, value, + name, }: { type: 'text' | 'email' | 'password'; label: string; placeholder?: string; value?: string; + name?: string; }) { - const elementId = Math.random().toString(36).substring(2, 15); - return (
- +
); diff --git a/src/components/user/login-form.tsx b/src/components/user/login-form.tsx index 20438e8..2869930 100644 --- a/src/components/user/login-form.tsx +++ b/src/components/user/login-form.tsx @@ -1,18 +1,38 @@ +import { signIn } from '@/auth'; import LabeledInput from '@/components/labeled-input'; import { Button } from '@/components/ui/button'; +import { AuthError } from 'next-auth'; +import { redirect } from 'next/navigation'; + +const SIGNIN_ERROR_URL = '/error'; export default function LoginForm() { return ( -
+ { + 'use server'; + try { + await signIn('credentials', formData); + } catch (error) { + if (error instanceof AuthError) { + return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`); + } + throw error; + } + }} + >