From f22b3e2d1fada8cee9573f8143edec10c0f92a14 Mon Sep 17 00:00:00 2001 From: rishabhpoddar Date: Fri, 4 Oct 2024 18:39:35 +0530 Subject: [PATCH 1/4] several fixes and refactoring --- lib/build/customFramework.d.ts | 61 ++++----- lib/build/customFramework.js | 171 ++++++++----------------- lib/build/nextjs.d.ts | 16 +-- lib/build/nextjs.js | 61 ++------- lib/ts/customFramework.ts | 224 ++++++++++++--------------------- lib/ts/nextjs.ts | 77 +++--------- test/with-typescript/index.ts | 4 +- 7 files changed, 187 insertions(+), 427 deletions(-) diff --git a/lib/build/customFramework.d.ts b/lib/build/customFramework.d.ts index 7da61beb0..d7abeb160 100644 --- a/lib/build/customFramework.d.ts +++ b/lib/build/customFramework.d.ts @@ -7,7 +7,6 @@ import { CollectingResponse, PreParsedRequest } from "./framework/custom"; import { SessionContainer, VerifySessionOptions } from "./recipe/session"; import { JWTPayload } from "jose"; -export declare type GetCookieFn = (req: T) => Record; export interface ParsableRequest { url: string; method: string; @@ -15,28 +14,13 @@ export interface ParsableRequest { formData: () => Promise; json: () => Promise; } -export declare function createPreParsedRequest( - request: RequestType, - getCookieFn?: GetCookieFn -): PreParsedRequest; -export declare function getCookieFromRequest(request: ParsableRequest): Record; -export declare function getQueryFromRequest(request: ParsableRequest): Record; -export declare function getHandleCall( - res: typeof Response, - stMiddleware: any -): (req: T) => Promise; -export declare function handleAuthAPIRequest(CustomResponse: typeof Response): (req: Request) => Promise; -export declare function getSessionDetails( - preParsedRequest: PreParsedRequest, - options?: VerifySessionOptions, - userContext?: Record -): Promise<{ - session: SessionContainer | undefined; - hasToken: boolean; - hasInvalidClaims: boolean; - baseResponse: CollectingResponse; - response?: Response; -}>; +export declare function getCookieFromRequest( + request: RequestType +): Record; +export declare function getQueryFromRequest( + request: RequestType +): Record; +export declare function handleAuthAPIRequest(): (req: Request) => Promise; /** * A helper function to retrieve session details on the server side. * @@ -44,9 +28,15 @@ export declare function getSessionDetails( * because getSession can update the access token. These updated tokens would not be * propagated to the client side, as request interceptors do not run on the server side. */ -export declare function getSessionForSSR( - request: Request, - jwks?: any +export declare function getSessionForSSR( + request: RequestType +): Promise<{ + accessTokenPayload: JWTPayload | undefined; + hasToken: boolean; + error: Error | undefined; +}>; +export declare function getSessionForSSRUsingAccessToken( + accessToken: string | undefined ): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; @@ -59,15 +49,12 @@ export declare function withSession< request: RequestType, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, - userContext?: Record, - getCookieFn?: GetCookieFn + userContext?: Record +): Promise; +export declare function withPreParsedRequestResponse< + RequestType extends ParsableRequest = Request, + ResponseType extends Response = Response +>( + req: RequestType, + handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise ): Promise; -export declare function addCookies( - baseResponse: CollectingResponse, - userResponse: UserResponseType -): UserResponseType; -export declare function handleError( - err: any, - baseRequest: PreParsedRequest, - baseResponse: CollectingResponse -): Promise; diff --git a/lib/build/customFramework.js b/lib/build/customFramework.js index 72515b298..25064135d 100644 --- a/lib/build/customFramework.js +++ b/lib/build/customFramework.js @@ -10,24 +10,22 @@ var __importDefault = return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.handleError = exports.addCookies = exports.withSession = exports.getSessionForSSR = exports.getSessionDetails = exports.handleAuthAPIRequest = exports.getHandleCall = exports.getQueryFromRequest = exports.getCookieFromRequest = exports.createPreParsedRequest = void 0; +exports.withPreParsedRequestResponse = exports.withSession = exports.getSessionForSSRUsingAccessToken = exports.getSessionForSSR = exports.handleAuthAPIRequest = exports.getQueryFromRequest = exports.getCookieFromRequest = void 0; const cookie_1 = require("cookie"); const custom_1 = require("./framework/custom"); const session_1 = __importDefault(require("./recipe/session")); const recipe_1 = __importDefault(require("./recipe/session/recipe")); -const constants_1 = require("./recipe/session/constants"); -const cookieAndHeaders_1 = require("./recipe/session/cookieAndHeaders"); const jwt_1 = require("./recipe/session/jwt"); const accessToken_1 = require("./recipe/session/accessToken"); const combinedRemoteJWKSet_1 = require("./combinedRemoteJWKSet"); -function createPreParsedRequest(request, getCookieFn = getCookieFromRequest) { +function createPreParsedRequest(request) { /** * This helper function can take any `Request` type of object * and parse the details into an equivalent PreParsedRequest * that can be used with the custom framework helpers. */ return new custom_1.PreParsedRequest({ - cookies: getCookieFn(request), + cookies: getCookieFromRequest(request), url: request.url, method: request.method, query: getQueryFromRequest(request), @@ -40,7 +38,6 @@ function createPreParsedRequest(request, getCookieFn = getCookieFromRequest) { }, }); } -exports.createPreParsedRequest = createPreParsedRequest; function getCookieFromRequest(request) { /** * This function will extract the cookies from any `Request` @@ -75,95 +72,34 @@ exports.getQueryFromRequest = getQueryFromRequest; function getAccessToken(request) { return getCookieFromRequest(request)["sAccessToken"]; } -function getHandleCall(res, stMiddleware) { +function getHandleCall(stMiddleware) { return async function handleCall(req) { - const baseResponse = new custom_1.CollectingResponse(); - const { handled, error } = await stMiddleware(req, baseResponse); - if (error) { - throw error; - } - if (!handled) { - return new res("Not found", { status: 404 }); - } - for (const respCookie of baseResponse.cookies) { - baseResponse.headers.append( - "Set-Cookie", - cookie_1.serialize(respCookie.key, respCookie.value, { - domain: respCookie.domain, - expires: new Date(respCookie.expires), - httpOnly: respCookie.httpOnly, - path: respCookie.path, - sameSite: respCookie.sameSite, - secure: respCookie.secure, - }) - ); - } - return new res(baseResponse.body, { - headers: baseResponse.headers, - status: baseResponse.statusCode, + return withPreParsedRequestResponse(req, async (baseRequest, baseResponse) => { + const { handled, error } = await stMiddleware(baseRequest, baseResponse); + if (error) { + throw error; + } + if (!handled) { + return new Response("Not found", { status: 404 }); + } + return new Response(baseResponse.body, { + headers: baseResponse.headers, + status: baseResponse.statusCode, + }); }); }; } -exports.getHandleCall = getHandleCall; -function handleAuthAPIRequest(CustomResponse) { +function handleAuthAPIRequest() { /** * Util function to handle all calls by intercepting them, calling * Supertokens middleware and then accordingly returning. */ const stMiddleware = custom_1.middleware((req) => { - return createPreParsedRequest(req); + return req; }); - return getHandleCall(CustomResponse, stMiddleware); + return getHandleCall(stMiddleware); } exports.handleAuthAPIRequest = handleAuthAPIRequest; -async function getSessionDetails(preParsedRequest, options, userContext) { - const baseResponse = new custom_1.CollectingResponse(); - // Possible interop issue. - const recipe = recipe_1.default.getInstanceOrThrowError(); - const tokenTransferMethod = recipe.config.getTokenTransferMethod({ - req: preParsedRequest, - forCreateNewSession: false, - userContext: userContext, - }); - const transferMethods = - tokenTransferMethod === "any" ? constants_1.availableTokenTransferMethods : [tokenTransferMethod]; - const hasToken = transferMethods.some((transferMethod) => { - const token = cookieAndHeaders_1.getToken(preParsedRequest, "access", transferMethod); - if (!token) { - return false; - } - try { - jwt_1.parseJWTWithoutSignatureVerification(token); - return true; - } catch (_a) { - return false; - } - }); - try { - const session = await session_1.default.getSession(preParsedRequest, baseResponse, options, userContext); - return { - session, - hasInvalidClaims: false, - hasToken, - baseResponse, - }; - } catch (err) { - if (session_1.default.Error.isErrorFromSuperTokens(err)) { - return { - hasToken, - hasInvalidClaims: err.type === session_1.default.Error.INVALID_CLAIMS, - session: undefined, - baseResponse, - response: new Response("Authentication required", { - status: err.type === session_1.default.Error.INVALID_CLAIMS ? 403 : 401, - }), - }; - } else { - throw err; - } - } -} -exports.getSessionDetails = getSessionDetails; /** * A helper function to retrieve session details on the server side. * @@ -171,52 +107,53 @@ exports.getSessionDetails = getSessionDetails; * because getSession can update the access token. These updated tokens would not be * propagated to the client side, as request interceptors do not run on the server side. */ -async function getSessionForSSR(request, jwks) { - const accessToken = getAccessToken(request); +async function getSessionForSSR(request) { + return getSessionForSSRUsingAccessToken(getAccessToken(request)); +} +exports.getSessionForSSR = getSessionForSSR; +async function getSessionForSSRUsingAccessToken(accessToken) { const hasToken = !!accessToken; - // NOTE: We are accepting jwks as a parameter so that this function can - // be effectively tested. - // There's more details on why this is needed in the tests file where this - // function is being tested. - let jwksToUse = jwks; - if (!jwks) { - const sessionRecipe = recipe_1.default.getInstanceOrThrowError(); - jwksToUse = combinedRemoteJWKSet_1.getCombinedJWKS(sessionRecipe.config); - } try { - if (accessToken) { - const tokenInfo = jwt_1.parseJWTWithoutSignatureVerification(accessToken); - const decoded = await accessToken_1.getInfoFromAccessToken(tokenInfo, jwksToUse, false); - return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; - } - return { accessTokenPayload: undefined, hasToken, error: undefined }; - } catch (error) { - if (error instanceof Error && error.name === "JWTExpired") { + const sessionRecipe = recipe_1.default.getInstanceOrThrowError(); + const jwksToUse = combinedRemoteJWKSet_1.getCombinedJWKS(sessionRecipe.config); + try { + if (accessToken) { + const tokenInfo = jwt_1.parseJWTWithoutSignatureVerification(accessToken); + const decoded = await accessToken_1.getInfoFromAccessToken(tokenInfo, jwksToUse, false); + return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; + } + return { accessTokenPayload: undefined, hasToken, error: undefined }; + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: undefined }; } + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: error }; } } -exports.getSessionForSSR = getSessionForSSR; -async function withSession(request, handler, options, userContext, getCookieFn = getCookieFromRequest) { +exports.getSessionForSSRUsingAccessToken = getSessionForSSRUsingAccessToken; +async function withSession(request, handler, options, userContext) { try { - const baseRequest = createPreParsedRequest(request, getCookieFn); - const { session, response, baseResponse } = await getSessionDetails(baseRequest, options, userContext); - if (response !== undefined) { - return response; - } - let userResponse; - try { - userResponse = await handler(undefined, session); - } catch (err) { - userResponse = await handleError(err, baseRequest, baseResponse); - } - return addCookies(baseResponse, userResponse); + return await withPreParsedRequestResponse(request, async (baseRequest, baseResponse) => { + const session = await session_1.default.getSession(baseRequest, baseResponse, options, userContext); + return handler(undefined, session); + }); } catch (error) { return await handler(error, undefined); } } exports.withSession = withSession; +async function withPreParsedRequestResponse(req, handler) { + let baseRequest = createPreParsedRequest(req); + let baseResponse = new custom_1.CollectingResponse(); + let userResponse; + try { + userResponse = await handler(baseRequest, baseResponse); + } catch (err) { + userResponse = await handleError(err, baseRequest, baseResponse); + } + return addCookies(baseResponse, userResponse); +} +exports.withPreParsedRequestResponse = withPreParsedRequestResponse; function addCookies(baseResponse, userResponse) { /** * Add cookies to the userResponse passed by copying it from the baseResponse. @@ -257,7 +194,6 @@ function addCookies(baseResponse, userResponse) { } return userResponse; } -exports.addCookies = addCookies; async function handleError(err, baseRequest, baseResponse) { await custom_1.errorHandler()(err, baseRequest, baseResponse, (errorHandlerError) => { if (errorHandlerError) { @@ -270,4 +206,3 @@ async function handleError(err, baseRequest, baseResponse) { headers: baseResponse.headers, }); } -exports.handleError = handleError; diff --git a/lib/build/nextjs.d.ts b/lib/build/nextjs.d.ts index 79ba7a644..18adb76ac 100644 --- a/lib/build/nextjs.d.ts +++ b/lib/build/nextjs.d.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { CollectingResponse, PreParsedRequest } from "./framework/custom"; import { SessionContainer, VerifySessionOptions } from "./recipe/session"; -import { GetCookieFn } from "./customFramework"; +import { JWTPayload } from "jose"; declare type PartialNextRequest = { method: string; url: string; @@ -21,22 +21,16 @@ export default class NextJS { request: any, response: any ): Promise; - static getCookieExtractor(): GetCookieFn; - static getAppDirRequestHandler( - NextResponse: typeof Response - ): (req: T) => Promise; + static getAppDirRequestHandler(): (req: Request) => Promise; static getSSRSession( cookies: Array<{ name: string; value: string; - }>, - headers: Headers, - options?: VerifySessionOptions, - userContext?: Record + }> ): Promise<{ - session: SessionContainer | undefined; + accessTokenPayload: JWTPayload | undefined; hasToken: boolean; - hasInvalidClaims: boolean; + error: Error | undefined; }>; static withSession( req: NextRequest, diff --git a/lib/build/nextjs.js b/lib/build/nextjs.js index 019006d3d..6ced5eff5 100644 --- a/lib/build/nextjs.js +++ b/lib/build/nextjs.js @@ -13,22 +13,9 @@ * License for the specific language governing permissions and limitations * under the License. */ -var __rest = - (this && this.__rest) || - function (s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; - } - return t; - }; Object.defineProperty(exports, "__esModule", { value: true }); exports.withPreParsedRequestResponse = exports.withSession = exports.getSSRSession = exports.getAppDirRequestHandler = exports.superTokensNextWrapper = void 0; const express_1 = require("./framework/express"); -const utils_1 = require("./utils"); -const custom_1 = require("./framework/custom"); const customFramework_1 = require("./customFramework"); function next(request, response, resolve, reject) { return async function (middlewareError) { @@ -65,48 +52,22 @@ class NextJS { } }); } - static getCookieExtractor() { - return (req) => Object.fromEntries(req.cookies.getAll().map((cookie) => [cookie.name, cookie.value])); - } - static getAppDirRequestHandler(NextResponse) { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - const stMiddleware = custom_1.middleware((req) => { - return customFramework_1.createPreParsedRequest(req, getCookieFromNextReq); - }); - return customFramework_1.getHandleCall(NextResponse, stMiddleware); + static getAppDirRequestHandler() { + return customFramework_1.handleAuthAPIRequest(); } - static async getSSRSession(cookies, headers, options, userContext) { - // Create an instance of PreParsedRequest without access to the actual - // request body and inject cookies into it. - let baseRequest = new custom_1.PreParsedRequest({ - method: "get", - url: "", - query: {}, - headers: headers, - cookies: Object.fromEntries(cookies.map((cookie) => [cookie.name, cookie.value])), - getFormBody: async () => [], - getJSONBody: async () => [], - }); - const _a = await customFramework_1.getSessionDetails(baseRequest, options, utils_1.getUserContext(userContext)), - { baseResponse, response } = _a, - result = __rest(_a, ["baseResponse", "response"]); - return result; + static async getSSRSession(cookies) { + var _a; + let accessToken = + (_a = cookies.find((cookie) => cookie.name === "sAccessToken")) === null || _a === void 0 + ? void 0 + : _a.value; + return await customFramework_1.getSessionForSSRUsingAccessToken(accessToken); } static async withSession(req, handler, options, userContext) { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - return await customFramework_1.withSession(req, handler, options, userContext, getCookieFromNextReq); + return await customFramework_1.withSession(req, handler, options, userContext); } static async withPreParsedRequestResponse(req, handler) { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - let baseRequest = customFramework_1.createPreParsedRequest(req, getCookieFromNextReq); - let baseResponse = new custom_1.CollectingResponse(); - let userResponse; - try { - userResponse = await handler(baseRequest, baseResponse); - } catch (err) { - userResponse = await customFramework_1.handleError(err, baseRequest, baseResponse); - } - return customFramework_1.addCookies(baseResponse, userResponse); + return customFramework_1.withPreParsedRequestResponse(req, handler); } } exports.default = NextJS; diff --git a/lib/ts/customFramework.ts b/lib/ts/customFramework.ts index a51894568..36c273d26 100644 --- a/lib/ts/customFramework.ts +++ b/lib/ts/customFramework.ts @@ -8,15 +8,12 @@ import { serialize } from "cookie"; import { CollectingResponse, errorHandler, middleware, PreParsedRequest } from "./framework/custom"; import Session, { SessionContainer, VerifySessionOptions } from "./recipe/session"; import SessionRecipe from "./recipe/session/recipe"; -import { availableTokenTransferMethods } from "./recipe/session/constants"; -import { getToken } from "./recipe/session/cookieAndHeaders"; import { parseJWTWithoutSignatureVerification } from "./recipe/session/jwt"; import { JWTPayload } from "jose"; import { HTTPMethod } from "./types"; import { getInfoFromAccessToken } from "./recipe/session/accessToken"; import { getCombinedJWKS } from "./combinedRemoteJWKSet"; - -export type GetCookieFn = (req: T) => Record; +import { BaseRequest } from "./framework"; export interface ParsableRequest { url: string; @@ -26,17 +23,14 @@ export interface ParsableRequest { json: () => Promise; } -export function createPreParsedRequest( - request: RequestType, - getCookieFn: GetCookieFn = getCookieFromRequest -): PreParsedRequest { +function createPreParsedRequest(request: RequestType): PreParsedRequest { /** * This helper function can take any `Request` type of object * and parse the details into an equivalent PreParsedRequest * that can be used with the custom framework helpers. */ return new PreParsedRequest({ - cookies: getCookieFn(request), + cookies: getCookieFromRequest(request), url: request.url as string, method: request.method as HTTPMethod, query: getQueryFromRequest(request), @@ -50,7 +44,9 @@ export function createPreParsedRequest { +export function getCookieFromRequest( + request: RequestType +): Record { /** * This function will extract the cookies from any `Request` * type of object and return them to be usable with PreParsedRequest. @@ -67,7 +63,9 @@ export function getCookieFromRequest(request: ParsableRequest): Record { +export function getQueryFromRequest( + request: RequestType +): Record { /** * Helper function to extract query from any `Request` type of * object and return them to be usable with PreParsedRequest. @@ -81,112 +79,38 @@ export function getQueryFromRequest(request: ParsableRequest): Record(request: RequestType): string | undefined { return getCookieFromRequest(request)["sAccessToken"]; } -export function getHandleCall(res: typeof Response, stMiddleware: any) { - return async function handleCall(req: T) { - const baseResponse = new CollectingResponse(); - - const { handled, error } = await stMiddleware(req, baseResponse); - - if (error) { - throw error; - } - if (!handled) { - return new res("Not found", { status: 404 }); - } - - for (const respCookie of baseResponse.cookies) { - baseResponse.headers.append( - "Set-Cookie", - serialize(respCookie.key, respCookie.value, { - domain: respCookie.domain, - expires: new Date(respCookie.expires), - httpOnly: respCookie.httpOnly, - path: respCookie.path, - sameSite: respCookie.sameSite, - secure: respCookie.secure, - }) - ); - } - - return new res(baseResponse.body, { - headers: baseResponse.headers, - status: baseResponse.statusCode, +function getHandleCall(stMiddleware: any) { + return async function handleCall(req: RequestType) { + return withPreParsedRequestResponse(req, async (baseRequest, baseResponse) => { + const { handled, error } = await stMiddleware(baseRequest, baseResponse); + if (error) { + throw error; + } + if (!handled) { + return new Response("Not found", { status: 404 }); + } + return new Response(baseResponse.body, { + headers: baseResponse.headers, + status: baseResponse.statusCode, + }); }); }; } -export function handleAuthAPIRequest(CustomResponse: typeof Response) { +export function handleAuthAPIRequest() { /** * Util function to handle all calls by intercepting them, calling * Supertokens middleware and then accordingly returning. */ - const stMiddleware = middleware((req) => { - return createPreParsedRequest(req); - }); - - return getHandleCall(CustomResponse, stMiddleware); -} - -export async function getSessionDetails( - preParsedRequest: PreParsedRequest, - options?: VerifySessionOptions, - userContext?: Record -): Promise<{ - session: SessionContainer | undefined; - hasToken: boolean; - hasInvalidClaims: boolean; - baseResponse: CollectingResponse; - response?: Response; -}> { - const baseResponse = new CollectingResponse(); - // Possible interop issue. - const recipe = SessionRecipe.getInstanceOrThrowError(); - const tokenTransferMethod = recipe.config.getTokenTransferMethod({ - req: preParsedRequest, - forCreateNewSession: false, - userContext: userContext as any, - }); - const transferMethods = tokenTransferMethod === "any" ? availableTokenTransferMethods : [tokenTransferMethod]; - const hasToken = transferMethods.some((transferMethod) => { - const token = getToken(preParsedRequest, "access", transferMethod); - if (!token) { - return false; - } - try { - parseJWTWithoutSignatureVerification(token); - return true; - } catch { - return false; - } + const stMiddleware = middleware((req) => { + return req; }); - try { - const session = await Session.getSession(preParsedRequest, baseResponse, options, userContext); - return { - session, - hasInvalidClaims: false, - hasToken, - baseResponse, - }; - } catch (err) { - if (Session.Error.isErrorFromSuperTokens(err)) { - return { - hasToken, - hasInvalidClaims: err.type === Session.Error.INVALID_CLAIMS, - session: undefined, - baseResponse, - response: new Response("Authentication required", { - status: err.type === Session.Error.INVALID_CLAIMS ? 403 : 401, - }), - }; - } else { - throw err; - } - } + return getHandleCall(stMiddleware); } /** @@ -196,38 +120,39 @@ export async function getSessionDetails( * because getSession can update the access token. These updated tokens would not be * propagated to the client side, as request interceptors do not run on the server side. */ -export async function getSessionForSSR( - request: Request, - jwks?: any +export async function getSessionForSSR( + request: RequestType ): Promise<{ accessTokenPayload: JWTPayload | undefined; hasToken: boolean; error: Error | undefined; }> { - const accessToken = getAccessToken(request); - const hasToken = !!accessToken; + return getSessionForSSRUsingAccessToken(getAccessToken(request)); +} - // NOTE: We are accepting jwks as a parameter so that this function can - // be effectively tested. - // There's more details on why this is needed in the tests file where this - // function is being tested. - let jwksToUse = jwks; - if (!jwks) { +export async function getSessionForSSRUsingAccessToken( + accessToken: string | undefined +): Promise<{ + accessTokenPayload: JWTPayload | undefined; + hasToken: boolean; + error: Error | undefined; +}> { + const hasToken = !!accessToken; + try { const sessionRecipe = SessionRecipe.getInstanceOrThrowError(); - jwksToUse = getCombinedJWKS(sessionRecipe.config); - } + const jwksToUse = getCombinedJWKS(sessionRecipe.config); - try { - if (accessToken) { - const tokenInfo = parseJWTWithoutSignatureVerification(accessToken); - const decoded = await getInfoFromAccessToken(tokenInfo, jwksToUse, false); - return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; - } - return { accessTokenPayload: undefined, hasToken, error: undefined }; - } catch (error) { - if (error instanceof Error && error.name === "JWTExpired") { + try { + if (accessToken) { + const tokenInfo = parseJWTWithoutSignatureVerification(accessToken); + const decoded = await getInfoFromAccessToken(tokenInfo, jwksToUse, false); + return { accessTokenPayload: decoded.userData, hasToken, error: undefined }; + } + return { accessTokenPayload: undefined, hasToken, error: undefined }; + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: undefined }; } + } catch (error) { return { accessTokenPayload: undefined, hasToken, error: error as Error }; } } @@ -239,32 +164,39 @@ export async function withSession< request: RequestType, handler: (error: Error | undefined, session: SessionContainer | undefined) => Promise, options?: VerifySessionOptions, - userContext?: Record, - getCookieFn: GetCookieFn = getCookieFromRequest + userContext?: Record ): Promise { try { - const baseRequest = createPreParsedRequest(request, getCookieFn); - const { session, response, baseResponse } = await getSessionDetails(baseRequest, options, userContext); - - if (response !== undefined) { - return response as ResponseType; - } - - let userResponse: Response; - - try { - userResponse = await handler(undefined, session); - } catch (err) { - userResponse = await handleError(err, baseRequest, baseResponse); - } - - return addCookies(baseResponse, userResponse) as ResponseType; + return await withPreParsedRequestResponse(request, async (baseRequest, baseResponse) => { + const session = await Session.getSession(baseRequest, baseResponse, options, userContext); + return handler(undefined, session); + }); } catch (error) { return await handler(error as Error, undefined); } } -export function addCookies( +export async function withPreParsedRequestResponse< + RequestType extends ParsableRequest = Request, + ResponseType extends Response = Response +>( + req: RequestType, + handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise +): Promise { + let baseRequest = createPreParsedRequest(req); + let baseResponse = new CollectingResponse(); + let userResponse: ResponseType; + + try { + userResponse = await handler(baseRequest, baseResponse); + } catch (err) { + userResponse = await handleError(err, baseRequest, baseResponse); + } + + return addCookies(baseResponse, userResponse); +} + +function addCookies( baseResponse: CollectingResponse, userResponse: UserResponseType ): UserResponseType { @@ -311,7 +243,7 @@ export function addCookies( return userResponse; } -export async function handleError( +async function handleError( err: any, baseRequest: PreParsedRequest, baseResponse: CollectingResponse diff --git a/lib/ts/nextjs.ts b/lib/ts/nextjs.ts index 38fe2d3ca..1688175e8 100644 --- a/lib/ts/nextjs.ts +++ b/lib/ts/nextjs.ts @@ -14,17 +14,14 @@ */ import { errorHandler } from "./framework/express"; -import { getUserContext } from "./utils"; -import { CollectingResponse, PreParsedRequest, middleware } from "./framework/custom"; +import { CollectingResponse, PreParsedRequest } from "./framework/custom"; import { SessionContainer, VerifySessionOptions } from "./recipe/session"; +import { JWTPayload } from "jose"; import { - addCookies, - createPreParsedRequest, - GetCookieFn, - getHandleCall, - getSessionDetails, - handleError, + withPreParsedRequestResponse as customWithPreParsedRequestResponse, + getSessionForSSRUsingAccessToken, withSession as customWithSession, + handleAuthAPIRequest, } from "./customFramework"; function next( @@ -88,47 +85,19 @@ export default class NextJS { }); } - static getCookieExtractor(): GetCookieFn { - return (req: T): Record => - Object.fromEntries(req.cookies.getAll().map((cookie) => [cookie.name, cookie.value])); - } - - static getAppDirRequestHandler(NextResponse: typeof Response) { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - const stMiddleware = middleware((req) => { - return createPreParsedRequest(req, getCookieFromNextReq); - }); - return getHandleCall(NextResponse, stMiddleware); + static getAppDirRequestHandler() { + return handleAuthAPIRequest(); } static async getSSRSession( - cookies: Array<{ name: string; value: string }>, - headers: Headers, - options?: VerifySessionOptions, - userContext?: Record + cookies: Array<{ name: string; value: string }> ): Promise<{ - session: SessionContainer | undefined; + accessTokenPayload: JWTPayload | undefined; hasToken: boolean; - hasInvalidClaims: boolean; + error: Error | undefined; }> { - // Create an instance of PreParsedRequest without access to the actual - // request body and inject cookies into it. - let baseRequest = new PreParsedRequest({ - method: "get", - url: "", - query: {}, - headers: headers, - cookies: Object.fromEntries(cookies.map((cookie) => [cookie.name, cookie.value])), - getFormBody: async () => [], - getJSONBody: async () => [], - }); - - const { baseResponse, response, ...result } = await getSessionDetails( - baseRequest, - options, - getUserContext(userContext) - ); - return result; + let accessToken = cookies.find((cookie) => cookie.name === "sAccessToken")?.value; + return await getSessionForSSRUsingAccessToken(accessToken); } static async withSession( @@ -137,32 +106,14 @@ export default class NextJS { options?: VerifySessionOptions, userContext?: Record ): Promise { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - return await customWithSession( - req, - handler, - options, - userContext, - getCookieFromNextReq - ); + return await customWithSession(req, handler, options, userContext); } static async withPreParsedRequestResponse( req: NextRequest, handler: (baseRequest: PreParsedRequest, baseResponse: CollectingResponse) => Promise ): Promise { - const getCookieFromNextReq = NextJS.getCookieExtractor(); - let baseRequest = createPreParsedRequest(req, getCookieFromNextReq); - let baseResponse = new CollectingResponse(); - let userResponse: NextResponse; - - try { - userResponse = await handler(baseRequest, baseResponse); - } catch (err) { - userResponse = await handleError(err, baseRequest, baseResponse); - } - - return addCookies(baseResponse, userResponse); + return customWithPreParsedRequestResponse(req, handler); } } export let superTokensNextWrapper = NextJS.superTokensNextWrapper; diff --git a/test/with-typescript/index.ts b/test/with-typescript/index.ts index b9be6ec15..97dc2dfbf 100644 --- a/test/with-typescript/index.ts +++ b/test/with-typescript/index.ts @@ -2258,13 +2258,13 @@ async function handleCall(req: NextRequest): Promise { }); } -NextJS.getAppDirRequestHandler(NextResponse); +NextJS.getAppDirRequestHandler(); customVerifySession({ checkDatabase: true })(new PreParsedRequest({} as any), new CollectingResponse()); const nextRequest = new NextRequest("http://localhost:3000/api/user"); -NextJS.getSSRSession(nextRequest.cookies.getAll(), nextRequest.headers); +NextJS.getSSRSession(nextRequest.cookies.getAll()); NextJS.withSession(nextRequest, async function test(session): Promise { return NextResponse.json({}); }); From 892c05b77e271366865f54820dd4593f06edabad Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 7 Oct 2024 08:01:41 +0530 Subject: [PATCH 2/4] Fix custom framework related tests --- test/customFramework.test.js | 98 ++---------------------------------- 1 file changed, 5 insertions(+), 93 deletions(-) diff --git a/test/customFramework.test.js b/test/customFramework.test.js index aee2a5ab2..b78966e99 100644 --- a/test/customFramework.test.js +++ b/test/customFramework.test.js @@ -65,60 +65,6 @@ async function signJWT(privateKey, jwks, payload, expiresIn = "2h") { .sign(privateKey); } -describe(`createPreParsedRequest ${printPath("[test/customFramework.test.js]")}`, () => { - it("should create a PreParsedRequest with correct properties from the Request object", async () => { - // Mock a Request object - const mockRequest = { - url: "https://example.com/path?name=test", - method: "POST", - headers: new Headers({ - "Content-Type": "application/json", - Authorization: "Bearer token", - Cookie: "session=abcd1234; theme=dark", - }), - formData: async () => new FormData(), - json: async () => ({ key: "value" }), - }; - - // Assume getCookieFromRequest and getQueryFromRequest return specific mock data - const mockCookies = { session: "abcd1234", theme: "dark" }; - const mockQuery = { name: "test" }; - - // Create the PreParsedRequest - const preParsedReq = createPreParsedRequest(mockRequest); - - // Assertions - assert(preParsedReq instanceof PreParsedRequest, "Should return an instance of PreParsedRequest"); - assert.deepStrictEqual( - preParsedReq.getCookieValue("session"), - mockCookies.session, - "Should parse `session` value from cookie correctly" - ); - assert.deepStrictEqual( - preParsedReq.getCookieValue("theme"), - mockCookies.theme, - "Should parse `session` value from cookie correctly" - ); - assert.strictEqual(preParsedReq.getOriginalURL(), mockRequest.url, "Should set the correct URL"); - assert.strictEqual(preParsedReq.getMethod(), mockRequest.method.toLowerCase(), "Should set the correct method"); - assert.deepStrictEqual( - preParsedReq.getKeyValueFromQuery("name"), - mockQuery.name, - "Should parse query parameters correctly" - ); - assert.strictEqual( - preParsedReq.getHeaderValue("Authorization"), - mockRequest.headers.get("Authorization"), - "Should set the correct headers" - ); - - // Test getJSONBody methods - const jsonBody = await preParsedReq.getJSONBody(); - - assert.deepStrictEqual(jsonBody, { key: "value" }, "getJSONBody should return parsed JSON body"); - }); -}); - describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, () => { let connectionURI; let accessToken, accessTokenPayload; @@ -349,22 +295,14 @@ describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, assert.strictEqual(await response.text(), "Not found", "Should return Not found"); }); - // NOTE: For all the JWT related testing, we are using a different key because - // the default way of getting the key is by hitting the `/jwt/jwks` endpoint - // but that endpoint doesn't return anything for testing and thus we are testing - // with a custom key. - it("getSessionForSSR should return session for valid token", async () => { - // Sign the JWT - const validToken = await signJWT(privateKey, jwks, accessTokenPayload); - // Create a mock request containing the valid token as a cookie const mockRequest = new Request("https://example.com", { - headers: { Cookie: `sAccessToken=${validToken}` }, + headers: { Cookie: `sAccessToken=${accessToken}` }, }); // Call the getSessionForSSR function - const result = await getSessionForSSR(mockRequest, await createJWTVerifyGetKey(jwks)); + const result = await getSessionForSSR(mockRequest); // Assertions assert.strictEqual(result.hasToken, true, "hasToken should be true for a valid token"); @@ -378,7 +316,7 @@ describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, const mockRequest = new Request("https://example.com"); // Call the getSessionForSSR function - const result = await getSessionForSSR(mockRequest, privateKey); + const result = await getSessionForSSR(mockRequest); // Assertions assert.strictEqual(result.hasToken, false, "hasToken should be false when no token is present"); @@ -390,32 +328,6 @@ describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, assert.strictEqual(result.error, undefined, "error should be undefined when no token is present"); }); - it("should handle an expired token gracefully", async () => { - // Sign the JWT with an expiration time in the past (e.g., 1 second ago) - const expiredToken = await signJWT(privateKey, jwks, accessTokenPayload, Math.floor(Date.now() / 1000) - 1); - - // Create a mock request containing the expired token as a cookie - const mockRequest = new Request("https://example.com", { - headers: { Cookie: `sAccessToken=${expiredToken}` }, - }); - - // Call the getSessionForSSR function - const result = await getSessionForSSR(mockRequest, privateKey); - - // Assertions - assert.strictEqual(result.hasToken, true, "hasToken should be true for an expired token"); - assert.strictEqual( - result.accessTokenPayload, - undefined, - "accessTokenPayload should be undefined for an expired token" - ); - assert.strictEqual( - result.error.type, - "TRY_REFRESH_TOKEN", - "error should be TRY_REFRESH_TOKEN for an expired token" - ); - }); - it("should return an error for an invalid token", async () => { // Assume you have an invalid token that does not match the JWKS const invalidToken = "your-invalid-jwt-token"; @@ -426,7 +338,7 @@ describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, }); // Call the getSessionForSSR function - const result = await getSessionForSSR(mockRequest, privateKey); + const result = await getSessionForSSR(mockRequest); // Assertions assert.strictEqual(result.hasToken, true, "hasToken should be true for an invalid token"); @@ -435,6 +347,6 @@ describe(`handleAuthAPIRequest ${printPath("[test/customFramework.test.js]")}`, undefined, "accessTokenPayload should be undefined for an invalid token" ); - assert.ok(result.error instanceof Error, "error should be an instance of Error for an invalid token"); + assert.strictEqual(result.error, undefined, "error should be undefined for an invalid token"); }); }); From 1d6e6791b8a926cd283b86a0a8e4efceaf21ee7e Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 7 Oct 2024 10:50:41 +0530 Subject: [PATCH 3/4] Add/fix tests for next js --- test/nextjs.test.js | 256 +++++++++++++++++++------------------------- 1 file changed, 113 insertions(+), 143 deletions(-) diff --git a/test/nextjs.test.js b/test/nextjs.test.js index 3e7744988..35d5cdcd5 100644 --- a/test/nextjs.test.js +++ b/test/nextjs.test.js @@ -13,10 +13,12 @@ * under the License. */ +const { parseJWTWithoutSignatureVerification } = require("../lib/build/recipe/session/jwt"); + const [major, minor, patch] = process.versions.node.split(".").map(Number); if (major >= 18) { - const { printPath, setupST, startST, killAllST, cleanST, delay } = require("./utils"); + const { printPath, setupST, startST, killAllST, cleanST, delay, killAllSTCoresOnly } = require("./utils"); let assert = require("assert"); let { ProcessState } = require("../lib/build/processState"); let SuperTokens = require("../lib/build/").default; @@ -729,58 +731,30 @@ if (major >= 18) { const authenticatedRequest = new NextRequest("http://localhost:3000/api/get-user", { headers: { - Authorization: `Bearer ${tokens.access}`, + Cookie: `sAccessToken=${tokens.access}`, }, }); - let sessionContainer = await getSSRSession( - authenticatedRequest.cookies.getAll(), - authenticatedRequest.headers - ); + let sessionContainer = await getSSRSession(authenticatedRequest.cookies.getAll()); assert.equal(sessionContainer.hasToken, true); - assert.equal(sessionContainer.session.getUserId(), process.env.user); + assert.equal(sessionContainer.accessTokenPayload.sub, process.env.user); const unAuthenticatedRequest = new NextRequest("http://localhost:3000/api/get-user"); - sessionContainer = await getSSRSession( - unAuthenticatedRequest.cookies.getAll(), - unAuthenticatedRequest.headers - ); + sessionContainer = await getSSRSession(unAuthenticatedRequest.cookies.getAll()); assert.equal(sessionContainer.hasToken, false); - assert.equal(sessionContainer.session, undefined); - - const requestWithFailedClaim = new NextRequest("http://localhost:3000/api/get-user", { - headers: { - Authorization: `Bearer ${tokens.access}`, - }, - }); - - sessionContainer = await getSSRSession( - requestWithFailedClaim.cookies.getAll(), - requestWithFailedClaim.headers, - { - overrideGlobalClaimValidators: async (globalValidators) => [ - ...globalValidators, - EmailVerification.EmailVerificationClaim.validators.isVerified(), - ], - } - ); - assert.equal(sessionContainer.hasToken, true); - assert.equal(sessionContainer.hasInvalidClaims, true); + assert.equal(sessionContainer.accessTokenPayload, undefined); await delay(3); const requestWithExpiredToken = new NextRequest("http://localhost:3000/api/get-user", { headers: { - Authorization: `Bearer ${tokens.access}`, + Cookie: `sAccessToken=${tokens.access}`, }, }); - sessionContainer = await getSSRSession( - requestWithExpiredToken.cookies.getAll(), - requestWithExpiredToken.headers - ); + sessionContainer = await getSSRSession(requestWithExpiredToken.cookies.getAll()); assert.equal(sessionContainer.session, undefined); assert.equal(sessionContainer.hasToken, true); }); @@ -841,7 +815,7 @@ if (major >= 18) { const requestWithFailedClaim = new NextRequest("http://localhost:3000/api/get-user", { headers: { - Authorization: `Bearer ${tokens.access}`, + Cookie: `sAccessToken=${tokens.access}`, }, }); @@ -893,6 +867,42 @@ if (major >= 18) { assert.equal(responseThatThrows.status, 500); }); + it("withSession with updated access token payload should be correctly returned", async () => { + const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); + + const authenticatedRequest = new NextRequest("http://localhost:3000/api/get-user", { + headers: { + Authorization: `Bearer ${tokens.access}`, + }, + }); + + const authenticatedResponse = await withSession(authenticatedRequest, async (err, session) => { + if (err) return NextResponse.json(err, { status: 500 }); + + // Update token payload + await session.mergeIntoAccessTokenPayload({ test: true }); + + return NextResponse.json({ + userId: session.getUserId(), + sessionHandle: session.getHandle(), + accessTokenPayload: session.getAccessTokenPayload(), + }); + }); + const updatedAccessToken = authenticatedResponse.headers.get("st-access-token"); + const tokenInfo = parseJWTWithoutSignatureVerification(updatedAccessToken); + + assert.strictEqual( + authenticatedResponse.headers.get("Cache-Control"), + "no-cache, no-store, max-age=0, must-revalidate", + "cache control headers should be set" + ); + assert.strictEqual( + tokenInfo.payload.test, + true, + "access token payload should have a test value that is true" + ); + }); + it("withPreParsedRequestResponse", async function () { const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); @@ -977,32 +987,17 @@ if (major >= 18) { const requestWithNoToken = new NextRequest("http://localhost:3000/api/get-user"); - sessionContainer = await getSSRSession(requestWithNoToken.cookies.getAll(), requestWithNoToken.headers); + sessionContainer = await getSSRSession(requestWithNoToken.cookies.getAll()); assert.equal(sessionContainer.hasToken, false); const requestWithInvalidToken = new NextRequest("http://localhost:3000/api/get-user", { headers: { - Authorization: `Bearer some-random-token`, - }, - }); - - sessionContainer = await getSSRSession( - requestWithInvalidToken.cookies.getAll(), - requestWithInvalidToken.headers - ); - assert.equal(sessionContainer.hasToken, false); - - const requestWithTokenInHeader = new NextRequest("http://localhost:3000/api/get-user", { - headers: { - Authorization: `Bearer ${tokens.access}`, + Cookie: `sAccessToken=some-random-token`, }, }); - sessionContainer = await getSSRSession( - requestWithTokenInHeader.cookies.getAll(), - requestWithTokenInHeader.headers - ); + sessionContainer = await getSSRSession(requestWithInvalidToken.cookies.getAll()); assert.equal(sessionContainer.hasToken, true); const requestWithTokenInCookie = new NextRequest("http://localhost:3000/api/get-user", { @@ -1011,10 +1006,7 @@ if (major >= 18) { }, }); - sessionContainer = await getSSRSession( - requestWithTokenInCookie.cookies.getAll(), - requestWithTokenInCookie.headers - ); + sessionContainer = await getSSRSession(requestWithTokenInCookie.cookies.getAll()); assert.equal(sessionContainer.hasToken, true); }); }); @@ -1055,7 +1047,7 @@ if (major >= 18) { const requestWithNoToken = new NextRequest("http://localhost:3000/api/get-user"); - sessionContainer = await getSSRSession(requestWithNoToken.cookies.getAll(), requestWithNoToken.headers); + sessionContainer = await getSSRSession(requestWithNoToken.cookies.getAll()); assert.equal(sessionContainer.hasToken, false); @@ -1065,11 +1057,8 @@ if (major >= 18) { }, }); - sessionContainer = await getSSRSession( - requestWithInvalidToken.cookies.getAll(), - requestWithInvalidToken.headers - ); - assert.equal(sessionContainer.hasToken, false); + sessionContainer = await getSSRSession(requestWithInvalidToken.cookies.getAll()); + assert.equal(sessionContainer.hasToken, true); const requestWithTokenInHeader = new NextRequest("http://localhost:3000/api/get-user", { headers: { @@ -1077,10 +1066,7 @@ if (major >= 18) { }, }); - sessionContainer = await getSSRSession( - requestWithTokenInHeader.cookies.getAll(), - requestWithTokenInHeader.headers - ); + sessionContainer = await getSSRSession(requestWithTokenInHeader.cookies.getAll()); assert.equal(sessionContainer.hasToken, false); const requestWithTokenInCookie = new NextRequest("http://localhost:3000/api/get-user", { @@ -1089,90 +1075,74 @@ if (major >= 18) { }, }); - sessionContainer = await getSSRSession( - requestWithTokenInCookie.cookies.getAll(), - requestWithTokenInCookie.headers - ); + sessionContainer = await getSSRSession(requestWithTokenInCookie.cookies.getAll()); assert.equal(sessionContainer.hasToken, true); }); }); + }); - describe("tokenTransferMethod = header", function () { - before(async function () { - process.env.user = undefined; - await killAllST(); - await setupST(); - const connectionURI = await startST(); - ProcessState.getInstance().reset(); - SuperTokens.init({ - supertokens: { - connectionURI, - }, - appInfo: { - apiDomain: "api.supertokens.io", - appName: "SuperTokens", - apiBasePath: "/api/auth", - websiteDomain: "supertokens.io", - }, - recipeList: [ - EmailPassword.init(), - Session.init({ - getTokenTransferMethod: () => "header", - }), - ], - }); - }); - - after(async function () { - await killAllST(); - await cleanST(); + describe("with email verification should throw st-ev claim has expired", async () => { + before(async function () { + process.env.user = undefined; + await killAllST(); + await setupST(); + const connectionURI = await startST(); + ProcessState.getInstance().reset(); + SuperTokens.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + apiBasePath: "/api/auth", + websiteDomain: "supertokens.io", + }, + recipeList: [ + EmailPassword.init(), + Session.init({ + getTokenTransferMethod: () => "any", + }), + EmailVerification.init({ + mode: "REQUIRED", + }), + ], }); + }); - it("should return hasToken value correctly", async function () { - const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); - - const requestWithNoToken = new NextRequest("http://localhost:3000/api/get-user"); - - sessionContainer = await getSSRSession(requestWithNoToken.cookies.getAll(), requestWithNoToken.headers); - - assert.equal(sessionContainer.hasToken, false); - - const requestWithInvalidToken = new NextRequest("http://localhost:3000/api/get-user", { - headers: { - Authorization: `Bearer some-random-token`, - }, - }); - - sessionContainer = await getSSRSession( - requestWithInvalidToken.cookies.getAll(), - requestWithInvalidToken.headers - ); - assert.equal(sessionContainer.hasToken, false); - - const requestWithTokenInHeader = new NextRequest("http://localhost:3000/api/get-user", { - headers: { - Authorization: `Bearer ${tokens.access}`, - }, - }); + after(async function () { + await killAllST(); + await cleanST(); + }); - sessionContainer = await getSSRSession( - requestWithTokenInHeader.cookies.getAll(), - requestWithTokenInHeader.headers - ); - assert.equal(sessionContainer.hasToken, true); + it("should throw st-ev claim has expired for unverified email", async () => { + const tokens = await getValidTokensAfterSignup(); + const authenticatedRequest = new NextRequest("http://localhost:3000/api/get-user", { + headers: { + Cookie: `sAccessToken=${tokens.access}`, + }, + }); - const requestWithTokenInCookie = new NextRequest("http://localhost:3000/api/get-user", { - headers: { - Cookie: `sAccessToken=${tokens.access}`, - }, + const authenticatedResponse = await withSession(authenticatedRequest, async (err, session) => { + if (err) return NextResponse.json(`CUSTOM_ERROR: ${err}`, { status: 500 }); + return NextResponse.json({ + userId: session.getUserId(), + sessionHandle: session.getHandle(), + accessTokenPayload: session.getAccessTokenPayload(), }); - - sessionContainer = await getSSRSession( - requestWithTokenInCookie.cookies.getAll(), - requestWithTokenInCookie.headers - ); - assert.equal(sessionContainer.hasToken, false); }); + const responseJSON = await authenticatedResponse.json(); + assert.strictEqual(responseJSON.message, "invalid claim", "should return message: invalid claim"); + assert.strictEqual( + responseJSON.claimValidationErrors.length, + 1, + "should return claim validation errors of length 1" + ); + assert.strictEqual( + responseJSON.claimValidationErrors[0].id, + "st-ev", + "should return claim validation error id as st-ev" + ); }); }); From 57be23d72837d086130d7f7de9af385488c9fb2b Mon Sep 17 00:00:00 2001 From: Deepjyoti Barman Date: Mon, 7 Oct 2024 12:10:03 +0530 Subject: [PATCH 4/4] Add test for session refresh in next --- .../app/api/auth/[...path]/route.ts | 4 +- test/nextjs.test.js | 92 ++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/examples/next/with-emailpassword/app/api/auth/[...path]/route.ts b/examples/next/with-emailpassword/app/api/auth/[...path]/route.ts index 4385f250f..51385b9db 100644 --- a/examples/next/with-emailpassword/app/api/auth/[...path]/route.ts +++ b/examples/next/with-emailpassword/app/api/auth/[...path]/route.ts @@ -1,11 +1,11 @@ import { getAppDirRequestHandler } from "supertokens-node/nextjs"; -import { NextRequest, NextResponse } from "next/server"; +import { NextRequest } from "next/server"; import supertokens from "supertokens-node"; import { backendConfig } from "../../../../config/backendConfig"; supertokens.init(backendConfig()); -const handleCall = getAppDirRequestHandler(NextResponse); +const handleCall = getAppDirRequestHandler(); export async function GET(request: NextRequest) { const res = await handleCall(request); diff --git a/test/nextjs.test.js b/test/nextjs.test.js index 35d5cdcd5..6883fc35f 100644 --- a/test/nextjs.test.js +++ b/test/nextjs.test.js @@ -18,7 +18,16 @@ const { parseJWTWithoutSignatureVerification } = require("../lib/build/recipe/se const [major, minor, patch] = process.versions.node.split(".").map(Number); if (major >= 18) { - const { printPath, setupST, startST, killAllST, cleanST, delay, killAllSTCoresOnly } = require("./utils"); + const { + printPath, + setupST, + startST, + killAllST, + cleanST, + delay, + killAllSTCoresOnly, + extractInfoFromResponse, + } = require("./utils"); let assert = require("assert"); let { ProcessState } = require("../lib/build/processState"); let SuperTokens = require("../lib/build/").default; @@ -948,6 +957,85 @@ if (major >= 18) { assert.strictEqual(error, unknownError); } }); + + // it("should go to next error handler when withSession is called without core", async function () { + // const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "header" }); + + // const authenticatedRequest = new NextRequest("http://localhost:3000/api/get-user", { + // headers: { + // Cookie: `sAccessToken=${tokens.access}`, + // }, + // }); + + // // Manually kill to get error when withSession is called + // await killAllSTCoresOnly(); + + // const authenticatedResponse = await withSession(authenticatedRequest, async (err, session) => { + // if (err) return NextResponse.json(`CUSTOM_ERROR: ${err}`, { status: 500 }); + // return NextResponse.json({ + // userId: session.getUserId(), + // sessionHandle: session.getHandle(), + // accessTokenPayload: session.getAccessTokenPayload(), + // }); + // }); + // const responseJSON = await authenticatedResponse.json(); + // console.log(responseJSON); + // assert.strictEqual(responseJSON, {}, "test"); + // }); + }); + + describe("session refresh test", async () => { + before(async function () { + process.env.user = undefined; + await killAllST(); + await setupST(); + const connectionURI = await startST(); + ProcessState.getInstance().reset(); + SuperTokens.init({ + supertokens: { + connectionURI, + }, + appInfo: { + apiDomain: "api.supertokens.io", + appName: "SuperTokens", + apiBasePath: "/api/auth", + websiteDomain: "supertokens.io", + }, + recipeList: [ + EmailPassword.init(), + Session.init({ + getTokenTransferMethod: () => "cookie", + }), + ], + }); + }); + + after(async function () { + await killAllST(); + await cleanST(); + }); + + it("should successfully refresh session", async () => { + const tokens = await getValidTokensAfterSignup({ tokenTransferMethod: "cookie" }); + + const authenticatedRequest = new NextRequest("http://localhost:3000/api/auth/session/refresh", { + headers: { + Cookie: `sAccessToken=${tokens.access};sRefreshToken=${tokens.refresh}`, + "st-auth-mode": "cookie", + }, + }); + + const authenticatedResponse = await withPreParsedRequestResponse( + authenticatedRequest, + async (baseRequest, baseResponse) => { + const session = await Session.getSession(baseRequest, baseResponse); + return NextResponse.json({ userId: session.getUserId() }); + } + ); + const responseJSON = await authenticatedResponse.json(); + assert.equal(authenticatedResponse.status, 200, "response should return a 200 OK"); + assert.ok(responseJSON.userId, "response should contain the user ID"); + }); }); describe(`getSSRSession:hasToken`, function () { @@ -1199,6 +1287,8 @@ if (major >= 18) { } } + tokens.antiCsrf = response.headers.get("anti-csrf"); + return tokens; } }