mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-18 03:01:09 +01:00
🐛 Fix issues with login page
This commit is contained in:
@@ -39,7 +39,7 @@
|
||||
"@mantine/modals": "^6.0.0",
|
||||
"@mantine/next": "^6.0.0",
|
||||
"@mantine/notifications": "^6.0.0",
|
||||
"@next-auth/prisma-adapter": "^1.0.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@nivo/core": "^0.83.0",
|
||||
"@nivo/line": "^0.83.0",
|
||||
"@prisma/client": "^5.0.0",
|
||||
@@ -70,7 +70,7 @@
|
||||
"i18next": "^22.5.1",
|
||||
"js-file-download": "^0.4.12",
|
||||
"next": "13.4.10",
|
||||
"next-auth": "^4.22.3",
|
||||
"next-auth": "^4.21.0",
|
||||
"next-i18next": "^13.0.0",
|
||||
"nzbget-api": "^0.0.3",
|
||||
"prismjs": "^1.29.0",
|
||||
|
||||
BIN
prisma/db.sqlite
BIN
prisma/db.sqlite
Binary file not shown.
@@ -1,27 +1,18 @@
|
||||
{
|
||||
"title": "Welcome back!",
|
||||
"text": "Please enter your password",
|
||||
"text": "Please enter your credentials",
|
||||
"form": {
|
||||
"fields": {
|
||||
"username": {
|
||||
"label": "Username"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "Your password"
|
||||
"label": "Password"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"submit": "Sign in"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"checking": {
|
||||
"title": "Checking your password",
|
||||
"message": "Your password is being checked..."
|
||||
},
|
||||
"correct": {
|
||||
"title": "Sign in successful, redirecting..."
|
||||
},
|
||||
"wrong": {
|
||||
"title": "The password you entered is incorrect, please try again."
|
||||
}
|
||||
}
|
||||
"alert": "Your credentials are incorrect or this account doesn't exist. Please try again."
|
||||
}
|
||||
@@ -1,116 +1,88 @@
|
||||
import { Button, Container, Paper, PasswordInput, Text, Title } from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Flex,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useForm, zodResolver } from '@mantine/form';
|
||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||
import { IconCheck, IconX } from '@tabler/icons-react';
|
||||
import { IconAlertCircle, IconAlertTriangle, IconCheck, IconX } from '@tabler/icons-react';
|
||||
import axios from 'axios';
|
||||
import { setCookie } from 'cookies-next';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
import { z } from 'zod';
|
||||
import { signInSchema } from '~/validations/user';
|
||||
|
||||
import { loginNamespaces } from '../tools/server/translation-namespaces';
|
||||
|
||||
// TODO: Add links to the wiki articles about the login process.
|
||||
export default function AuthenticationTitle() {
|
||||
const router = useRouter();
|
||||
export default function LoginPage() {
|
||||
const { t } = useTranslation('authentication/login');
|
||||
const queryParams = useRouter().query as { error?: 'CredentialsSignin' | (string & {}) };
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
password: '',
|
||||
},
|
||||
const form = useForm<z.infer<typeof signInSchema>>({
|
||||
validateInputOnChange: true,
|
||||
validateInputOnBlur: true,
|
||||
validate: zodResolver(signInSchema),
|
||||
});
|
||||
|
||||
const handleSubmit = (values: z.infer<typeof signInSchema>) => {
|
||||
signIn('credentials', {
|
||||
redirect: true,
|
||||
name: values.name,
|
||||
password: values.password,
|
||||
callbackUrl: '/',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Container
|
||||
size="lg"
|
||||
style={{
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
withBorder
|
||||
shadow="md"
|
||||
p={30}
|
||||
mt={30}
|
||||
radius="md"
|
||||
style={{ width: '100%', maxWidth: 420 }}
|
||||
>
|
||||
<Title
|
||||
align="center"
|
||||
sx={(theme) => ({ fontFamily: `Greycliff CF, ${theme.fontFamily}`, fontWeight: 900 })}
|
||||
>
|
||||
<Flex h="100dvh" display="flex" w="100%" direction="column" align="center" justify="center">
|
||||
<Card withBorder shadow="md" p="xl" radius="md" w="90%" maw={420}>
|
||||
<Title align="center" weight={900}>
|
||||
{t('title')}
|
||||
</Title>
|
||||
|
||||
<Text color="dimmed" size="sm" align="center" mt={5}>
|
||||
<Text color="dimmed" size="sm" align="center" mt={5} mb="md">
|
||||
{t('text')}
|
||||
</Text>
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
setCookie('password', values.password, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
sameSite: 'lax',
|
||||
});
|
||||
showNotification({
|
||||
id: 'load-data',
|
||||
loading: true,
|
||||
title: t('notifications.checking.title'),
|
||||
message: t('notifications.checking.message'),
|
||||
autoClose: false,
|
||||
withCloseButton: false,
|
||||
});
|
||||
axios
|
||||
.post('/api/configs/tryPassword', {
|
||||
tried: values.password,
|
||||
})
|
||||
.then((res) => {
|
||||
setTimeout(() => {
|
||||
if (res.data.success === true) {
|
||||
router.push('/');
|
||||
updateNotification({
|
||||
id: 'load-data',
|
||||
color: 'teal',
|
||||
title: t('notifications.correct.title'),
|
||||
message: undefined,
|
||||
icon: <IconCheck />,
|
||||
autoClose: 1000,
|
||||
});
|
||||
}
|
||||
if (res.data.success === false) {
|
||||
updateNotification({
|
||||
id: 'load-data',
|
||||
color: 'red',
|
||||
title: t('notifications.wrong.title'),
|
||||
message: undefined,
|
||||
icon: <IconX />,
|
||||
autoClose: 2000,
|
||||
});
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
})}
|
||||
>
|
||||
<PasswordInput
|
||||
id="password"
|
||||
label={t('form.fields.password.label')}
|
||||
placeholder={t('form.fields.password.placeholder') ?? undefined}
|
||||
required
|
||||
autoFocus
|
||||
mt="md"
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
<Button fullWidth type="submit" mt="xl">
|
||||
{t('form.buttons.submit')}
|
||||
</Button>
|
||||
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label={t('form.fields.username.label')}
|
||||
withAsterisk
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
id="password"
|
||||
variant="filled"
|
||||
label={t('form.fields.password.label')}
|
||||
withAsterisk
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
|
||||
<Button fullWidth type="submit">
|
||||
{t('form.buttons.submit')}
|
||||
</Button>
|
||||
|
||||
{queryParams.error === 'CredentialsSignin' && (
|
||||
<Alert icon={<IconAlertTriangle size="1rem" />} color="red">
|
||||
{t('alert')}
|
||||
</Alert>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
</Card>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { type DefaultSession, type NextAuthOptions, getServerSession } from 'nex
|
||||
import { decode, encode } from 'next-auth/jwt';
|
||||
import Credentials from 'next-auth/providers/credentials';
|
||||
import { prisma } from '~/server/db';
|
||||
import EmptyNextAuthProvider from '~/utils/empty-provider';
|
||||
import { fromDate, generateSessionToken } from '~/utils/session';
|
||||
import { signInSchema } from '~/validations/user';
|
||||
|
||||
@@ -63,9 +64,6 @@ export const constructAuthOptions = (
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
include: {
|
||||
roles: true,
|
||||
},
|
||||
});
|
||||
|
||||
session.user.isAdmin = userFromDatabase.isAdmin;
|
||||
@@ -102,6 +100,10 @@ export const constructAuthOptions = (
|
||||
strategy: 'database',
|
||||
maxAge: sessionMaxAgeInSeconds,
|
||||
},
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
error: '/login',
|
||||
},
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers: [
|
||||
Credentials({
|
||||
@@ -117,9 +119,6 @@ export const constructAuthOptions = (
|
||||
const data = await signInSchema.parseAsync(credentials);
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
include: {
|
||||
roles: true,
|
||||
},
|
||||
where: {
|
||||
name: data.name,
|
||||
},
|
||||
@@ -146,6 +145,7 @@ export const constructAuthOptions = (
|
||||
};
|
||||
},
|
||||
}),
|
||||
EmptyNextAuthProvider(),
|
||||
],
|
||||
jwt: {
|
||||
async encode(params) {
|
||||
|
||||
14
src/utils/empty-provider.ts
Normal file
14
src/utils/empty-provider.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { OAuthConfig } from 'next-auth/providers';
|
||||
|
||||
export default function EmptyNextAuthProvider(): OAuthConfig<any> {
|
||||
return {
|
||||
id: 'empty',
|
||||
name: 'Empty',
|
||||
type: 'oauth',
|
||||
profile: () => {
|
||||
throw new Error(
|
||||
'EmptyNextAuthProvider can not be used and is only a placeholder because credentials authentication can not be used as session authentication without additional providers.'
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user