✨ Add login redirection
* ✨ Add login redirection * 🚑 Fix cross site scripting using server side regex validation * ✅ Add unit test
This commit is contained in:
7
.github/workflows/docker_dev.yml
vendored
7
.github/workflows/docker_dev.yml
vendored
@@ -41,6 +41,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Setup
|
- name: Setup
|
||||||
@@ -74,7 +75,11 @@ jobs:
|
|||||||
|
|
||||||
- run: yarn turbo build
|
- run: yarn turbo build
|
||||||
|
|
||||||
- run: yarn test:run
|
- run: yarn test:coverage
|
||||||
|
|
||||||
|
- name: Report coverage
|
||||||
|
if: always()
|
||||||
|
uses: davelosert/vitest-coverage-report-action@v2
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
|
|||||||
@@ -120,6 +120,7 @@
|
|||||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^6.0.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
"@vitest/coverage-c8": "^0.33.0",
|
"@vitest/coverage-c8": "^0.33.0",
|
||||||
|
"@vitest/coverage-v8": "^0.34.5",
|
||||||
"@vitest/ui": "^0.34.4",
|
"@vitest/ui": "^0.34.4",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
"eslint-config-next": "^13.4.5",
|
"eslint-config-next": "^13.4.5",
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"submit": "Sign in"
|
"submit": "Sign in"
|
||||||
}
|
},
|
||||||
|
"afterLoginRedirection": "After login, you'll be redirected to {{url}}"
|
||||||
},
|
},
|
||||||
"alert": "Your credentials are incorrect or this account doesn't exist. Please try again."
|
"alert": "Your credentials are incorrect or this account doesn't exist. Please try again."
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { IconAlertTriangle } from '@tabler/icons-react';
|
import { IconAlertTriangle } from '@tabler/icons-react';
|
||||||
import { GetServerSideProps } from 'next';
|
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||||
import { signIn } from 'next-auth/react';
|
import { signIn } from 'next-auth/react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
@@ -20,14 +20,16 @@ import Image from 'next/image';
|
|||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
import { ThemeSchemeToggle } from '~/components/ThemeSchemeToggle/ThemeSchemeToggle';
|
||||||
import { FloatingBackground } from '~/components/layout/Background/FloatingBackground';
|
import { FloatingBackground } from '~/components/layout/Background/FloatingBackground';
|
||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
import { signInSchema } from '~/validations/user';
|
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 { t } = useTranslation('authentication/login');
|
||||||
const { i18nZodResolver } = useI18nZodResolver();
|
const { i18nZodResolver } = useI18nZodResolver();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -54,7 +56,7 @@ export default function LoginPage() {
|
|||||||
setIsError(true);
|
setIsError(true);
|
||||||
return;
|
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">
|
<Flex h="100dvh" display="flex" w="100%" direction="column" align="center" justify="center">
|
||||||
<FloatingBackground />
|
<FloatingBackground />
|
||||||
<ThemeSchemeToggle pos="absolute" top={20} right={20}/>
|
<ThemeSchemeToggle pos="absolute" top={20} right={20} />
|
||||||
<Stack spacing={40} align="center" w="100%">
|
<Stack spacing={40} align="center" w="100%">
|
||||||
<Stack spacing={0} align="center">
|
<Stack spacing={0} align="center">
|
||||||
<Image src="/imgs/logo/logo.svg" width={80} height={80} alt="" />
|
<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}>
|
<Button mt="xs" variant="light" fullWidth type="submit" loading={isLoading}>
|
||||||
{t('form.buttons.submit')}
|
{t('form.buttons.submit')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{redirectAfterLogin && (
|
||||||
|
<Text color="dimmed" align="center" size="xs">
|
||||||
|
{t('form.afterLoginRedirection', { url: redirectAfterLogin })}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
</Card>
|
</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 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) {
|
if (session) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
@@ -144,6 +159,7 @@ export const getServerSideProps: GetServerSideProps = async ({ locale, req, res
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...(await getServerSideTranslations(['authentication/login'], locale, req, res)),
|
...(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 { BoardLayout } from '~/components/layout/Templates/BoardLayout';
|
||||||
import { useInitConfig } from '~/config/init';
|
import { useInitConfig } from '~/config/init';
|
||||||
import { env } from '~/env';
|
import { env } from '~/env';
|
||||||
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { configExists } from '~/tools/config/configExists';
|
import { configExists } from '~/tools/config/configExists';
|
||||||
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';
|
import { getFrontendConfig } from '~/tools/config/getFrontendConfig';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
|
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||||
import { boardNamespaces } from '~/tools/server/translation-namespaces';
|
import { boardNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { ConfigType } from '~/types/config';
|
import { ConfigType } from '~/types/config';
|
||||||
import { getServerAuthSession } from '~/server/auth';
|
|
||||||
|
|
||||||
export default function BoardPage({
|
export default function BoardPage({
|
||||||
config: initialConfig,
|
config: initialConfig,
|
||||||
@@ -35,13 +36,8 @@ const routeParamsSchema = z.object({
|
|||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = async ({
|
export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = async (ctx) => {
|
||||||
params,
|
const routeParams = routeParamsSchema.safeParse(ctx.params);
|
||||||
locale,
|
|
||||||
req,
|
|
||||||
res,
|
|
||||||
}) => {
|
|
||||||
const routeParams = routeParamsSchema.safeParse(params);
|
|
||||||
if (!routeParams.success) {
|
if (!routeParams.success) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
@@ -56,38 +52,32 @@ export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = a
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = await getFrontendConfig(routeParams.data.slug);
|
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 = () => {
|
const session = await getServerAuthSession({ req: ctx.req, res: ctx.res });
|
||||||
return {
|
|
||||||
props: {
|
const result = checkForSessionOrAskForLogin(
|
||||||
config,
|
ctx,
|
||||||
primaryColor: config.settings.customization.colors.primary,
|
session,
|
||||||
secondaryColor: config.settings.customization.colors.secondary,
|
() => config.settings.access.allowGuests || !session?.user
|
||||||
primaryShade: config.settings.customization.colors.shade,
|
);
|
||||||
dockerEnabled: !!env.DOCKER_HOST && !!env.DOCKER_PORT,
|
if (result) {
|
||||||
...translations,
|
return result;
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
if (!config.settings.access.allowGuests) {
|
props: {
|
||||||
const session = await getServerAuthSession({ req, res });
|
config,
|
||||||
|
primaryColor: config.settings.customization.colors.primary,
|
||||||
if (session?.user) {
|
secondaryColor: config.settings.customization.colors.secondary,
|
||||||
return getSuccessResponse();
|
primaryShade: config.settings.customization.colors.shade,
|
||||||
}
|
dockerEnabled: !!env.DOCKER_HOST && !!env.DOCKER_PORT,
|
||||||
|
...translations,
|
||||||
return {
|
},
|
||||||
notFound: true,
|
};
|
||||||
props: {
|
|
||||||
primaryColor: config.settings.customization.colors.primary,
|
|
||||||
secondaryColor: config.settings.customization.colors.secondary,
|
|
||||||
primaryShade: config.settings.customization.colors.shade,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return getSuccessResponse();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useListState } from '@mantine/hooks';
|
import { useListState } from '@mantine/hooks';
|
||||||
import { modals } from '@mantine/modals';
|
|
||||||
import {
|
import {
|
||||||
IconBox,
|
IconBox,
|
||||||
IconCategory,
|
IconCategory,
|
||||||
@@ -35,6 +34,7 @@ import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
|||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { sleep } from '~/tools/client/time';
|
import { sleep } from '~/tools/client/time';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
|
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
@@ -204,10 +204,9 @@ const BoardsPage = () => {
|
|||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const session = await getServerAuthSession(ctx);
|
const session = await getServerAuthSession(ctx);
|
||||||
|
|
||||||
if (!session?.user) {
|
const result = checkForSessionOrAskForLogin(ctx, session, () => true);
|
||||||
return {
|
if (result) {
|
||||||
notFound: true,
|
return result;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
|
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
|
|
||||||
@@ -132,10 +133,9 @@ export type CreateAccountSchema = z.infer<typeof createAccountSchema>;
|
|||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const session = await getServerAuthSession(ctx);
|
const session = await getServerAuthSession(ctx);
|
||||||
|
|
||||||
if (!session?.user.isAdmin) {
|
const result = checkForSessionOrAskForLogin(ctx, session, () => session?.user.isAdmin == true);
|
||||||
return {
|
if (result) {
|
||||||
notFound: true,
|
return result;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
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;
|
||||||
|
};
|
||||||
125
tests/pages/auth/login.spec.ts
Normal file
125
tests/pages/auth/login.spec.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
import { IncomingMessage } from 'http';
|
||||||
|
import { ServerResponse } from 'http';
|
||||||
|
import { GetServerSidePropsContext } from 'next';
|
||||||
|
import { SSRConfig } from 'next-i18next';
|
||||||
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
|
import { describe, expect, it, vitest } from 'vitest';
|
||||||
|
import { getServerSideProps } from '~/pages/auth/login';
|
||||||
|
import * as serverAuthModule from '~/server/auth';
|
||||||
|
|
||||||
|
import * as getServerSideTranslationsModule from '../../../src/tools/server/getServerSideTranslations';
|
||||||
|
|
||||||
|
vitest.mock('./../../server/auth.ts', () => ({
|
||||||
|
getServerAuthSession: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vitest.mock('./../../tools/server/getServerSideTranslations.ts', () => ({
|
||||||
|
getServerSideTranslations: () => null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('login page', () => {
|
||||||
|
it('getServerSideProps should return null redirectAfterLogin when no query value', async () => {
|
||||||
|
// arrange
|
||||||
|
vitest.spyOn(serverAuthModule, 'getServerAuthSession').mockReturnValue(Promise.resolve(null));
|
||||||
|
vitest
|
||||||
|
.spyOn(getServerSideTranslationsModule, 'getServerSideTranslations')
|
||||||
|
.mockReturnValue(Promise.resolve({ _i18Next: 'hello' } as unknown as SSRConfig));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const response = await getServerSideProps({
|
||||||
|
query: {},
|
||||||
|
locale: 'de-DE',
|
||||||
|
req: {},
|
||||||
|
res: {},
|
||||||
|
} as GetServerSidePropsContext<ParsedUrlQuery>);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(response).toStrictEqual({
|
||||||
|
props: {
|
||||||
|
redirectAfterLogin: null,
|
||||||
|
_i18Next: 'hello',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(serverAuthModule.getServerAuthSession).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledWith(
|
||||||
|
['authentication/login'],
|
||||||
|
'de-DE',
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getServerSideProps should return url when redirectAfterLogin is local and valid', async () => {
|
||||||
|
// arrange
|
||||||
|
vitest.spyOn(serverAuthModule, 'getServerAuthSession').mockReturnValue(Promise.resolve(null));
|
||||||
|
vitest
|
||||||
|
.spyOn(getServerSideTranslationsModule, 'getServerSideTranslations')
|
||||||
|
.mockReturnValue(Promise.resolve({ _i18Next: 'hello' } as unknown as SSRConfig));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const response = await getServerSideProps({
|
||||||
|
query: {
|
||||||
|
redirectAfterLogin: '/manage/users/create',
|
||||||
|
},
|
||||||
|
locale: 'de-DE',
|
||||||
|
req: {} as IncomingMessage & { cookies: Partial<{ [key: string]: string }> },
|
||||||
|
res: {} as ServerResponse<IncomingMessage>,
|
||||||
|
resolvedUrl: '/auth/login',
|
||||||
|
} as GetServerSidePropsContext<ParsedUrlQuery>);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(response).toStrictEqual({
|
||||||
|
props: {
|
||||||
|
redirectAfterLogin: '/manage/users/create',
|
||||||
|
_i18Next: 'hello',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(serverAuthModule.getServerAuthSession).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledWith(
|
||||||
|
['authentication/login'],
|
||||||
|
'de-DE',
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getServerSideProps should return null when url does not match regex', async () => {
|
||||||
|
// arrange
|
||||||
|
vitest.spyOn(serverAuthModule, 'getServerAuthSession').mockReturnValue(Promise.resolve(null));
|
||||||
|
vitest
|
||||||
|
.spyOn(getServerSideTranslationsModule, 'getServerSideTranslations')
|
||||||
|
.mockReturnValue(Promise.resolve({ _i18Next: 'hello' } as unknown as SSRConfig));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const response = await getServerSideProps({
|
||||||
|
query: {
|
||||||
|
redirectAfterLogin: "data:text/html,<script>alert('hi');</script>",
|
||||||
|
},
|
||||||
|
locale: 'de-DE',
|
||||||
|
req: {} as IncomingMessage & { cookies: Partial<{ [key: string]: string }> },
|
||||||
|
res: {} as ServerResponse<IncomingMessage>,
|
||||||
|
resolvedUrl: '/auth/login',
|
||||||
|
} as GetServerSidePropsContext<ParsedUrlQuery>);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(response).toStrictEqual({
|
||||||
|
props: {
|
||||||
|
redirectAfterLogin: null,
|
||||||
|
_i18Next: 'hello',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(serverAuthModule.getServerAuthSession).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledOnce();
|
||||||
|
expect(getServerSideTranslationsModule.getServerSideTranslations).toHaveBeenCalledWith(
|
||||||
|
['authentication/login'],
|
||||||
|
'de-DE',
|
||||||
|
{},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -158,12 +158,11 @@ describe('[slug] page', () => {
|
|||||||
|
|
||||||
// assert
|
// assert
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
notFound: true,
|
redirect: {
|
||||||
props: {
|
destination: "/auth/login?redirectAfterLogin=/board/my-authentication-board",
|
||||||
primaryColor: 'red',
|
permanent: false
|
||||||
secondaryColor: 'blue',
|
|
||||||
primaryShade: 'green',
|
|
||||||
},
|
},
|
||||||
|
props: {},
|
||||||
});
|
});
|
||||||
expect(serverAuthModule.getServerAuthSession).toHaveBeenCalledOnce();
|
expect(serverAuthModule.getServerAuthSession).toHaveBeenCalledOnce();
|
||||||
expect(configExistsModule.configExists).toHaveBeenCalledOnce();
|
expect(configExistsModule.configExists).toHaveBeenCalledOnce();
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default defineConfig({
|
|||||||
environment: 'happy-dom',
|
environment: 'happy-dom',
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: 'v8',
|
provider: 'v8',
|
||||||
reporter: ['html'],
|
reporter: ['html', 'json-summary', 'json'],
|
||||||
all: true,
|
all: true,
|
||||||
exclude: ['.next/', '.yarn/', 'data/'],
|
exclude: ['.next/', '.yarn/', 'data/'],
|
||||||
},
|
},
|
||||||
|
|||||||
46
yarn.lock
46
yarn.lock
@@ -3231,6 +3231,27 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@vitest/coverage-v8@npm:^0.34.5":
|
||||||
|
version: 0.34.5
|
||||||
|
resolution: "@vitest/coverage-v8@npm:0.34.5"
|
||||||
|
dependencies:
|
||||||
|
"@ampproject/remapping": ^2.2.1
|
||||||
|
"@bcoe/v8-coverage": ^0.2.3
|
||||||
|
istanbul-lib-coverage: ^3.2.0
|
||||||
|
istanbul-lib-report: ^3.0.1
|
||||||
|
istanbul-lib-source-maps: ^4.0.1
|
||||||
|
istanbul-reports: ^3.1.5
|
||||||
|
magic-string: ^0.30.1
|
||||||
|
picocolors: ^1.0.0
|
||||||
|
std-env: ^3.3.3
|
||||||
|
test-exclude: ^6.0.0
|
||||||
|
v8-to-istanbul: ^9.1.0
|
||||||
|
peerDependencies:
|
||||||
|
vitest: ">=0.32.0 <1"
|
||||||
|
checksum: 6ffbcbd0d992b535c7fbc5d8ad82f8939e11786e28a899d1b2ef60f51c192063738d60b0037f7c5fec5545130f671b408bc5f20432abb280bf9788214467c475
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@vitest/expect@npm:0.33.0":
|
"@vitest/expect@npm:0.33.0":
|
||||||
version: 0.33.0
|
version: 0.33.0
|
||||||
resolution: "@vitest/expect@npm:0.33.0"
|
resolution: "@vitest/expect@npm:0.33.0"
|
||||||
@@ -6948,6 +6969,7 @@ __metadata:
|
|||||||
"@typescript-eslint/parser": ^6.0.0
|
"@typescript-eslint/parser": ^6.0.0
|
||||||
"@vitejs/plugin-react": ^4.0.0
|
"@vitejs/plugin-react": ^4.0.0
|
||||||
"@vitest/coverage-c8": ^0.33.0
|
"@vitest/coverage-c8": ^0.33.0
|
||||||
|
"@vitest/coverage-v8": ^0.34.5
|
||||||
"@vitest/ui": ^0.34.4
|
"@vitest/ui": ^0.34.4
|
||||||
axios: ^1.0.0
|
axios: ^1.0.0
|
||||||
bcryptjs: ^2.4.3
|
bcryptjs: ^2.4.3
|
||||||
@@ -7729,7 +7751,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"istanbul-lib-report@npm:^3.0.0":
|
"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1":
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
resolution: "istanbul-lib-report@npm:3.0.1"
|
resolution: "istanbul-lib-report@npm:3.0.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7740,7 +7762,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"istanbul-reports@npm:^3.1.4":
|
"istanbul-lib-source-maps@npm:^4.0.1":
|
||||||
|
version: 4.0.1
|
||||||
|
resolution: "istanbul-lib-source-maps@npm:4.0.1"
|
||||||
|
dependencies:
|
||||||
|
debug: ^4.1.1
|
||||||
|
istanbul-lib-coverage: ^3.0.0
|
||||||
|
source-map: ^0.6.1
|
||||||
|
checksum: 21ad3df45db4b81852b662b8d4161f6446cd250c1ddc70ef96a585e2e85c26ed7cd9c2a396a71533cfb981d1a645508bc9618cae431e55d01a0628e7dec62ef2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"istanbul-reports@npm:^3.1.4, istanbul-reports@npm:^3.1.5":
|
||||||
version: 3.1.6
|
version: 3.1.6
|
||||||
resolution: "istanbul-reports@npm:3.1.6"
|
resolution: "istanbul-reports@npm:3.1.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10904,6 +10937,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"source-map@npm:^0.6.1":
|
||||||
|
version: 0.6.1
|
||||||
|
resolution: "source-map@npm:0.6.1"
|
||||||
|
checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"split-ca@npm:^1.0.1":
|
"split-ca@npm:^1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "split-ca@npm:1.0.1"
|
resolution: "split-ca@npm:1.0.1"
|
||||||
@@ -12134,7 +12174,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"v8-to-istanbul@npm:^9.0.0":
|
"v8-to-istanbul@npm:^9.0.0, v8-to-istanbul@npm:^9.1.0":
|
||||||
version: 9.1.0
|
version: 9.1.0
|
||||||
resolution: "v8-to-istanbul@npm:9.1.0"
|
resolution: "v8-to-istanbul@npm:9.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user