@cipherstash/nextjs
Version:
Nextjs package for use with @cipherstash/protect
126 lines (100 loc) • 3.06 kB
text/typescript
import { decodeJwt } from 'jose'
import { cookies } from 'next/headers'
import type { NextRequest } from 'next/server'
import { NextResponse } from 'next/server'
import { logger } from '../../utils/logger'
import { fetchCtsToken, setCtsToken } from './cts'
function getSubjectFromToken(jwt: string): string | undefined {
const payload = decodeJwt(jwt)
// The CTS JWT payload has a sub field that starts with "CS|"
if (typeof payload?.sub === 'string' && payload?.sub.startsWith('CS|')) {
return payload.sub.slice(3)
}
return payload?.sub
}
export const CS_COOKIE_NAME = '__cipherstash_cts_session'
export type CtsToken = {
accessToken: string
expiry: number
}
export type GetCtsTokenResponse = Promise<
| {
success: boolean
error: string
ctsToken?: never
}
| {
success: boolean
error?: never
ctsToken: CtsToken
}
>
export const getCtsToken = async (oidcToken?: string): GetCtsTokenResponse => {
const cookieStore = await cookies()
const cookieData = cookieStore.get(CS_COOKIE_NAME)?.value
if (oidcToken && !cookieData) {
logger.debug(
'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.',
)
return await fetchCtsToken(oidcToken)
}
if (!cookieData) {
logger.debug('No CipherStash session cookie found in the request.')
return {
success: false,
error: 'No CipherStash session cookie found in the request.',
}
}
const cts_token = JSON.parse(cookieData) as CtsToken
return {
success: true,
ctsToken: cts_token,
}
}
export const resetCtsToken = (res?: NextResponse) => {
if (res) {
res.cookies.delete(CS_COOKIE_NAME)
return res
}
const response = NextResponse.next()
response.cookies.delete(CS_COOKIE_NAME)
return response
}
export const protectMiddleware = async (
oidcToken: string,
req: NextRequest,
res?: NextResponse,
) => {
const ctsSession = req.cookies.get(CS_COOKIE_NAME)?.value
if (oidcToken && ctsSession) {
const ctsToken = JSON.parse(ctsSession) as CtsToken
const ctsTokenSubject = getSubjectFromToken(ctsToken.accessToken)
const oidcTokenSubject = getSubjectFromToken(oidcToken)
if (ctsTokenSubject === oidcTokenSubject) {
logger.debug(
'The JWT token and the CipherStash session are both valid for the same user.',
)
return res ?? NextResponse.next()
}
return await setCtsToken(oidcToken, res)
}
if (oidcToken && !ctsSession) {
logger.debug(
'The JWT token was defined, so the CipherStash session will be set.',
)
return await setCtsToken(oidcToken, res)
}
if (!oidcToken && ctsSession) {
logger.debug(
'The JWT token was undefined, so the CipherStash session was reset.',
)
return resetCtsToken(res)
}
logger.debug(
'The JWT token was undefined, so the CipherStash session was not set.',
)
if (res) {
return res
}
return NextResponse.next()
}