import type { IncomingHttpHeaders } from "http" ; import type { CookiesOptions } from "../.." ; import type { CookieOption , LoggerInstance , SessionStrategy } from "../types" ; const ALLOWED_COOKIE_SIZE = 4096 ; const ESTIMATED_EMPTY_COOKIE_SIZE = 163 ; const CHUNK_SIZE = ALLOWED_COOKIE_SIZE - ESTIMATED_EMPTY_COOKIE_SIZE ; /** Stringified form of `JWT`. Extract the content with `jwt.decode` */ export type JWTString = string ; export type SetCookieOptions = Partial < CookieOption [ "options" ] > & { expires ? : Date | string ; encode ? : ( val : unknown ) => string ; } ; /**
 * If `options.session.strategy` is set to `jwt`, this is a stringified `JWT`.
 * In case of `strategy: "database"`, this is the `sessionToken` of the session in the database.
 */ export type SessionToken < T extends SessionStrategy = "jwt" > = T extends "jwt" ? JWTString : string ; TODO export function defaultCookies ( useSecureCookies ) { const cookiePrefix = useSecureCookies ? "__Secure-" : "" ; return { sessionToken : { name : `${ cookiePrefix }next-auth.session-token` , options : { httpOnly : true , sameSite : "lax" , path : "/" , secure : useSecureCookies , } , } , callbackUrl : { name : `${ cookiePrefix }next-auth.callback-url` , options : { sameSite : "lax" , path : "/" , secure : useSecureCookies , } , } , csrfToken : { name : `${ useSecureCookies ? "__Host-" : "" }next-auth.csrf-token` , options : { httpOnly : true , sameSite : "lax" , path : "/" , secure : useSecureCookies , } , } , pkceCodeVerifier : { name : `${ cookiePrefix }next-auth.pkce.code_verifier` , options : { httpOnly : true , sameSite : "lax" , path : "/" , secure : useSecureCookies , } , } , state : { name : `${ cookiePrefix }next-auth.state` , options : { httpOnly : true , sameSite : "lax" , path : "/" , secure : useSecureCookies , } , } , } ; } export interface Cookie extends CookieOption { value : string ; } type Chunks = Record < string , string > ; export class SessionStore { #chunks : Chunks = { } ; #option : CookieOption ; #logger : LoggerInstance | Console ; constructor ( option , req , logger ) { this . #logger = logger ; this . #option = option ; if ( ! req ) return ; for ( const name in req . cookies ) { if ( name . startsWith ( option . name ) ) { this . #chunks [ name ] = req . cookies [ name ] ; } } } get value ( ) { return Object . values ( this . #chunks ) ?. join ( "" ) ; } /** Given a cookie, return a list of cookies, chunked to fit the allowed cookie size. */ #chunk ( cookie ) { const chunkCount = Math . ceil ( cookie . value . length / CHUNK_SIZE ) ; if ( chunkCount === 1 ) { this . #chunks [ cookie . name ] = cookie . value ; return [ cookie ] ; } const cookies = [ ] ; for ( let i = 0 ; i < chunkCount ; i ++ ) { const name = `${ cookie . name }.${ i }` ; const value = cookie . value . substr ( i * CHUNK_SIZE , CHUNK_SIZE ) ; cookies . push ( { ... cookie , name , value } ) ; this . #chunks [ name ] = value ; } this . #logger . debug ( "CHUNKING_SESSION_COOKIE" , { message : `Session cookie exceeds allowed ${ ALLOWED_COOKIE_SIZE } bytes.` , emptyCookieSize : ESTIMATED_EMPTY_COOKIE_SIZE , valueSize : cookie . value . length , chunks : cookies . map ( ( c ) => c . value . length + ESTIMATED_EMPTY_COOKIE_SIZE ) , } ) ; return cookies ; } /** Returns cleaned cookie chunks. */ #clean ( ) { const cleanedChunks = { } ; for ( const name in this . #chunks ) { delete this . #chunks ?. [ name ] ; cleanedChunks [ name ] = { name , value : "" , options : { ... this . #option . options , maxAge : 0 } , } ; } return cleanedChunks ; } /**
     * Given a cookie value, return new cookies, chunked, to fit the allowed cookie size.
     * If the cookie has changed from chunked to unchunked or vice versa,
     * it deletes the old cookies as well.
     */ chunk ( value , options ) { const cookies = this . #clean ( ) ; const chunked = this . #chunk ( { name : this . #option . name , value , options : { ... this . #option . options , ... options } , } ) ; for ( const chunk of chunked ) { cookies [ chunk . name ] = chunk ; } return Object . values ( cookies ) ; } /** Returns a list of cookies that should be cleaned. */ clean ( ) { return Object . values ( this . #clean ( ) ) ; } }