refactor: dynamically generated login page
This commit is contained in:
parent
ddcb14e564
commit
20eb6ae04a
4 changed files with 75 additions and 17 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { auth } from '@/auth';
|
import { auth, providerMap } from '@/auth';
|
||||||
import SSOLogin from '@/components/user/sso-login-button';
|
import SSOLogin from '@/components/user/sso-login-button';
|
||||||
import LoginForm from '@/components/user/login-form';
|
import LoginForm from '@/components/user/login-form';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
@ -35,11 +35,15 @@ export default async function LoginPage() {
|
||||||
<CardContent className='gap-6 flex flex-col'>
|
<CardContent className='gap-6 flex flex-col'>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|
||||||
<hr />
|
{providerMap.length > 0 && <hr />}
|
||||||
|
|
||||||
{process.env.AUTH_AUTHENTIK_ISSUER && (
|
{providerMap.map((provider) => (
|
||||||
<SSOLogin provider='authentik' providerDisplayName='SSO' />
|
<SSOLogin
|
||||||
)}
|
key={provider.id}
|
||||||
|
provider={provider.id}
|
||||||
|
providerDisplayName={provider.name}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
47
src/auth.ts
47
src/auth.ts
|
@ -1,16 +1,49 @@
|
||||||
import NextAuth from 'next-auth';
|
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';
|
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({
|
export const { handlers, signIn, signOut, auth } = NextAuth({
|
||||||
providers: [process.env.AUTH_AUTHENTIK_ISSUER ? Authentik : null].filter(
|
providers,
|
||||||
(x) => x !== null,
|
session: {
|
||||||
),
|
strategy: 'jwt',
|
||||||
callbacks: {
|
|
||||||
authorized: async ({ auth }) => {
|
|
||||||
return !!auth?.user;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
signIn: '/login',
|
signIn: '/login',
|
||||||
|
signOut: '/logout',
|
||||||
|
},
|
||||||
|
callbacks: {
|
||||||
|
authorized({ auth }) {
|
||||||
|
return !!auth?.user;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,23 +6,24 @@ export default function LabeledInput({
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
|
name,
|
||||||
}: {
|
}: {
|
||||||
type: 'text' | 'email' | 'password';
|
type: 'text' | 'email' | 'password';
|
||||||
label: string;
|
label: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
name?: string;
|
||||||
}) {
|
}) {
|
||||||
const elementId = Math.random().toString(36).substring(2, 15);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
<Label htmlFor={elementId}>{label}</Label>
|
<Label htmlFor={name}>{label}</Label>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
type={type}
|
type={type}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
id={elementId}
|
id={name}
|
||||||
|
name={name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,18 +1,38 @@
|
||||||
|
import { signIn } from '@/auth';
|
||||||
import LabeledInput from '@/components/labeled-input';
|
import LabeledInput from '@/components/labeled-input';
|
||||||
import { Button } from '@/components/ui/button';
|
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() {
|
export default function LoginForm() {
|
||||||
return (
|
return (
|
||||||
<form className='flex flex-col gap-5 w-full'>
|
<form
|
||||||
|
className='flex flex-col gap-5 w-full'
|
||||||
|
action={async (formData) => {
|
||||||
|
'use server';
|
||||||
|
try {
|
||||||
|
await signIn('credentials', formData);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof AuthError) {
|
||||||
|
return redirect(`${SIGNIN_ERROR_URL}?error=${error.type}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='email'
|
type='email'
|
||||||
label='E-Mail'
|
label='E-Mail'
|
||||||
placeholder='Enter your E-Mail'
|
placeholder='Enter your E-Mail'
|
||||||
|
name='email'
|
||||||
/>
|
/>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Password'
|
label='Password'
|
||||||
placeholder='Enter your Password'
|
placeholder='Enter your Password'
|
||||||
|
name='password'
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className='hover:bg-blue-600 hover:text-white'
|
className='hover:bg-blue-600 hover:text-white'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue