From e96837190aae35adb7346945e4c708fb8d8c6f05 Mon Sep 17 00:00:00 2001 From: Edwin Guzman Date: Mon, 8 Jan 2024 15:48:02 -0500 Subject: [PATCH] Removing getPatronData and middleware and calling auth function on getServerSideProps instead --- __test__/pages/Home.test.tsx | 7 ++ middleware.ts | 39 --------- pages/index.tsx | 15 ++-- src/server/auth.ts | 7 +- src/server/getPatronData.ts | 105 ------------------------- src/server/tests/auth.test.ts | 8 +- src/server/tests/getPatronData.test.ts | 89 --------------------- 7 files changed, 19 insertions(+), 251 deletions(-) delete mode 100644 middleware.ts delete mode 100644 src/server/getPatronData.ts delete mode 100644 src/server/tests/getPatronData.test.ts diff --git a/__test__/pages/Home.test.tsx b/__test__/pages/Home.test.tsx index bae733e3e..c1c21c4d7 100644 --- a/__test__/pages/Home.test.tsx +++ b/__test__/pages/Home.test.tsx @@ -3,6 +3,13 @@ import { render, screen } from "@testing-library/react" import Home from "../../pages/index" +// Mock the auth module on the Page component level. +jest.mock("jose", () => ({ + importSPKI: async () => Promise.resolve("testPublicKey"), + jwtVerify: async () => ({ + payload: {}, + }), +})) // Mock next router jest.mock("next/router", () => jest.requireActual("next-router-mock")) diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index 7133b2835..000000000 --- a/middleware.ts +++ /dev/null @@ -1,39 +0,0 @@ -// write middleware file for nextjs -import { NextResponse, type NextRequest } from "next/server" -import initializePatronTokenAuth from "./src/server/auth" - -interface CustomNextRequest extends NextRequest { - userId: string - isTokenValid: boolean -} -export type CustomNextHandler = (req: CustomNextRequest) => void - -/** - * Middleware to check and parse the nyplIdentiyPatron cookie and then verify - * it through JWT. The decoded patron information is then set in Nextjs' - * request headers. - */ -const middleware: CustomNextHandler = async (request: NextRequest) => { - // const requestHeaders = new Headers(request.headers) - // const patronTokenResponse = await initializePatronTokenAuth(request) - - // requestHeaders.set("x-is-token-valid", `${patronTokenResponse?.isTokenValid}`) - // requestHeaders.set("x-user-id", `${patronTokenResponse?.decodedPatron?.sub}`) - - const response = NextResponse.next() - - // const response = NextResponse.next({ - // isTokenValid: patronTokenResponse?.isTokenValid, - // userId: patronTokenResponse?.decodedPatron?.sub, - // } as any) - console.log("response") - return response -} - -export default middleware - -// EG: This is a bit annoying but the catch-all regex did not work for me. -// We might have to manually include all routes individually. -export const config = { - matcher: ["/", "/search/:path*"], -} diff --git a/pages/index.tsx b/pages/index.tsx index 9f75af9cf..3cad562c9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -12,7 +12,6 @@ import Layout from "../src/components/Layout/Layout" import RCLink from "../src/components/RCLink/RCLink" import { SITE_NAME } from "../src/config/constants" import { appConfig } from "../src/config/config" -import { getPatronData } from "../src/server/getPatronData" import initializePatronTokenAuth from "../src/server/auth" export default function Home() { @@ -161,16 +160,12 @@ export default function Home() { } export async function getServerSideProps({ req }) { - // Get patron data if the cookie has been parsed in the middleware. - // This could be passed down to props for the page component to use. + // Every page that needs patron data must call initializePatronTokenAuth + // to find if the token is valid and what the patron id is. const patronTokenResponse = await initializePatronTokenAuth(req) - // const patronData = await getPatronData({ - // isTokenValid: patronTokenResponse?.isTokenValid, - // userId: patronTokenResponse?.decodedPatron?.sub, - // }) - console.log("sevre side props") - // console.log(req.cookies) - console.log("This is just to test patronTokenResponse", patronTokenResponse) + // Now it can be used to get patron data from Sierra or Platform API + // or use `isTokenValid` to redirect to login page if it's not valid. + console.log("patronTokenResponse is", patronTokenResponse) // return props object return { diff --git a/src/server/auth.ts b/src/server/auth.ts index 2a4ab5469..134c74084 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type { JWTPayload } from "jose" -import { importSPKI, jwtVerify } from "jose" +// import type { JWTPayload } from "jose" +import { importSPKI, jwtVerify, type JWTPayload } from "jose" import type { NextRequest } from "next/server" import { appConfig } from "../config/config" @@ -20,8 +20,9 @@ interface UserJwtPayload extends JWTPayload { * and then verify it through JWT. The decoded patron information is returned. */ export default async function initializePatronTokenAuth(req: NextRequest) { - const nyplIdentityPatron = (req.cookies as any)?.nyplIdentityPatron + type cookie = typeof req.cookies & { nyplIdentityPatron?: string } + const nyplIdentityPatron = (req.cookies as cookie)?.nyplIdentityPatron const nyplIdentityCookieObject = nyplIdentityPatron ? JSON.parse(nyplIdentityPatron) : {} diff --git a/src/server/getPatronData.ts b/src/server/getPatronData.ts deleted file mode 100644 index 011dfdeb0..000000000 --- a/src/server/getPatronData.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { isEmpty } from "underscore" - -import nyplApiClient from "./nyplApiClient" - -type NoticePreference = "None" | "Email" | "Telephone" -interface PatronProps { - isTokenValid: boolean - userId: string -} -interface PatronObject { - id: string - names: string[] - barcodes: string[] - emails: string[] - loggedIn: boolean - moneyOwed?: number - homeLibraryCode?: string - patronType?: string - expirationDate?: string - phones?: string[] - noticePreference?: NoticePreference -} - -/** - * extractNoticePreference(fixedFields) - * Returns 'None', 'Email', or 'Telephone'. - * @param {string} fixedFields - Object coming from patron object - * `fixedFields` property - */ -export function extractNoticePreference(fixedFields = {}): NoticePreference { - if (isEmpty(fixedFields)) return "None" - - const noticePreferenceMapping = { - z: "Email", - p: "Telephone", - "-": "None", - } - const noticePreferenceField = fixedFields["268"] - - if (!noticePreferenceField?.value) return "None" - return noticePreferenceMapping[noticePreferenceField.value] || "None" -} - -export async function getPatronData({ - isTokenValid = false, - userId, -}: PatronProps) { - let patron: PatronObject = { - id: "", - names: [], - barcodes: [], - emails: [], - loggedIn: false, - } - - if (isTokenValid && userId) { - await nyplApiClient().then((client) => { - client - .get(`/patrons/${userId}`, { cache: false }) - .then((response) => { - if (!isEmpty(response)) { - const { - id, - names, - emails, - moneyOwed, - homeLibraryCode, - patronType, - expirationDate, - phones, - } = response.data - const barcodes = response.data.barCodes - const noticePreference = extractNoticePreference( - response.data.fixedFields - ) - // Data exists for the Patron - patron = { - id, - names, - barcodes, - emails, - loggedIn: true, - moneyOwed, - homeLibraryCode, - patronType, - expirationDate, - phones, - noticePreference, - } - } - }) - .catch((error) => { - // TODO once logger is set up - // logger.error( - // "Error attemping to make server side fetch call to patrons in getPatronData" + - // `, /patrons/${userId}`, - // error - // ) - console.log("getPatronData", error) - }) - }) - } - - return patron -} diff --git a/src/server/tests/auth.test.ts b/src/server/tests/auth.test.ts index af831a096..b8b458394 100644 --- a/src/server/tests/auth.test.ts +++ b/src/server/tests/auth.test.ts @@ -20,14 +20,12 @@ jest.mock("jose", () => ({ })) const reqNoCookies = { - cookies: { get: () => undefined }, + cookies: {}, } as NextRequest const reqCookiesWithToken = { cookies: { - get: () => ({ - value: '{"access_token":123}', - }), - }, + nyplIdentityPatron: '{"access_token":123}', + } as any, } as NextRequest describe("initializePatronTokenAuth", () => { diff --git a/src/server/tests/getPatronData.test.ts b/src/server/tests/getPatronData.test.ts deleted file mode 100644 index 9442d6fb8..000000000 --- a/src/server/tests/getPatronData.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { extractNoticePreference, getPatronData } from "../getPatronData" - -jest.mock("../nyplApiClient", () => { - return async () => { - return await new Promise((resolve) => { - resolve({ - get: () => { - return new Promise((resolve) => { - resolve({ - data: { - id: "123", - names: ["John Doe"], - emails: ["test@test.org"], - moneyOwed: 0, - homeLibraryCode: "eb", - patronType: "7", - expirationDate: "12/12/2034", - phones: ["718-123-4567"], - barCodes: ["123456789"], - fixedFields: { - "268": { - label: "NOTICE PREFERENCE", - value: "z", - }, - }, - }, - }) - }) - }, - }) - }) - } -}) - -describe("extractNoticePreference", () => { - it('should return "None" if fixedFields is empty', () => { - expect(extractNoticePreference()).toEqual("None") - }) - it('should return "None" if fixedFields does not have a "268" field', () => { - expect(extractNoticePreference({ "123": "nonsense" })).toEqual("None") - }) - it('should return "Email" if "268" field value is "z"', () => { - expect(extractNoticePreference({ "268": { value: "z" } })).toEqual("Email") - }) - it('should return "Telephone" if "268" field value is "p"', () => { - expect(extractNoticePreference({ "268": { value: "p" } })).toEqual( - "Telephone" - ) - }) - it('should return "None" if "268" field value is "-"', () => { - expect(extractNoticePreference({ "268": { value: "-" } })).toEqual("None") - }) -}) - -describe("getPatronData", () => { - it("should return a patron object with loggedIn false if isTokenValid is false", async () => { - const patron = await getPatronData({ isTokenValid: false, userId: null }) - - expect(patron.id).toEqual("") - expect(patron.names).toEqual([]) - expect(patron.barcodes).toEqual([]) - expect(patron.emails).toEqual([]) - expect(patron.loggedIn).toEqual(false) - }) - it("should return a patron object with loggedIn false if userId is not provided", async () => { - const patron = await getPatronData({ isTokenValid: true, userId: null }) - - expect(patron.id).toEqual("") - expect(patron.names).toEqual([]) - expect(patron.barcodes).toEqual([]) - expect(patron.emails).toEqual([]) - expect(patron.loggedIn).toEqual(false) - }) - it("should return a patron object from Platform API when isTokenValid is true and userId is provided", async () => { - const patron = await getPatronData({ isTokenValid: true, userId: "123" }) - - expect(patron.id).toEqual("123") - expect(patron.names).toEqual(["John Doe"]) - expect(patron.barcodes).toEqual(["123456789"]) - expect(patron.emails).toEqual(["test@test.org"]) - expect(patron.loggedIn).toEqual(true) - expect(patron.moneyOwed).toEqual(0) - expect(patron.homeLibraryCode).toEqual("eb") - expect(patron.patronType).toEqual("7") - expect(patron.expirationDate).toEqual("12/12/2034") - expect(patron.phones).toEqual(["718-123-4567"]) - expect(patron.noticePreference).toEqual("Email") - }) -})