Skip to content

Commit

Permalink
feat: update supertokens (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
exKAZUu authored Jan 12, 2024
1 parent 6322ee7 commit df11fe0
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 235 deletions.
20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,37 @@
"@chakra-ui/react": "2.8.2",
"@emotion/react": "11.11.3",
"@emotion/styled": "11.11.0",
"@prisma/client": "5.7.1",
"@prisma/client": "5.8.0",
"@tanstack/react-query": "5.17.9",
"@willbooster/shared-lib-react": "3.0.5",
"framer-motion": "10.17.9",
"framer-motion": "10.18.0",
"next": "14.0.4",
"nextjs-cors": "2.2.0",
"pino": "8.17.2",
"pino-pretty": "10.3.1",
"pm2": "5.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "4.12.0",
"react-icons": "5.0.1",
"supertokens-auth-react": "0.36.1",
"supertokens-node": "16.6.8",
"supertokens-node": "16.7.1",
"supertokens-web-js": "0.8.0",
"zod": "3.22.4",
"zod-form-data": "2.0.2"
},
"devDependencies": {
"@chakra-ui/cli": "2.4.1",
"@types/cookie": "0.6.0",
"@types/eslint": "8.56.1",
"@types/eslint": "8.56.2",
"@types/micromatch": "4.0.6",
"@types/node": "20.10.7",
"@types/node": "20.11.0",
"@types/react-dom": "18.2.18",
"@typescript-eslint/eslint-plugin": "6.18.0",
"@typescript-eslint/parser": "6.18.0",
"@typescript-eslint/eslint-plugin": "6.18.1",
"@typescript-eslint/parser": "6.18.1",
"@willbooster/eslint-config-next": "1.1.0",
"@willbooster/prettier-config": "9.1.2",
"@willbooster/wb": "6.1.15",
"build-ts": "12.0.1",
"build-ts": "12.0.2",
"eslint": "8.56.0",
"eslint-config-next": "14.0.4",
"eslint-config-prettier": "9.1.0",
Expand All @@ -79,7 +79,7 @@
"micromatch": "4.0.5",
"pinst": "3.0.0",
"prettier": "3.1.1",
"prisma": "5.7.1",
"prisma": "5.8.0",
"sort-package-json": "2.6.0",
"typescript": "5.3.3"
},
Expand Down
6 changes: 3 additions & 3 deletions src/app/(withAuth)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { NextPage } from 'next';
import { redirect } from 'next/navigation';
import React, { Suspense } from 'react';

