🐛 Fix issues with login page

This commit is contained in:
Meier Lukas
2023-07-28 20:45:54 +02:00
parent d7f6bdf417
commit 326395730e
8 changed files with 1104 additions and 997 deletions

View File

@@ -39,7 +39,7 @@
"@mantine/modals": "^6.0.0", "@mantine/modals": "^6.0.0",
"@mantine/next": "^6.0.0", "@mantine/next": "^6.0.0",
"@mantine/notifications": "^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/core": "^0.83.0",
"@nivo/line": "^0.83.0", "@nivo/line": "^0.83.0",
"@prisma/client": "^5.0.0", "@prisma/client": "^5.0.0",
@@ -70,7 +70,7 @@
"i18next": "^22.5.1", "i18next": "^22.5.1",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"next": "13.4.10", "next": "13.4.10",
"next-auth": "^4.22.3", "next-auth": "^4.21.0",
"next-i18next": "^13.0.0", "next-i18next": "^13.0.0",
"nzbget-api": "^0.0.3", "nzbget-api": "^0.0.3",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",

Binary file not shown.

View File

@@ -1,27 +1,18 @@
{ {
"title": "Welcome back!", "title": "Welcome back!",
"text": "Please enter your password", "text": "Please enter your credentials",
"form": { "form": {
"fields": { "fields": {
"username": {
"label": "Username"
},
"password": { "password": {
"label": "Password", "label": "Password"
"placeholder": "Your password"
} }
}, },
"buttons": { "buttons": {
"submit": "Sign in" "submit": "Sign in"
} }
}, },
"notifications": { "alert": "Your credentials are incorrect or this account doesn't exist. Please try again."
"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."
}
}
}

View File

@@ -1,116 +1,88 @@
import { Button, Container, Paper, PasswordInput, Text, Title } from '@mantine/core'; import {
import { useForm } from '@mantine/form'; 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 { 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 axios from 'axios';
import { setCookie } from 'cookies-next'; import { setCookie } from 'cookies-next';
import { signIn } from 'next-auth/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useRouter } from 'next/router'; 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'; import { loginNamespaces } from '../tools/server/translation-namespaces';
// TODO: Add links to the wiki articles about the login process. export default function LoginPage() {
export default function AuthenticationTitle() {
const router = useRouter();
const { t } = useTranslation('authentication/login'); const { t } = useTranslation('authentication/login');
const queryParams = useRouter().query as { error?: 'CredentialsSignin' | (string & {}) };
const form = useForm({ const form = useForm<z.infer<typeof signInSchema>>({
initialValues: { validateInputOnChange: true,
password: '', validateInputOnBlur: true,
}, validate: zodResolver(signInSchema),
}); });
const handleSubmit = (values: z.infer<typeof signInSchema>) => {
signIn('credentials', {
redirect: true,
name: values.name,
password: values.password,
callbackUrl: '/',
});
};
return ( return (
<Container <Flex h="100dvh" display="flex" w="100%" direction="column" align="center" justify="center">
size="lg" <Card withBorder shadow="md" p="xl" radius="md" w="90%" maw={420}>
style={{ <Title align="center" weight={900}>
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 })}
>
{t('title')} {t('title')}
</Title> </Title>
<Text color="dimmed" size="sm" align="center" mt={5}> <Text color="dimmed" size="sm" align="center" mt={5} mb="md">
{t('text')} {t('text')}
</Text> </Text>
<form
onSubmit={form.onSubmit((values) => { <form onSubmit={form.onSubmit(handleSubmit)}>
setCookie('password', values.password, { <Stack>
maxAge: 60 * 60 * 24 * 30, <TextInput
sameSite: 'lax', variant="filled"
}); label={t('form.fields.username.label')}
showNotification({ withAsterisk
id: 'load-data', {...form.getInputProps('name')}
loading: true, />
title: t('notifications.checking.title'),
message: t('notifications.checking.message'), <PasswordInput
autoClose: false, id="password"
withCloseButton: false, variant="filled"
}); label={t('form.fields.password.label')}
axios withAsterisk
.post('/api/configs/tryPassword', { {...form.getInputProps('password')}
tried: values.password, />
})
.then((res) => { <Button fullWidth type="submit">
setTimeout(() => { {t('form.buttons.submit')}
if (res.data.success === true) { </Button>
router.push('/');
updateNotification({ {queryParams.error === 'CredentialsSignin' && (
id: 'load-data', <Alert icon={<IconAlertTriangle size="1rem" />} color="red">
color: 'teal', {t('alert')}
title: t('notifications.correct.title'), </Alert>
message: undefined, )}
icon: <IconCheck />, </Stack>
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> </form>
</Paper> </Card>
</Container> </Flex>
); );
} }

View File

@@ -6,6 +6,7 @@ import { type DefaultSession, type NextAuthOptions, getServerSession } from 'nex
import { decode, encode } from 'next-auth/jwt'; import { decode, encode } from 'next-auth/jwt';
import Credentials from 'next-auth/providers/credentials'; import Credentials from 'next-auth/providers/credentials';
import { prisma } from '~/server/db'; import { prisma } from '~/server/db';
import EmptyNextAuthProvider from '~/utils/empty-provider';
import { fromDate, generateSessionToken } from '~/utils/session'; import { fromDate, generateSessionToken } from '~/utils/session';
import { signInSchema } from '~/validations/user'; import { signInSchema } from '~/validations/user';
@@ -63,9 +64,6 @@ export const constructAuthOptions = (
where: { where: {
id: user.id, id: user.id,
}, },
include: {
roles: true,
},
}); });
session.user.isAdmin = userFromDatabase.isAdmin; session.user.isAdmin = userFromDatabase.isAdmin;
@@ -102,6 +100,10 @@ export const constructAuthOptions = (
strategy: 'database', strategy: 'database',
maxAge: sessionMaxAgeInSeconds, maxAge: sessionMaxAgeInSeconds,
}, },
pages: {
signIn: '/login',
error: '/login',
},
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
providers: [ providers: [
Credentials({ Credentials({
@@ -117,9 +119,6 @@ export const constructAuthOptions = (
const data = await signInSchema.parseAsync(credentials); const data = await signInSchema.parseAsync(credentials);
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
include: {
roles: true,
},
where: { where: {
name: data.name, name: data.name,
}, },
@@ -146,6 +145,7 @@ export const constructAuthOptions = (
}; };
}, },
}), }),
EmptyNextAuthProvider(),
], ],
jwt: { jwt: {
async encode(params) { async encode(params) {

View 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.'
);
},
};
}

1888
yarn.lock

File diff suppressed because it is too large Load Diff