From 7d0fac0b0dd26718741c0625538eb047536c6965 Mon Sep 17 00:00:00 2001 From: CJ Brewer Date: Wed, 22 Jan 2025 09:33:42 -0700 Subject: [PATCH] feat: add generic next middleware --- .changeset/brave-steaks-fly.md | 5 +++ packages/nextjs/src/clerk/index.ts | 57 +++--------------------------- packages/nextjs/src/cts/index.ts | 55 ++++++++++++++++++++++++++++ packages/nextjs/src/index.ts | 31 ++++++++++++++++ 4 files changed, 95 insertions(+), 53 deletions(-) create mode 100644 .changeset/brave-steaks-fly.md create mode 100644 packages/nextjs/src/cts/index.ts diff --git a/.changeset/brave-steaks-fly.md b/.changeset/brave-steaks-fly.md new file mode 100644 index 00000000..1e61fa36 --- /dev/null +++ b/.changeset/brave-steaks-fly.md @@ -0,0 +1,5 @@ +--- +"@cipherstash/nextjs": minor +--- + +Implemented a generic Next.js jseql middleware. diff --git a/packages/nextjs/src/clerk/index.ts b/packages/nextjs/src/clerk/index.ts index be417a11..72b6f2a7 100644 --- a/packages/nextjs/src/clerk/index.ts +++ b/packages/nextjs/src/clerk/index.ts @@ -1,7 +1,8 @@ import type { ClerkMiddlewareAuth } from '@clerk/nextjs/server' import type { NextRequest } from 'next/server' import { NextResponse } from 'next/server' -import { CS_COOKIE_NAME, type CtsToken } from '../index' +import { CS_COOKIE_NAME, resetCtsToken } from '../index' +import { setCtsToken } from '../cts' import { logger } from '../../../utils/logger' export const jseqlClerkMiddleware = async ( @@ -22,55 +23,7 @@ export const jseqlClerkMiddleware = async ( return NextResponse.next() } - const workspaceId = process.env.CS_WORKSPACE_ID - - if (!workspaceId) { - logger.error( - 'The "CS_WORKSPACE_ID" environment variable is not set, and is required by jseqlClerkMiddleware. No CipherStash session will be set.', - ) - - return NextResponse.next() - } - - const ctsEndoint = - process.env.CS_CTS_ENDPOINT || - 'https://ap-southeast-2.aws.auth.viturhosted.net' - - const ctsResponse = await fetch(`${ctsEndoint}/api/authorize`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - workspaceId, - oidcToken, - }), - }) - - if (!ctsResponse.ok) { - logger.debug(`Failed to fetch CTS token: ${ctsResponse.statusText}`) - - logger.error( - 'There was an issue communicating with the CipherStash CTS API, the CipherStash session was not set. If the issue persists, please contact support.', - ) - - return NextResponse.next() - } - - const cts_token = (await ctsResponse.json()) as CtsToken - - // Setting cookies on the request and response using the `ResponseCookies` API - const response = NextResponse.next() - response.cookies.set({ - name: CS_COOKIE_NAME, - value: JSON.stringify(cts_token), - expires: new Date(cts_token.expiry * 1000), - sameSite: 'lax', - path: '/', - }) - - response.cookies.get(CS_COOKIE_NAME) - return response + return await setCtsToken(oidcToken) } if (!userId && ctsSession) { @@ -78,9 +31,7 @@ export const jseqlClerkMiddleware = async ( 'No Clerk token found in the request, so the CipherStash session was reset.', ) - const response = NextResponse.next() - response.cookies.delete(CS_COOKIE_NAME) - return response + return resetCtsToken() } logger.debug( diff --git a/packages/nextjs/src/cts/index.ts b/packages/nextjs/src/cts/index.ts new file mode 100644 index 00000000..086e5e37 --- /dev/null +++ b/packages/nextjs/src/cts/index.ts @@ -0,0 +1,55 @@ +import { NextResponse } from 'next/server' +import { logger } from '../../../utils/logger' +import { CS_COOKIE_NAME, type CtsToken } from '../index' + +export const setCtsToken = async (oidcToken: string) => { + const workspaceId = process.env.CS_WORKSPACE_ID + + if (!workspaceId) { + logger.error( + 'The "CS_WORKSPACE_ID" environment variable is not set, and is required by jseqlClerkMiddleware. No CipherStash session will be set.', + ) + + return NextResponse.next() + } + + const ctsEndoint = + process.env.CS_CTS_ENDPOINT || + 'https://ap-southeast-2.aws.auth.viturhosted.net' + + const ctsResponse = await fetch(`${ctsEndoint}/api/authorize`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + workspaceId, + oidcToken, + }), + }) + + if (!ctsResponse.ok) { + logger.debug(`Failed to fetch CTS token: ${ctsResponse.statusText}`) + + logger.error( + 'There was an issue communicating with the CipherStash CTS API, the CipherStash session was not set. If the issue persists, please contact support.', + ) + + return NextResponse.next() + } + + const cts_token = (await ctsResponse.json()) as CtsToken + + // Setting cookies on the request and response using the `ResponseCookies` API + const response = NextResponse.next() + response.cookies.set({ + name: CS_COOKIE_NAME, + value: JSON.stringify(cts_token), + expires: new Date(cts_token.expiry * 1000), + sameSite: 'lax', + path: '/', + }) + + response.cookies.get(CS_COOKIE_NAME) + return response +} diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index 6130dd91..77c2cb66 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -1,4 +1,7 @@ +import type { NextRequest } from 'next/server' +import { NextResponse } from 'next/server' import { cookies } from 'next/headers' +import { setCtsToken } from './cts' import { logger } from '../../utils/logger' export const CS_COOKIE_NAME = '__cipherstash_cts_session' @@ -20,3 +23,31 @@ export const getCtsToken = async () => { const cts_token = JSON.parse(cookieData) as CtsToken return cts_token } + +export const resetCtsToken = () => { + const response = NextResponse.next() + response.cookies.delete(CS_COOKIE_NAME) + return response +} + +export const jseqlMiddleware = async (oidcToken: string, req: NextRequest) => { + const ctsSession = req.cookies.has(CS_COOKIE_NAME) + + if (oidcToken && !ctsSession) { + return await setCtsToken(oidcToken) + } + + if (!oidcToken && ctsSession) { + logger.debug( + 'The JWT token was undefined, so the CipherStash session was reset.', + ) + + return resetCtsToken() + } + + logger.debug( + 'The JWT token was undefined, so the CipherStash session was not set.', + ) + + return NextResponse.next() +}