Split server-only and client-only code in authenticated-container.tsx (#609)

This commit is contained in:
Kamil Kisiela 2022-11-07 17:38:49 +01:00 committed by GitHub
parent 51cb4bb412
commit 6a0c77ae59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 108 additions and 92 deletions

View file

@ -1,7 +1,8 @@
import { ReactElement } from 'react';
import { gql, useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import { SchemaExplorerFilter } from '@/components/target/explorer/filter';
import { GraphQLObjectTypeComponent } from '@/components/target/explorer/object-type';

View file

@ -1,7 +1,8 @@
import { ReactElement } from 'react';
import { DocumentType, gql, useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import {
GraphQLEnumTypeComponent,

View file

@ -5,7 +5,8 @@ import { VscBug, VscDiff, VscListFlat } from 'react-icons/vsc';
import reactStringReplace from 'react-string-replace';
import { useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { Label } from '@/components/common';
import { TargetLayout } from '@/components/layouts';
import {

View file

@ -1,4 +1,4 @@
import { withSessionProtection } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import HistoryPage from '../history';
export const getServerSideProps = withSessionProtection();

View file

@ -13,7 +13,8 @@ import { VscClose } from 'react-icons/vsc';
import { gql, useMutation, useQuery } from 'urql';
import { useDebouncedCallback } from 'use-debounce';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import { MarkAsValid } from '@/components/target/history/MarkAsValid';
import { Button, DataWrapper, GraphQLBlock, noSchema, Title } from '@/components/v2';

View file

@ -2,7 +2,8 @@ import { ReactElement, useCallback, useState } from 'react';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import { GraphiQL } from 'graphiql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import { Button, Title } from '@/components/v2';
import { HiveLogo, Link2Icon } from '@/components/v2/icon';

View file

@ -6,7 +6,8 @@ import { formatISO, subDays, subHours, subMinutes } from 'date-fns';
import { VscChevronDown } from 'react-icons/vsc';
import { useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import { OperationsFilterTrigger } from '@/components/target/operations/Filters';
import { OperationsList } from '@/components/target/operations/List';

View file

@ -6,7 +6,8 @@ import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { TargetLayout } from '@/components/layouts';
import { SchemaEditor } from '@/components/schema-editor';
import { Button, Card, Checkbox, Heading, Input, Switch, Table, Tag, TimeAgo, Title } from '@/components/v2';

View file

@ -1,7 +1,8 @@
import { ReactElement, useCallback, useState } from 'react';
import { useMutation, useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { ProjectLayout } from '@/components/layouts';
import { Button, Card, Checkbox, Heading, Table, Tag, Title } from '@/components/v2';
import { CreateAlertModal, CreateChannelModal } from '@/components/v2/modals';

View file

@ -3,7 +3,8 @@ import NextLink from 'next/link';
import clsx from 'clsx';
import { useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { ProjectLayout } from '@/components/layouts';
import { Activities, Badge, Button, Card, DropdownMenu, EmptyList, Heading, TimeAgo, Title } from '@/components/v2';
import { LinkIcon, MoreIcon, SettingsIcon } from '@/components/v2/icon';

View file

@ -4,7 +4,8 @@ import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { ProjectLayout } from '@/components/layouts';
import { ExternalCompositionSettings } from '@/components/project/settings/external-composition';
import { Button, Card, Heading, Input, Link, Select, Spinner, Tag, Title } from '@/components/v2';

View file

@ -3,7 +3,8 @@ import NextLink from 'next/link';
import { onlyText } from 'react-children-utilities';
import { useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { OrganizationLayout } from '@/components/layouts';
import { Activities, Button, Card, DropdownMenu, EmptyList, Heading, Skeleton, TimeAgo, Title } from '@/components/v2';
import { getActivity } from '@/components/v2/activities';

View file

@ -4,7 +4,8 @@ import { useFormik } from 'formik';
import { DocumentType, gql, useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { OrganizationLayout } from '@/components/layouts';
import { Avatar, Button, Card, Checkbox, DropdownMenu, Input, Title } from '@/components/v2';
import { CopyIcon, KeyIcon, MoreIcon, SettingsIcon, TrashIcon } from '@/components/v2/icon';

View file

@ -3,7 +3,8 @@ import { useFormik } from 'formik';
import { gql, useMutation, useQuery } from 'urql';
import * as Yup from 'yup';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { OrganizationLayout } from '@/components/layouts';
import { OIDCIntegrationSection } from '@/components/organization/settings/oidc-integration-section';
import { Button, Card, Heading, Input, Spinner, Tag, Title } from '@/components/v2';

View file

@ -3,7 +3,8 @@ import dynamic from 'next/dynamic';
import { Stat, StatHelpText, StatLabel, StatNumber } from '@chakra-ui/react';
import { endOfMonth, startOfMonth } from 'date-fns';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { OrganizationLayout } from '@/components/layouts';
import { BillingView } from '@/components/organization/billing/Billing';
import { CurrencyFormatter } from '@/components/organization/billing/helpers';

View file

@ -4,7 +4,8 @@ import { Stat, StatHelpText, StatLabel, StatNumber } from '@chakra-ui/react';
import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
import { useMutation, useQuery } from 'urql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { Section } from '@/components/common';
import { DataWrapper, QueryError } from '@/components/common/DataWrapper';
import { OrganizationLayout } from '@/components/layouts';

View file

@ -6,7 +6,8 @@ import { DataWrapper } from '@/components/common/DataWrapper';
import { useRouteSelector } from '@/lib/hooks/use-route-selector';
import Cookies from 'cookies';
import { LAST_VISITED_ORG_KEY } from '@/constants';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
import { createTRPCClient } from '@trpc/client';
import type { InternalApi } from '@hive/server';
import { env } from '@/env/backend';
@ -14,10 +15,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
import Session from 'supertokens-node/recipe/session';
import { writeLastVisitedOrganization } from '@/lib/cookies';
export async function getSuperTokensUserIdFromRequest(
req: NextApiRequest,
res: NextApiResponse
): Promise<string | null> {
async function getSuperTokensUserIdFromRequest(req: NextApiRequest, res: NextApiResponse): Promise<string | null> {
const session = await Session.getSession(req, res, { sessionRequired: false });
return session?.getUserId() ?? null;
}

View file

@ -7,7 +7,8 @@ import { Button } from '@chakra-ui/react';
import { Title } from '@/components/common';
import { DataWrapper } from '@/components/common/DataWrapper';
import { useNotifications } from '@/lib/hooks/use-notifications';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
const Center = tw.div`w-full h-full flex flex-row items-center justify-center`;

View file

@ -5,7 +5,8 @@ import { VscChevronDown } from 'react-icons/vsc';
import { AdminStats, Filters } from '@/components/admin/AdminStats';
import { Page } from '@/components/common';
import { DATE_RANGE_OPTIONS } from '@/components/common/TimeFilter';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
function Manage() {
const [last, setLast] = React.useState(30);

View file

@ -4,7 +4,8 @@ import * as Yup from 'yup';
import { Avatar, Button, SubHeader, Heading, Input, Tabs, Title } from '@/components/v2';
import { MeDocument } from '@/graphql';
import { authenticated, withSessionProtection } from '@/components/authenticated-container';
import { authenticated } from '@/components/authenticated-container';
import { withSessionProtection } from '@/lib/supertokens/guard';
const UpdateMeMutation = gql(/* GraphQL */ `
mutation updateMe($input: UpdateMeInput!) {

View file

@ -1,10 +1,7 @@
import React from 'react';
import type { ReactNode } from 'react';
import { captureException } from '@sentry/nextjs';
import { Header } from './v2';
import { HiveStripeWrapper } from '@/lib/billing/stripe';
import type { GetServerSideProps } from 'next';
import { SessionContainerInterface } from 'supertokens-node/lib/build/recipe/session/types';
import Session, { SessionAuth } from 'supertokens-auth-react/recipe/session';
import { useRouter } from 'next/router';
@ -57,66 +54,3 @@ export const authenticated =
</AuthenticatedContainer>
);
};
export const serverSidePropsSessionHandling = async (context: Parameters<GetServerSideProps>[0]) => {
const { backendConfig } = await import('@/config/supertokens/backend');
const SupertokensNode = await import('supertokens-node');
const Session = await import('supertokens-node/recipe/session');
SupertokensNode.init(backendConfig());
let session: SessionContainerInterface | undefined;
try {
session = await Session.getSession(context.req, context.res, { sessionRequired: false });
// TODO: better error decoding :)
} catch (err: any) {
// Check whether the email is already verified.
// If it is not then we need to redirect to the email verification page - which will trigger the email sending.
if (err.type === Session.Error.INVALID_CLAIMS) {
return {
redirect: {
destination: '/auth/verify-email',
permanent: false,
},
};
}
if (err.type === Session.Error.TRY_REFRESH_TOKEN) {
return { props: { fromSupertokens: 'needs-refresh' } };
}
captureException(err);
throw err;
}
if (session === undefined) {
return {
redirect: {
destination: `/auth?redirectToPath=${encodeURIComponent(context.resolvedUrl)}`,
permanent: false,
},
};
}
return null;
};
function defaultHandler() {
return Promise.resolve({ props: {} });
}
/**
* Utility for protecting a server side props function with session handling.
* Redirects user to the login page in case there is no session.
*/
export function withSessionProtection(handlerFn: GetServerSideProps = defaultHandler) {
const getServerSideProps: GetServerSideProps = async context => {
const result = await serverSidePropsSessionHandling(context);
if (result) {
return result;
}
return handlerFn(context);
};
return getServerSideProps;
}

View file

@ -3,7 +3,7 @@ import { superTokensNextWrapper } from 'supertokens-node/nextjs';
import { verifySession } from 'supertokens-node/recipe/session/framework/express';
import type { NextApiRequest, NextApiResponse } from 'next';
import { backendConfig } from '@/config/supertokens/backend';
import { SessionContainerInterface } from 'supertokens-node/lib/build/recipe/session/types';
import type { SessionContainerInterface } from 'supertokens-node/lib/build/recipe/session/types';
supertokens.init(backendConfig());

View file

@ -0,0 +1,66 @@
import { captureException } from '@sentry/nextjs';
import type { GetServerSideProps } from 'next';
import type { SessionContainerInterface } from 'supertokens-node/lib/build/recipe/session/types';
export const serverSidePropsSessionHandling = async (context: Parameters<GetServerSideProps>[0]) => {
const { backendConfig } = await import('@/config/supertokens/backend');
const SupertokensNode = await import('supertokens-node');
const Session = await import('supertokens-node/recipe/session');
SupertokensNode.init(backendConfig());
let session: SessionContainerInterface | undefined;
try {
session = await Session.getSession(context.req, context.res, { sessionRequired: false });
// TODO: better error decoding :)
} catch (err: any) {
// Check whether the email is already verified.
// If it is not then we need to redirect to the email verification page - which will trigger the email sending.
if (err.type === Session.Error.INVALID_CLAIMS) {
return {
redirect: {
destination: '/auth/verify-email',
permanent: false,
},
};
}
if (err.type === Session.Error.TRY_REFRESH_TOKEN) {
return { props: { fromSupertokens: 'needs-refresh' } };
}
captureException(err);
throw err;
}
if (session === undefined) {
return {
redirect: {
destination: `/auth?redirectToPath=${encodeURIComponent(context.resolvedUrl)}`,
permanent: false,
},
};
}
return null;
};
function defaultHandler() {
return Promise.resolve({ props: {} });
}
/**
* Utility for protecting a server side props function with session handling.
* Redirects user to the login page in case there is no session.
*/
export function withSessionProtection(handlerFn: GetServerSideProps = defaultHandler) {
const getServerSideProps: GetServerSideProps = async context => {
const result = await serverSidePropsSessionHandling(context);
if (result) {
return result;
}
return handlerFn(context);
};
return getServerSideProps;
}

View file

@ -1,5 +1,5 @@
import { env } from '@/env/backend';
import { TypeProvider } from 'supertokens-node/recipe/thirdpartyemailpassword';
import type { TypeProvider } from 'supertokens-node/recipe/thirdpartyemailpassword';
import zod from 'zod';
type OktaConfig = Exclude<typeof env['auth']['okta'], null>;