✨ Add 401 page (#1508)
This commit is contained in:
5
public/locales/en/layout/errors/access-denied.json
Normal file
5
public/locales/en/layout/errors/access-denied.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Access denied",
|
||||||
|
"text": "You do not have sufficient permissions to access this page. If you believe, that this is not intentional, please contact your administrator.",
|
||||||
|
"switchAccount": "Switch to a different account"
|
||||||
|
}
|
||||||
1
src/images/undraw_secure_login_pdn4.svg
Normal file
1
src/images/undraw_secure_login_pdn4.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.4 KiB |
55
src/pages/401.tsx
Normal file
55
src/pages/401.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Button, Center, Stack, Title, Text, createStyles } from "@mantine/core";
|
||||||
|
import { useTranslation } from "next-i18next";
|
||||||
|
import Image from "next/image";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { MainLayout } from "~/components/layout/Templates/MainLayout";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import imageAccessDenied from '~/images/undraw_secure_login_pdn4.svg';
|
||||||
|
import { pageAccessDeniedNamespaces } from "~/tools/server/translation-namespaces";
|
||||||
|
import { getServerSideTranslations } from "~/tools/server/getServerSideTranslations";
|
||||||
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
|
export default function Custom401() {
|
||||||
|
const { classes } = useStyles();
|
||||||
|
const { t } = useTranslation('layout/errors/access-denied');
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<Center h="100dvh" w="100dvw">
|
||||||
|
<Head>
|
||||||
|
<title>Access denied • Homarr</title>
|
||||||
|
</Head>
|
||||||
|
<Stack maw={500} p="xl">
|
||||||
|
<Image className={classes.image} src={imageAccessDenied} width={200} height={200} alt="" />
|
||||||
|
<Title>{t('title')}</Title>
|
||||||
|
<Text>{t('text')}</Text>
|
||||||
|
|
||||||
|
<Button component={Link} variant="light" href="/auth/login">
|
||||||
|
{t('switchAccount')}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Center>
|
||||||
|
</MainLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticProps({ req, res, locale }: GetServerSidePropsContext) {
|
||||||
|
const translations = await getServerSideTranslations(
|
||||||
|
[...pageAccessDeniedNamespaces, 'common'],
|
||||||
|
locale,
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...translations,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = createStyles(() => ({
|
||||||
|
image: {
|
||||||
|
margin: '0 auto',
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -44,6 +44,7 @@ import { MainLayout } from '~/components/layout/Templates/MainLayout';
|
|||||||
import { createTrpcServersideHelpers } from '~/server/api/helper';
|
import { createTrpcServersideHelpers } from '~/server/api/helper';
|
||||||
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 { firstUpperCase } from '~/tools/shared/strings';
|
import { firstUpperCase } from '~/tools/shared/strings';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
@@ -275,22 +276,22 @@ const routeParamsSchema = z.object({
|
|||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async ({ req, res, locale, params }) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const routeParams = routeParamsSchema.safeParse(params);
|
const routeParams = routeParamsSchema.safeParse(context.params);
|
||||||
if (!routeParams.success) {
|
if (!routeParams.success) {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await getServerAuthSession({ req, res });
|
const session = await getServerAuthSession({ req: context.req, res: context.res });
|
||||||
if (!session?.user.isAdmin) {
|
|
||||||
return {
|
const result = checkForSessionOrAskForLogin(context, session, () => session?.user.isAdmin == true);
|
||||||
notFound: true,
|
if (result) {
|
||||||
};
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const helpers = await createTrpcServersideHelpers({ req, res });
|
const helpers = await createTrpcServersideHelpers({ req: context.req, res: context.res });
|
||||||
|
|
||||||
const config = await helpers.config.byName.fetch({ name: routeParams.data.slug });
|
const config = await helpers.config.byName.fetch({ name: routeParams.data.slug });
|
||||||
|
|
||||||
@@ -305,9 +306,9 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res, locale,
|
|||||||
'settings/customization/gridstack',
|
'settings/customization/gridstack',
|
||||||
'settings/customization/access',
|
'settings/customization/access',
|
||||||
],
|
],
|
||||||
locale,
|
context.locale,
|
||||||
req,
|
context.req,
|
||||||
res
|
context.res
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { getDefaultBoardAsync } from '~/server/db/queries/userSettings';
|
|||||||
import { userSettings } from '~/server/db/schema';
|
import { userSettings } from '~/server/db/schema';
|
||||||
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';
|
||||||
|
|
||||||
@@ -45,15 +46,9 @@ export const getServerSideProps: GetServerSideProps<BoardGetServerSideProps> = a
|
|||||||
);
|
);
|
||||||
const config = await getFrontendConfig(boardName);
|
const config = await getFrontendConfig(boardName);
|
||||||
|
|
||||||
if (!config?.settings?.access?.allowGuests && !session?.user) {
|
const result = checkForSessionOrAskForLogin(ctx, session, () => true);
|
||||||
return {
|
if (result) {
|
||||||
notFound: true,
|
return result;
|
||||||
props: {
|
|
||||||
primaryColor: config.settings.customization.colors.primary,
|
|
||||||
secondaryColor: config.settings.customization.colors.secondary,
|
|
||||||
primaryShade: config.settings.customization.colors.shade,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { getServerSideTranslations } from '~/tools/server/getServerSideTranslati
|
|||||||
import { OnlyKeysWithStructure } from '~/types/helpers';
|
import { OnlyKeysWithStructure } from '~/types/helpers';
|
||||||
|
|
||||||
import { type quickActions } from '../../../public/locales/en/manage/index.json';
|
import { type quickActions } from '../../../public/locales/en/manage/index.json';
|
||||||
|
import { checkForSessionOrAskForLogin } from '~/tools/server/loginBuilder';
|
||||||
|
|
||||||
const ManagementPage = () => {
|
const ManagementPage = () => {
|
||||||
const { t } = useTranslation('manage/index');
|
const { t } = useTranslation('manage/index');
|
||||||
@@ -118,10 +119,9 @@ const QuickActionCard = ({ type, href }: QuickActionCardProps) => {
|
|||||||
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(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
|||||||
import { dockerRouter } from '~/server/api/routers/docker/router';
|
import { dockerRouter } from '~/server/api/routers/docker/router';
|
||||||
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 { boardNamespaces } from '~/tools/server/translation-namespaces';
|
import { boardNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
@@ -54,24 +55,23 @@ export default function DockerPage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async ({ locale, req, res }) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const session = await getServerAuthSession({ req, res });
|
const session = await getServerAuthSession({ req: context.req, res: context.res });
|
||||||
if (!session?.user.isAdmin) {
|
const result = checkForSessionOrAskForLogin(context, session, () => session?.user.isAdmin == true);
|
||||||
return {
|
if (result) {
|
||||||
notFound: true,
|
return result;
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const caller = dockerRouter.createCaller({
|
const caller = dockerRouter.createCaller({
|
||||||
session: session,
|
session: session,
|
||||||
cookies: req.cookies,
|
cookies: context.req.cookies,
|
||||||
});
|
});
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
[...boardNamespaces, 'layout/manage', 'tools/docker'],
|
[...boardNamespaces, 'layout/manage', 'tools/docker'],
|
||||||
locale,
|
context.locale,
|
||||||
req,
|
context.req,
|
||||||
res
|
context.res
|
||||||
);
|
);
|
||||||
|
|
||||||
let containers = [];
|
let containers = [];
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { openDeleteUserModal } from '~/components/Manage/User/delete-user.modal'
|
|||||||
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 { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
@@ -180,11 +181,9 @@ const ManageUsersPage = () => {
|
|||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const session = await getServerAuthSession(ctx);
|
const session = await getServerAuthSession(ctx);
|
||||||
|
const result = checkForSessionOrAskForLogin(ctx, session, () => session?.user.isAdmin == true);
|
||||||
if (!session?.user.isAdmin) {
|
if (result) {
|
||||||
return {
|
return result;
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { openCreateInviteModal } from '~/components/Manage/User/Invite/create-in
|
|||||||
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 { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
@@ -152,11 +153,9 @@ const useStyles = createStyles(() => ({
|
|||||||
|
|
||||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||||
const session = await getServerAuthSession(ctx);
|
const session = await getServerAuthSession(ctx);
|
||||||
|
const result = checkForSessionOrAskForLogin(ctx, session, () => session?.user.isAdmin == true);
|
||||||
if (!session?.user.isAdmin) {
|
if (result) {
|
||||||
return {
|
return result;
|
||||||
notFound: true,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import {
|
|||||||
GetServerSidePropsResult,
|
GetServerSidePropsResult,
|
||||||
PreviewData,
|
PreviewData,
|
||||||
} from 'next';
|
} from 'next';
|
||||||
|
|
||||||
import { Session } from 'next-auth';
|
import { Session } from 'next-auth';
|
||||||
|
|
||||||
import { ParsedUrlQuery } from 'querystring';
|
import { ParsedUrlQuery } from 'querystring';
|
||||||
|
|
||||||
export const checkForSessionOrAskForLogin = (
|
export const checkForSessionOrAskForLogin = (
|
||||||
@@ -13,7 +15,6 @@ export const checkForSessionOrAskForLogin = (
|
|||||||
accessCallback: () => boolean
|
accessCallback: () => boolean
|
||||||
): GetServerSidePropsResult<any> | undefined => {
|
): GetServerSidePropsResult<any> | undefined => {
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
console.log('detected logged out user!');
|
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
redirect: {
|
redirect: {
|
||||||
@@ -26,7 +27,10 @@ export const checkForSessionOrAskForLogin = (
|
|||||||
if (!accessCallback()) {
|
if (!accessCallback()) {
|
||||||
return {
|
return {
|
||||||
props: {},
|
props: {},
|
||||||
notFound: true,
|
redirect: {
|
||||||
|
destination: '/401',
|
||||||
|
permanent: false
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,3 +51,5 @@ export const manageNamespaces = [
|
|||||||
export const loginNamespaces = ['authentication/login'];
|
export const loginNamespaces = ['authentication/login'];
|
||||||
|
|
||||||
export const pageNotFoundNamespaces = ['layout/errors/not-found'];
|
export const pageNotFoundNamespaces = ['layout/errors/not-found'];
|
||||||
|
|
||||||
|
export const pageAccessDeniedNamespaces = ['layout/errors/access-denied'];
|
||||||
|
|||||||
Reference in New Issue
Block a user