import { SessionAuthForNext } from '../../components/molecules/SessionAuthForNext';
import { SessionAuthForNextJs } from '../../components/molecules/SessionAuthForNextJs';
import { TryRefreshComponent } from '../../components/molecules/TryRefreshComponent';
import { DefaultFooter } from '../../components/organisms/DefaultFooter';
import { DefaultHeader } from '../../components/organisms/DefaultHeader';
Expand Down Expand Up @@ -35,7 +35,7 @@ const DefaultLayout: NextPage<LayoutProps> = async ({ children }) => {
* We pass in no children in this case to prevent hydration issues and still be able to redirect the
* user.
*/
<SessionAuthForNext />
<SessionAuthForNextJs />
) : (
/**
* This means that the session does not exist, but we have session tokens for the user.
Expand All @@ -51,7 +51,7 @@ const DefaultLayout: NextPage<LayoutProps> = async ({ children }) => {
*/
return (
<>
<SessionAuthForNext />
<SessionAuthForNextJs />

<DefaultHeader />
<Suspense fallback={<Spinner left="50%" position="fixed" top="50%" transform="translate(-50%, -50%)" />}>
Expand Down
27 changes: 0 additions & 27 deletions src/components/molecules/HomeClientComponent.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import { SessionAuth } from 'supertokens-auth-react/recipe/session';

type Props = Parameters<typeof SessionAuth>[0];

export const SessionAuthForNext: React.FC<Props> = (props: Props) => {
export const SessionAuthForNextJs: React.FC<Props> = (props: Props) => {
return <SessionAuth {...props} />;
};
89 changes: 16 additions & 73 deletions src/utils/session.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { cookies, headers } from 'next/headers';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { CollectingResponse, PreParsedRequest } from 'supertokens-node/framework/custom';
import type { SessionContainer, VerifySessionOptions } from 'supertokens-node/recipe/session';
import Session from 'supertokens-node/recipe/session';
import type { HTTPMethod } from 'supertokens-node/types';
import { getSSRSession } from 'supertokens-node/nextjs';
import type { SessionContainer } from 'supertokens-node/recipe/session';

import { logger } from '../infrastructures/pino';
import { prisma } from '../infrastructures/prisma';
Expand All @@ -20,88 +16,35 @@ export interface SessionOnServerLayout {
* For example this will be true if email verification is required but the user has not verified their email.
*/
hasInvalidClaims: boolean;
baseResponse: CollectingResponse;
nextResponse?: NextResponse;
error: unknown | undefined;
}

export async function getNullableSessionOnServer(
req?: NextRequest,
options?: VerifySessionOptions
): Promise<SessionOnServerLayout> {
const baseRequest = getBaseRequest(req);

// Collecting response is a wrapper exposed by SuperTokens. In this case we are using an empty
// CollectingResponse when calling `getSession`. If the request contains valid session tokens
// the SuperTokens SDK will attach all the relevant tokens to the collecting response object which
// we can then use to return those session tokens in the final result (refer to `withSession` in this file)
const baseResponse = new CollectingResponse();
export async function getNullableSessionOnServer(): Promise<SessionOnServerLayout> {
let session: SessionContainer | undefined;
let hasToken = false;
let hasInvalidClaims = false;
let error: unknown | undefined = undefined;

try {
// `getSession` will throw if session is required and there is no valid session. You can use
// `options` to configure whether or not you want to require sessions when calling `getSSRSession`
const session = await Session.getSession(baseRequest, baseResponse, options);

// Update User table with Prisma
({ hasInvalidClaims, hasToken, session } = await getSSRSession(cookies().getAll(), headers()));
if (session) {
await upsertUserToPrisma(session.getUserId());
}

return {
session,
hasInvalidClaims: false,
hasToken: session !== undefined,
baseResponse,
};
} catch (error) {
if (Session.Error.isErrorFromSuperTokens(error)) {
return {
hasToken: error.type !== Session.Error.UNAUTHORISED,
hasInvalidClaims: error.type === Session.Error.INVALID_CLAIMS,
session: undefined,
baseResponse,
nextResponse: new NextResponse('Authentication required', {
status: error.type === Session.Error.INVALID_CLAIMS ? 403 : 401,
}),
};
} else {
throw error;
void upsertUserToPrisma(session.getUserId());
}
} catch (error_: unknown) {
error = error_;
}
return { session, hasToken, hasInvalidClaims, error };
}

export async function getNonNullableSessionOnServer(
req?: NextRequest,
options?: VerifySessionOptions
): Promise<SessionContainer> {
const baseRequest = getBaseRequest(req);
const baseResponse = new CollectingResponse();
const session = await Session.getSession(baseRequest, baseResponse, options);
export async function getNonNullableSessionOnServer(): Promise<SessionContainer> {
const { session } = await getSSRSession(cookies().getAll(), headers());
if (!session) {
throw new Error('Failed to get a session.');
}
void upsertUserToPrisma(session.getUserId());
return session;
}

function getBaseRequest(req: NextRequest | undefined): PreParsedRequest {
const query = req === undefined ? {} : Object.fromEntries(new URL(req.url).searchParams.entries());
const parsedCookies: Record<string, string> = Object.fromEntries(
(req === undefined ? cookies() : req.cookies).getAll().map((cookie) => [cookie.name, cookie.value])
);

// Pre parsed request is a wrapper exposed by SuperTokens. It is used as a helper to detect if the
// original request contains session tokens. We then use this pre parsed request to call `getSession`
// to check if there is a valid session.
return new PreParsedRequest({
method: req === undefined ? 'get' : (req.method as HTTPMethod),
url: req === undefined ? '' : req.url,
query,
headers: req === undefined ? headers() : req.headers,
cookies: parsedCookies,
getFormBody: () => req!.formData(),
getJSONBody: () => req!.json(),
});
}

const upsertedUserIds = new Set<string>();

async function upsertUserToPrisma(id: string): Promise<void> {
Expand Down
Loading

0 comments on commit df11fe0

Please sign in to comment.