@cipherstash/nextjs
Version:
Nextjs package for use with @cipherstash/protect
1 lines • 14 kB
Source Map (JSON)
{"version":3,"sources":["../../src/clerk/index.ts","../../../utils/logger/index.ts","../../src/cts/index.ts","../../../utils/config/index.ts","../../src/index.ts"],"sourcesContent":["import type { ClerkMiddlewareAuth } from '@clerk/nextjs/server'\nimport type { NextRequest } from 'next/server'\nimport { NextResponse } from 'next/server'\nimport { logger } from '../../../utils/logger'\nimport { setCtsToken } from '../cts'\nimport { CS_COOKIE_NAME, resetCtsToken } from '../index'\n\nexport const protectClerkMiddleware = async (\n auth: ClerkMiddlewareAuth,\n req: NextRequest,\n) => {\n const { userId, getToken } = await auth()\n const ctsSession = req.cookies.has(CS_COOKIE_NAME)\n\n if (userId && !ctsSession) {\n const oidcToken = await getToken()\n\n if (!oidcToken) {\n logger.debug(\n 'No Clerk token found in the request, so the CipherStash session was not set.',\n )\n\n return NextResponse.next()\n }\n\n return await setCtsToken(oidcToken)\n }\n\n if (!userId && ctsSession) {\n logger.debug(\n 'No Clerk token found in the request, so the CipherStash session was reset.',\n )\n\n return resetCtsToken()\n }\n\n logger.debug(\n 'No Clerk token found in the request, so the CipherStash session was not set.',\n )\n\n return NextResponse.next()\n}\n","function getLevelValue(level: string): number {\n switch (level) {\n case 'debug':\n return 10\n case 'info':\n return 20\n case 'error':\n return 30\n default:\n return 30 // default to error level\n }\n}\n\nconst envLogLevel = process.env.PROTECT_LOG_LEVEL || 'info'\nconst currentLevel = getLevelValue(envLogLevel)\n\nfunction debug(...args: unknown[]): void {\n if (currentLevel <= getLevelValue('debug')) {\n console.debug('[protect] DEBUG', ...args)\n }\n}\n\nfunction info(...args: unknown[]): void {\n if (currentLevel <= getLevelValue('info')) {\n console.info('[protect] INFO', ...args)\n }\n}\n\nfunction error(...args: unknown[]): void {\n if (currentLevel <= getLevelValue('error')) {\n console.error('[protect] ERROR', ...args)\n }\n}\n\nexport const logger = {\n debug,\n info,\n error,\n}\n","import { NextResponse } from 'next/server'\nimport { logger } from '../../../utils/logger'\nimport { loadWorkSpaceId } from '../../../utils/config'\nimport {\n CS_COOKIE_NAME,\n type CtsToken,\n type GetCtsTokenResponse,\n} from '../index'\n\n// Can be used independently of the Next.js middleware\nexport const fetchCtsToken = async (oidcToken: string): GetCtsTokenResponse => {\n const workspaceId = loadWorkSpaceId()\n\n if (!workspaceId) {\n logger.error(\n 'The \"CS_WORKSPACE_ID\" environment variable is not set, and is required by protectClerkMiddleware. No CipherStash session will be set.',\n )\n\n return {\n success: false,\n error: 'The \"CS_WORKSPACE_ID\" environment variable is not set.',\n }\n }\n\n const ctsEndoint =\n process.env.CS_CTS_ENDPOINT ||\n 'https://ap-southeast-2.aws.auth.viturhosted.net'\n\n const ctsResponse = await fetch(`${ctsEndoint}/api/authorize`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n workspaceId,\n oidcToken,\n }),\n })\n\n if (!ctsResponse.ok) {\n logger.debug(`Failed to fetch CTS token: ${ctsResponse.statusText}`)\n\n logger.error(\n 'There was an issue communicating with the CipherStash CTS API, the CipherStash session was not set. If the issue persists, please contact support.',\n )\n\n return {\n success: false,\n error: `Failed to fetch CTS token: ${ctsResponse.statusText}`,\n }\n }\n\n const cts_token = (await ctsResponse.json()) as CtsToken\n\n return {\n success: true,\n ctsToken: cts_token,\n }\n}\n\n// Used in the Next.js middleware\nexport const setCtsToken = async (oidcToken: string, res?: NextResponse) => {\n const ctsResponse = await fetchCtsToken(oidcToken)\n const cts_token = ctsResponse.ctsToken\n\n if (!cts_token) {\n logger.debug(`Failed to fetch CTS token: ${ctsResponse.error}`)\n\n logger.error(\n 'There was an issue fetching the CipherStash session, the CipherStash session was not set. If the issue persists, please contact support.',\n )\n\n return res ?? NextResponse.next()\n }\n\n // Setting cookies on the request and response using the `ResponseCookies` API\n const response = res ?? NextResponse.next()\n response.cookies.set({\n name: CS_COOKIE_NAME,\n value: JSON.stringify(cts_token),\n expires: new Date(cts_token.expiry * 1000),\n sameSite: 'lax',\n path: '/',\n })\n\n return response\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\n\n/**\n * A lightweight function that parses a TOML-like string\n * and returns the `workspace_crn` value found under `[auth]`.\n *\n * @param tomlString The contents of the TOML file as a string.\n * @returns The workspace_crn if found, otherwise undefined.\n */\nfunction getWorkspaceCrn(tomlString: string): string | undefined {\n let currentSection = ''\n let workspaceCrn: string | undefined\n\n const lines = tomlString.split(/\\r?\\n/)\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n const sectionMatch = trimmedLine.match(/^\\[([^\\]]+)\\]$/)\n if (sectionMatch) {\n currentSection = sectionMatch[1]\n continue\n }\n\n const kvMatch = trimmedLine.match(/^(\\w+)\\s*=\\s*\"([^\"]+)\"$/)\n if (kvMatch) {\n const [_, key, value] = kvMatch\n\n if (currentSection === 'auth' && key === 'workspace_crn') {\n workspaceCrn = value\n break\n }\n }\n }\n\n return workspaceCrn\n}\n\n/**\n * Extracts the workspace ID from a CRN string.\n * CRN format: crn:region.aws:ID\n *\n * @param crn The CRN string to extract from\n * @returns The workspace ID portion of the CRN\n */\nfunction extractWorkspaceIdFromCrn(crn: string): string {\n const match = crn.match(/crn:[^:]+:([^:]+)$/)\n if (!match) {\n throw new Error('Invalid CRN format')\n }\n return match[1]\n}\n\nexport function loadWorkSpaceId(): string {\n const configPath = path.join(process.cwd(), 'cipherstash.toml')\n\n if (!fs.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n // Environment variables take precedence over config files\n if (process.env.CS_WORKSPACE_CRN) {\n return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN)\n }\n\n if (!fs.existsSync(configPath)) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n const tomlString = fs.readFileSync(configPath, 'utf8')\n const workspaceCrn = getWorkspaceCrn(tomlString)\n\n if (!workspaceCrn) {\n throw new Error(\n 'You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable.',\n )\n }\n\n return extractWorkspaceIdFromCrn(workspaceCrn)\n}\n","import { decodeJwt } from 'jose'\nimport { cookies } from 'next/headers'\nimport type { NextRequest } from 'next/server'\nimport { NextResponse } from 'next/server'\nimport { logger } from '../../utils/logger'\nimport { fetchCtsToken, setCtsToken } from './cts'\n\nfunction getSubjectFromToken(jwt: string): string | undefined {\n const payload = decodeJwt(jwt)\n\n // The CTS JWT payload has a sub field that starts with \"CS|\"\n if (typeof payload?.sub === 'string' && payload?.sub.startsWith('CS|')) {\n return payload.sub.slice(3)\n }\n\n return payload?.sub\n}\n\nexport const CS_COOKIE_NAME = '__cipherstash_cts_session'\n\nexport type CtsToken = {\n accessToken: string\n expiry: number\n}\n\nexport type GetCtsTokenResponse = Promise<\n | {\n success: boolean\n error: string\n ctsToken?: never\n }\n | {\n success: boolean\n error?: never\n ctsToken: CtsToken\n }\n>\n\nexport const getCtsToken = async (oidcToken?: string): GetCtsTokenResponse => {\n const cookieStore = await cookies()\n const cookieData = cookieStore.get(CS_COOKIE_NAME)?.value\n\n if (oidcToken && !cookieData) {\n logger.debug(\n 'The CipherStash session cookie was not found in the request, but a JWT token was provided. The JWT token will be used to fetch a new CipherStash session.',\n )\n\n return await fetchCtsToken(oidcToken)\n }\n\n if (!cookieData) {\n logger.debug('No CipherStash session cookie found in the request.')\n return {\n success: false,\n error: 'No CipherStash session cookie found in the request.',\n }\n }\n\n const cts_token = JSON.parse(cookieData) as CtsToken\n return {\n success: true,\n ctsToken: cts_token,\n }\n}\n\nexport const resetCtsToken = (res?: NextResponse) => {\n if (res) {\n res.cookies.delete(CS_COOKIE_NAME)\n return res\n }\n\n const response = NextResponse.next()\n response.cookies.delete(CS_COOKIE_NAME)\n return response\n}\n\nexport const protectMiddleware = async (\n oidcToken: string,\n req: NextRequest,\n res?: NextResponse,\n) => {\n const ctsSession = req.cookies.get(CS_COOKIE_NAME)?.value\n\n if (oidcToken && ctsSession) {\n const ctsToken = JSON.parse(ctsSession) as CtsToken\n const ctsTokenSubject = getSubjectFromToken(ctsToken.accessToken)\n const oidcTokenSubject = getSubjectFromToken(oidcToken)\n\n if (ctsTokenSubject === oidcTokenSubject) {\n logger.debug(\n 'The JWT token and the CipherStash session are both valid for the same user.',\n )\n\n return res ?? NextResponse.next()\n }\n\n return await setCtsToken(oidcToken, res)\n }\n\n if (oidcToken && !ctsSession) {\n logger.debug(\n 'The JWT token was defined, so the CipherStash session will be set.',\n )\n\n return await setCtsToken(oidcToken, res)\n }\n\n if (!oidcToken && ctsSession) {\n logger.debug(\n 'The JWT token was undefined, so the CipherStash session was reset.',\n )\n\n return resetCtsToken(res)\n }\n\n logger.debug(\n 'The JWT token was undefined, so the CipherStash session was not set.',\n )\n\n if (res) {\n return res\n }\n\n return NextResponse.next()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,IAAAA,iBAA6B;;;ACF7B,SAAS,cAAc,OAAuB;AAC5C,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEA,IAAM,cAAc,QAAQ,IAAI,qBAAqB;AACrD,IAAM,eAAe,cAAc,WAAW;AAE9C,SAAS,SAAS,MAAuB;AACvC,MAAI,gBAAgB,cAAc,OAAO,GAAG;AAC1C,YAAQ,MAAM,mBAAmB,GAAG,IAAI;AAAA,EAC1C;AACF;AAEA,SAAS,QAAQ,MAAuB;AACtC,MAAI,gBAAgB,cAAc,MAAM,GAAG;AACzC,YAAQ,KAAK,kBAAkB,GAAG,IAAI;AAAA,EACxC;AACF;AAEA,SAAS,SAAS,MAAuB;AACvC,MAAI,gBAAgB,cAAc,OAAO,GAAG;AAC1C,YAAQ,MAAM,mBAAmB,GAAG,IAAI;AAAA,EAC1C;AACF;AAEO,IAAM,SAAS;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF;;;ACtCA,IAAAC,iBAA6B;;;ACA7B,qBAAe;AACf,uBAAiB;AASjB,SAAS,gBAAgB,YAAwC;AAC/D,MAAI,iBAAiB;AACrB,MAAI;AAEJ,QAAM,QAAQ,WAAW,MAAM,OAAO;AAEtC,aAAW,QAAQ,OAAO;AACxB,UAAM,cAAc,KAAK,KAAK;AAE9B,QAAI,CAAC,eAAe,YAAY,WAAW,GAAG,GAAG;AAC/C;AAAA,IACF;AAEA,UAAM,eAAe,YAAY,MAAM,gBAAgB;AACvD,QAAI,cAAc;AAChB,uBAAiB,aAAa,CAAC;AAC/B;AAAA,IACF;AAEA,UAAM,UAAU,YAAY,MAAM,yBAAyB;AAC3D,QAAI,SAAS;AACX,YAAM,CAAC,GAAG,KAAK,KAAK,IAAI;AAExB,UAAI,mBAAmB,UAAU,QAAQ,iBAAiB;AACxD,uBAAe;AACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AASA,SAAS,0BAA0B,KAAqB;AACtD,QAAM,QAAQ,IAAI,MAAM,oBAAoB;AAC5C,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AACA,SAAO,MAAM,CAAC;AAChB;AAEO,SAAS,kBAA0B;AACxC,QAAM,aAAa,iBAAAC,QAAK,KAAK,QAAQ,IAAI,GAAG,kBAAkB;AAE9D,MAAI,CAAC,eAAAC,QAAG,WAAW,UAAU,KAAK,CAAC,QAAQ,IAAI,kBAAkB;AAC/D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,MAAI,QAAQ,IAAI,kBAAkB;AAChC,WAAO,0BAA0B,QAAQ,IAAI,gBAAgB;AAAA,EAC/D;AAEA,MAAI,CAAC,eAAAA,QAAG,WAAW,UAAU,GAAG;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,eAAAA,QAAG,aAAa,YAAY,MAAM;AACrD,QAAM,eAAe,gBAAgB,UAAU;AAE/C,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO,0BAA0B,YAAY;AAC/C;;;ACxFA,kBAA0B;AAC1B,qBAAwB;AAExB,oBAA6B;AAetB,IAAM,iBAAiB;AA+CvB,IAAM,gBAAgB,CAAC,QAAuB;AACnD,MAAI,KAAK;AACP,QAAI,QAAQ,OAAO,cAAc;AACjC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,2BAAa,KAAK;AACnC,WAAS,QAAQ,OAAO,cAAc;AACtC,SAAO;AACT;;;AFhEO,IAAM,gBAAgB,OAAO,cAA2C;AAC7E,QAAM,cAAc,gBAAgB;AAEpC,MAAI,CAAC,aAAa;AAChB,WAAO;AAAA,MACL;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,aACJ,QAAQ,IAAI,mBACZ;AAEF,QAAM,cAAc,MAAM,MAAM,GAAG,UAAU,kBAAkB;AAAA,IAC7D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,MAAI,CAAC,YAAY,IAAI;AACnB,WAAO,MAAM,8BAA8B,YAAY,UAAU,EAAE;AAEnE,WAAO;AAAA,MACL;AAAA,IACF;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,8BAA8B,YAAY,UAAU;AAAA,IAC7D;AAAA,EACF;AAEA,QAAM,YAAa,MAAM,YAAY,KAAK;AAE1C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAGO,IAAM,cAAc,OAAO,WAAmB,QAAuB;AAC1E,QAAM,cAAc,MAAM,cAAc,SAAS;AACjD,QAAM,YAAY,YAAY;AAE9B,MAAI,CAAC,WAAW;AACd,WAAO,MAAM,8BAA8B,YAAY,KAAK,EAAE;AAE9D,WAAO;AAAA,MACL;AAAA,IACF;AAEA,WAAO,OAAO,4BAAa,KAAK;AAAA,EAClC;AAGA,QAAM,WAAW,OAAO,4BAAa,KAAK;AAC1C,WAAS,QAAQ,IAAI;AAAA,IACnB,MAAM;AAAA,IACN,OAAO,KAAK,UAAU,SAAS;AAAA,IAC/B,SAAS,IAAI,KAAK,UAAU,SAAS,GAAI;AAAA,IACzC,UAAU;AAAA,IACV,MAAM;AAAA,EACR,CAAC;AAED,SAAO;AACT;;;AF/EO,IAAM,yBAAyB,OACpC,MACA,QACG;AACH,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,KAAK;AACxC,QAAM,aAAa,IAAI,QAAQ,IAAI,cAAc;AAEjD,MAAI,UAAU,CAAC,YAAY;AACzB,UAAM,YAAY,MAAM,SAAS;AAEjC,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,QACL;AAAA,MACF;AAEA,aAAO,4BAAa,KAAK;AAAA,IAC3B;AAEA,WAAO,MAAM,YAAY,SAAS;AAAA,EACpC;AAEA,MAAI,CAAC,UAAU,YAAY;AACzB,WAAO;AAAA,MACL;AAAA,IACF;AAEA,WAAO,cAAc;AAAA,EACvB;AAEA,SAAO;AAAA,IACL;AAAA,EACF;AAEA,SAAO,4BAAa,KAAK;AAC3B;","names":["import_server","import_server","path","fs"]}