✨ Add login redirection
* ✨ Add login redirection * 🚑 Fix cross site scripting using server side regex validation * ✅ Add unit test
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { useForm } from '@mantine/form';
|
||||
import { IconAlertTriangle } from '@tabler/icons-react';
|
||||
import { GetServerSideProps } from 'next';
|
||||
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Head from 'next/head';
|
||||
@@ -20,14 +20,16 @@ import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { z } from 'zod';
|
||||
import { ThemeSchemeToggle } from '~/components/ThemeSchemeToggle/ThemeSchemeToggle';
|
||||
import { FloatingBackground } from '~/components/layout/Background/FloatingBackground';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||
import { signInSchema } from '~/validations/user';
|
||||
import { ThemeSchemeToggle } from '~/components/ThemeSchemeToggle/ThemeSchemeToggle';
|
||||
|
||||
export default function LoginPage() {
|
||||
export default function LoginPage({
|
||||
redirectAfterLogin,
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const { t } = useTranslation('authentication/login');
|
||||
const { i18nZodResolver } = useI18nZodResolver();
|
||||
const router = useRouter();
|
||||
@@ -54,7 +56,7 @@ export default function LoginPage() {
|
||||
setIsError(true);
|
||||
return;
|
||||
}
|
||||
router.push('/manage');
|
||||
router.push(redirectAfterLogin ?? '/manage');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,7 +70,7 @@ export default function LoginPage() {
|
||||
|
||||
<Flex h="100dvh" display="flex" w="100%" direction="column" align="center" justify="center">
|
||||
<FloatingBackground />
|
||||
<ThemeSchemeToggle pos="absolute" top={20} right={20}/>
|
||||
<ThemeSchemeToggle pos="absolute" top={20} right={20} />
|
||||
<Stack spacing={40} align="center" w="100%">
|
||||
<Stack spacing={0} align="center">
|
||||
<Image src="/imgs/logo/logo.svg" width={80} height={80} alt="" />
|
||||
@@ -120,6 +122,12 @@ export default function LoginPage() {
|
||||
<Button mt="xs" variant="light" fullWidth type="submit" loading={isLoading}>
|
||||
{t('form.buttons.submit')}
|
||||
</Button>
|
||||
|
||||
{redirectAfterLogin && (
|
||||
<Text color="dimmed" align="center" size="xs">
|
||||
{t('form.afterLoginRedirection', { url: redirectAfterLogin })}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</form>
|
||||
</Card>
|
||||
@@ -129,9 +137,16 @@ export default function LoginPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ locale, req, res }) => {
|
||||
const regexExp = /^\/{1}[A-z\/]*$/;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async ({ locale, req, res, query }) => {
|
||||
const session = await getServerAuthSession({ req, res });
|
||||
|
||||
const zodResult = await z
|
||||
.object({ redirectAfterLogin: z.string().regex(regexExp) })
|
||||
.safeParseAsync(query);
|
||||
const redirectAfterLogin = zodResult.success ? zodResult.data.redirectAfterLogin : null;
|
||||
|
||||
if (session) {
|
||||
return {
|
||||
redirect: {
|
||||
@@ -144,6 +159,7 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, req, res
|
||||
return {
|
||||
props: {
|
||||
...(await getServerSideTranslations(['authentication/login'], locale, req, res)),
|
||||
redirectAfterLogin,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -5,12 +5,13 @@ import { Dashboard } from '~/components/Dashboard/Dashboard';
|
||||
import { BoardLayout } from '~/components/layout/Templates/BoardLayout';
|
||||
import { useInitConfig } from '~/config/init';
|
||||
import { env } from '~/env';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { configExists } from '~/tools/config/configExists';
|
||||
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||
import { boardNamespaces } from '~/tools/server/translation-namespaces';
|
||||
import { ConfigType } from '~/types/config';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
|
||||
export default function BoardPage({
|
||||
config: initialConfig,
|
||||
@@ -35,13 +36,8 @@ const routeParamsSchema = z.object({
|
||||
slug: z.string(),
|
||||
});
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = async ({
|
||||
params,
|
||||
locale,
|
||||
req,
|
||||
res,
|
||||
}) => {
|
||||
const routeParams = routeParamsSchema.safeParse(params);
|
||||
export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = async (ctx) => {
|
||||
const routeParams = routeParamsSchema.safeParse(ctx.params);
|
||||
if (!routeParams.success) {
|
||||
return {
|
||||
notFound: true,
|
||||
@@ -56,38 +52,32 @@ export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = a
|
||||
}
|
||||
|
||||
const config = await getFrontendConfig(routeParams.data.slug);
|
||||
const translations = await getServerSideTranslations(boardNamespaces, locale, req, res);
|
||||
const translations = await getServerSideTranslations(
|
||||
boardNamespaces,
|
||||
ctx.locale,
|
||||
ctx.req,
|
||||
ctx.res
|
||||
);
|
||||
|
||||
const getSuccessResponse = () => {
|
||||
return {
|
||||
props: {
|
||||
config,
|
||||
primaryColor: config.settings.customization.colors.primary,
|
||||
secondaryColor: config.settings.customization.colors.secondary,
|
||||
primaryShade: config.settings.customization.colors.shade,
|
||||
dockerEnabled: !!env.DOCKER_HOST && !!env.DOCKER_PORT,
|
||||
...translations,
|
||||
},
|
||||
};
|
||||
const session = await getServerAuthSession({ req: ctx.req, res: ctx.res });
|
||||
|
||||
const result = checkForSessionOrAskForLogin(
|
||||
ctx,
|
||||
session,
|
||||
() => config.settings.access.allowGuests || !session?.user
|
||||
);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
if (!config.settings.access.allowGuests) {
|
||||
const session = await getServerAuthSession({ req, res });
|
||||
|
||||
if (session?.user) {
|
||||
return getSuccessResponse();
|
||||
}
|
||||
|
||||
return {
|
||||
notFound: true,
|
||||
props: {
|
||||
primaryColor: config.settings.customization.colors.primary,
|
||||
secondaryColor: config.settings.customization.colors.secondary,
|
||||
primaryShade: config.settings.customization.colors.shade,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return getSuccessResponse();
|
||||
return {
|
||||
props: {
|
||||
config,
|
||||
primaryColor: config.settings.customization.colors.primary,
|
||||
secondaryColor: config.settings.customization.colors.secondary,
|
||||
primaryShade: config.settings.customization.colors.shade,
|
||||
dockerEnabled: !!env.DOCKER_HOST && !!env.DOCKER_PORT,
|
||||
...translations,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Title,
|
||||
} from '@mantine/core';
|
||||
import { useListState } from '@mantine/hooks';
|
||||
import { modals } from '@mantine/modals';
|
||||
import {
|
||||
IconBox,
|
||||
IconCategory,
|
||||
@@ -35,6 +34,7 @@ import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { sleep } from '~/tools/client/time';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
@@ -204,10 +204,9 @@ const BoardsPage = () => {
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const session = await getServerAuthSession(ctx);
|
||||
|
||||
if (!session?.user) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
const result = checkForSessionOrAskForLogin(ctx, session, () => true);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const translations = await getServerSideTranslations(
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||
|
||||
@@ -132,10 +133,9 @@ export type CreateAccountSchema = z.infer<typeof createAccountSchema>;
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
const session = await getServerAuthSession(ctx);
|
||||
|
||||
if (!session?.user.isAdmin) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
const result = checkForSessionOrAskForLogin(ctx, session, () => session?.user.isAdmin == true);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const translations = await getServerSideTranslations(
|
||||
|
||||
34
src/tools/server/loginBuilder.ts
Normal file
34
src/tools/server/loginBuilder.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
GetServerSideProps,
|
||||
GetServerSidePropsContext,
|
||||
GetServerSidePropsResult,
|
||||
PreviewData,
|
||||
} from 'next';
|
||||
import { Session } from 'next-auth';
|
||||
import { ParsedUrlQuery } from 'querystring';
|
||||
|
||||
export const checkForSessionOrAskForLogin = (
|
||||
context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>,
|
||||
session: Session | null,
|
||||
accessCallback: () => boolean,
|
||||
): GetServerSidePropsResult<any> | undefined => {
|
||||
if (!session?.user) {
|
||||
console.log('detected logged out user!');
|
||||
return {
|
||||
props: {},
|
||||
redirect: {
|
||||
destination: `/auth/login?redirectAfterLogin=${context.resolvedUrl}`,
|
||||
permanent: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!accessCallback()) {
|
||||
return {
|
||||
props: {},
|
||||
notFound: true
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
Reference in New Issue
Block a user