From 255d04fd56689cda1ebb1918a5ddcacf4290e7f9 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Wed, 24 Jan 2024 11:37:20 +0200 Subject: [PATCH 1/7] feat: Add getInitialSessionAuthContext to compute SessionAuth context --- lib/ts/nextjs.ts | 25 ++++++++++++++++++++++++- lib/ts/types.ts | 11 +++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index f76a3f3e7..abb84b463 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -20,7 +20,7 @@ import { middleware, errorHandler as customErrorHandler, } from "./framework/custom"; -import { HTTPMethod } from "./types"; +import { HTTPMethod, SSRSessionContextType } from "./types"; import Session, { SessionContainer, VerifySessionOptions } from "./recipe/session"; import SessionRecipe from "./recipe/session/recipe"; import { getToken } from "./recipe/session/cookieAndHeaders"; @@ -230,6 +230,28 @@ export default class NextJS { return result; } + static async getInitialSessionAuthContext(session: SessionContainer | undefined): Promise { + if (session) { + return { + isContextFromSSR: true, + loading: false, + doesSessionExist: true, + accessTokenPayload: await session.getAccessTokenPayload(), + invalidClaims: [], + userId: await session.getUserId(), + } + } + + return { + isContextFromSSR: true, + loading: false, + doesSessionExist: false, + accessTokenPayload: {}, + invalidClaims: [], + userId: '', + } + } + static async withSession( req: NextRequest, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, @@ -405,5 +427,6 @@ export default class NextJS { export let superTokensNextWrapper = NextJS.superTokensNextWrapper; export let getAppDirRequestHandler = NextJS.getAppDirRequestHandler; export let getSSRSession = NextJS.getSSRSession; +export let getInitialSessionAuthContext = NextJS.getInitialSessionAuthContext; export let withSession = NextJS.withSession; export let withPreParsedRequestResponse = NextJS.withPreParsedRequestResponse; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 4fc906b82..a36875b5f 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -18,6 +18,7 @@ import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { TypeFramework } from "./framework/types"; import { RecipeLevelUser } from "./recipe/accountlinking/types"; +import { ClaimValidationError } from "./recipe/session/types"; import { BaseRequest } from "./framework"; export type AppInfo = { @@ -113,3 +114,13 @@ export type User = { // the recipeUserId can be converted to string from the RecipeUserId object type. toJson: () => any; }; + +export type SSRSessionContextType = { + isContextFromSSR: true; + loading: false; + doesSessionExist: boolean; + accessTokenPayload: any; + invalidClaims: ClaimValidationError[]; // TODO: Find if it's possible to compute invalidClaims on BE + userId?: string; + accessDeniedValidatorError?: ClaimValidationError; +} \ No newline at end of file From 032ed7792b58f9a4972b5de8ce199ff850a6e067 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Wed, 24 Jan 2024 11:42:50 +0200 Subject: [PATCH 2/7] improve maintainability --- lib/ts/nextjs.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index abb84b463..117c371ea 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -231,25 +231,22 @@ export default class NextJS { } static async getInitialSessionAuthContext(session: SessionContainer | undefined): Promise { - if (session) { - return { - isContextFromSSR: true, - loading: false, - doesSessionExist: true, - accessTokenPayload: await session.getAccessTokenPayload(), - invalidClaims: [], - userId: await session.getUserId(), - } - } - - return { + const initialContext = { isContextFromSSR: true, loading: false, doesSessionExist: false, accessTokenPayload: {}, invalidClaims: [], userId: '', + } as SSRSessionContextType + + if (session) { + initialContext.doesSessionExist = true + initialContext.accessTokenPayload = await session.getAccessTokenPayload() + initialContext.userId = await session.getUserId() } + + return initialContext; } static async withSession( From 91c50d0bf544beda6ab4ae4155e2b1717c8ec241 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Wed, 24 Jan 2024 12:09:39 +0200 Subject: [PATCH 3/7] run build-pretty --- lib/build/nextjs.d.ts | 3 +++ lib/build/nextjs.js | 19 ++++++++++++++++++- lib/build/types.d.ts | 10 ++++++++++ lib/ts/nextjs.ts | 10 +++++----- lib/ts/types.ts | 2 +- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/lib/build/nextjs.d.ts b/lib/build/nextjs.d.ts index 7b06b2936..0de2b5448 100644 --- a/lib/build/nextjs.d.ts +++ b/lib/build/nextjs.d.ts @@ -1,5 +1,6 @@ // @ts-nocheck import { CollectingResponse, PreParsedRequest } from "./framework/custom"; +import { SSRSessionContextType } from "./types"; import { SessionContainer, VerifySessionOptions } from "./recipe/session"; declare type PartialNextRequest = { method: string; @@ -37,6 +38,7 @@ export default class NextJS { hasToken: boolean; hasInvalidClaims: boolean; }>; + static getInitialSessionAuthContext(session: SessionContainer | undefined): Promise; static withSession( req: NextRequest, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, @@ -51,6 +53,7 @@ export default class NextJS { export declare let superTokensNextWrapper: typeof NextJS.superTokensNextWrapper; export declare let getAppDirRequestHandler: typeof NextJS.getAppDirRequestHandler; export declare let getSSRSession: typeof NextJS.getSSRSession; +export declare let getInitialSessionAuthContext: typeof NextJS.getInitialSessionAuthContext; export declare let withSession: typeof NextJS.withSession; export declare let withPreParsedRequestResponse: typeof NextJS.withPreParsedRequestResponse; export {}; diff --git a/lib/build/nextjs.js b/lib/build/nextjs.js index 8fcef6c53..86b984e5f 100644 --- a/lib/build/nextjs.js +++ b/lib/build/nextjs.js @@ -16,7 +16,7 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.withPreParsedRequestResponse = exports.withSession = exports.getSSRSession = exports.getAppDirRequestHandler = exports.superTokensNextWrapper = void 0; +exports.withPreParsedRequestResponse = exports.withSession = exports.getInitialSessionAuthContext = exports.getSSRSession = exports.getAppDirRequestHandler = exports.superTokensNextWrapper = void 0; /* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. * * This software is licensed under the Apache License, Version 2.0 (the @@ -178,6 +178,22 @@ class NextJS { result = __rest(_a, ["baseResponse", "nextResponse"]); return result; } + static async getInitialSessionAuthContext(session) { + const initialContext = { + isContextFromSSR: true, + loading: false, + doesSessionExist: false, + accessTokenPayload: {}, + invalidClaims: [], + userId: "", + }; + if (session) { + initialContext.doesSessionExist = true; + initialContext.accessTokenPayload = await session.getAccessTokenPayload(); + initialContext.userId = await session.getUserId(); + } + return initialContext; + } static async withSession(req, handler, options, userContext) { try { const query = Object.fromEntries(new URL(req.url).searchParams.entries()); @@ -322,5 +338,6 @@ exports.default = NextJS; exports.superTokensNextWrapper = NextJS.superTokensNextWrapper; exports.getAppDirRequestHandler = NextJS.getAppDirRequestHandler; exports.getSSRSession = NextJS.getSSRSession; +exports.getInitialSessionAuthContext = NextJS.getInitialSessionAuthContext; exports.withSession = NextJS.withSession; exports.withPreParsedRequestResponse = NextJS.withPreParsedRequestResponse; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 6b3e0e34b..182f3b8fa 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -4,6 +4,7 @@ import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { TypeFramework } from "./framework/types"; import { RecipeLevelUser } from "./recipe/accountlinking/types"; +import { ClaimValidationError } from "./recipe/session/types"; import { BaseRequest } from "./framework"; export declare type AppInfo = { appName: string; @@ -86,3 +87,12 @@ export declare type User = { })[]; toJson: () => any; }; +export declare type SSRSessionContextType = { + isContextFromSSR: true; + loading: false; + doesSessionExist: boolean; + accessTokenPayload: any; + invalidClaims: ClaimValidationError[]; + userId?: string; + accessDeniedValidatorError?: ClaimValidationError; +}; diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index 117c371ea..b925b430e 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -237,13 +237,13 @@ export default class NextJS { doesSessionExist: false, accessTokenPayload: {}, invalidClaims: [], - userId: '', - } as SSRSessionContextType + userId: "", + } as SSRSessionContextType; if (session) { - initialContext.doesSessionExist = true - initialContext.accessTokenPayload = await session.getAccessTokenPayload() - initialContext.userId = await session.getUserId() + initialContext.doesSessionExist = true; + initialContext.accessTokenPayload = await session.getAccessTokenPayload(); + initialContext.userId = await session.getUserId(); } return initialContext; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index a36875b5f..8490b6ddb 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -123,4 +123,4 @@ export type SSRSessionContextType = { invalidClaims: ClaimValidationError[]; // TODO: Find if it's possible to compute invalidClaims on BE userId?: string; accessDeniedValidatorError?: ClaimValidationError; -} \ No newline at end of file +}; From 50b07ca2458976103ea38a2b1922e287b9ba93c7 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Thu, 25 Jan 2024 10:23:28 +0200 Subject: [PATCH 4/7] minor type fix --- lib/ts/nextjs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index b925b430e..3c5ece1d3 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -231,14 +231,14 @@ export default class NextJS { } static async getInitialSessionAuthContext(session: SessionContainer | undefined): Promise { - const initialContext = { + const initialContext: SSRSessionContextType = { isContextFromSSR: true, loading: false, doesSessionExist: false, accessTokenPayload: {}, invalidClaims: [], userId: "", - } as SSRSessionContextType; + }; if (session) { initialContext.doesSessionExist = true; From a6511010eb39c7e2583a902a2e910dd0ca7bde1d Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Thu, 25 Jan 2024 13:32:35 +0200 Subject: [PATCH 5/7] remove unused fields --- lib/build/nextjs.js | 1 - lib/build/types.d.ts | 3 --- lib/ts/nextjs.ts | 1 - lib/ts/types.ts | 3 --- 4 files changed, 8 deletions(-) diff --git a/lib/build/nextjs.js b/lib/build/nextjs.js index 86b984e5f..e89b292fd 100644 --- a/lib/build/nextjs.js +++ b/lib/build/nextjs.js @@ -184,7 +184,6 @@ class NextJS { loading: false, doesSessionExist: false, accessTokenPayload: {}, - invalidClaims: [], userId: "", }; if (session) { diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 182f3b8fa..a3170a09d 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -4,7 +4,6 @@ import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { TypeFramework } from "./framework/types"; import { RecipeLevelUser } from "./recipe/accountlinking/types"; -import { ClaimValidationError } from "./recipe/session/types"; import { BaseRequest } from "./framework"; export declare type AppInfo = { appName: string; @@ -92,7 +91,5 @@ export declare type SSRSessionContextType = { loading: false; doesSessionExist: boolean; accessTokenPayload: any; - invalidClaims: ClaimValidationError[]; userId?: string; - accessDeniedValidatorError?: ClaimValidationError; }; diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index 3c5ece1d3..ff05e2318 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -236,7 +236,6 @@ export default class NextJS { loading: false, doesSessionExist: false, accessTokenPayload: {}, - invalidClaims: [], userId: "", }; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 8490b6ddb..36f64481c 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -18,7 +18,6 @@ import NormalisedURLDomain from "./normalisedURLDomain"; import NormalisedURLPath from "./normalisedURLPath"; import { TypeFramework } from "./framework/types"; import { RecipeLevelUser } from "./recipe/accountlinking/types"; -import { ClaimValidationError } from "./recipe/session/types"; import { BaseRequest } from "./framework"; export type AppInfo = { @@ -120,7 +119,5 @@ export type SSRSessionContextType = { loading: false; doesSessionExist: boolean; accessTokenPayload: any; - invalidClaims: ClaimValidationError[]; // TODO: Find if it's possible to compute invalidClaims on BE userId?: string; - accessDeniedValidatorError?: ClaimValidationError; }; From cbced55a89a14ae5c45ded162577a56becc24f39 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Thu, 25 Jan 2024 16:32:22 +0200 Subject: [PATCH 6/7] add test case --- test/nextjs.test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/nextjs.test.js b/test/nextjs.test.js index 2f0fd38ee..e434f629e 100644 --- a/test/nextjs.test.js +++ b/test/nextjs.test.js @@ -26,6 +26,7 @@ const { superTokensNextWrapper, withSession, getSSRSession, + getInitialSessionAuthContext, getAppDirRequestHandler, withPreParsedRequestResponse, } = require("../lib/build/nextjs"); @@ -774,6 +775,38 @@ describe(`Next.js App Router: ${printPath("[test/nextjs.test.js]")}`, function ( assert.equal(sessionContainer.hasToken, true); }); + it("getInitialSessionAuthContext", async function () { + const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); + + const authenticatedRequest = new NextRequest("http://localhost:3000/api/get-user", { + headers: { + Authorization: `Bearer ${tokens.access}`, + }, + }); + + let sessionContainer = await getSSRSession(authenticatedRequest.cookies.getAll(), authenticatedRequest.headers); + + let initialSessionAuthContext = await getInitialSessionAuthContext(sessionContainer.session); + + assert.equal(initialSessionAuthContext.loading, false); + assert.equal(initialSessionAuthContext.isContextFromSSR, true); + assert.equal(initialSessionAuthContext.doesSessionExist, true); + assert.equal(initialSessionAuthContext.userId, process.env.user); + assert.notEqual(initialSessionAuthContext.accessTokenPayload, {}); + + const unAuthenticatedRequest = new NextRequest("http://localhost:3000/api/get-user"); + + sessionContainer = await getSSRSession(unAuthenticatedRequest.cookies.getAll(), unAuthenticatedRequest.headers); + + initialSessionAuthContext = await getInitialSessionAuthContext(sessionContainer.session); + + assert.equal(initialSessionAuthContext.loading, false); + assert.equal(initialSessionAuthContext.isContextFromSSR, true); + assert.equal(initialSessionAuthContext.doesSessionExist, false); + assert.equal(initialSessionAuthContext.userId, ""); + assert.deepEqual(initialSessionAuthContext.accessTokenPayload, {}); + }); + it("withSession", async function () { const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); From 7de5b21f5742daf144928ef3cc8b7844a818a6f2 Mon Sep 17 00:00:00 2001 From: Alex Buzin Date: Fri, 26 Jan 2024 10:17:03 +0200 Subject: [PATCH 7/7] remove optional from userId --- lib/ts/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 36f64481c..ee9655f3f 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -119,5 +119,5 @@ export type SSRSessionContextType = { loading: false; doesSessionExist: boolean; accessTokenPayload: any; - userId?: string; + userId: string; };