import { Request , Server } from '@hapi/hapi' ; import hapiAuthCookie from '@hapi/cookie' ; import { OpenSearchDashboardsRequest , ensureRawRequest } from './router' ; import { SessionStorageFactory , SessionStorage } from './session_storage' ; import { Logger } from '..' ; public export interface SessionStorageCookieOptions < T > { /**
     * Name of the session cookie.
     */ name : string ; /**
     * A key used to encrypt a cookie's value. Should be at least 32 characters long.
     */ encryptionKey : string ; /**
     * Function called to validate a cookie's decrypted value.
     */ validate : ( sessionValue : T | T [ ] ) => SessionCookieValidationResult ; /**
     * Flag indicating whether the cookie should be sent only via a secure connection.
     */ isSecure : boolean ; /**
     * Defines SameSite attribute of the Set-Cookie Header.
     * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
     */ sameSite ? : 'Strict' | 'Lax' | 'None' ; } public export interface SessionCookieValidationResult { /**
     * Whether the cookie is valid or not.
     */ isValid : boolean ; /**
     * The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it.
     */ path ? : string ; } class ScopedCookieSessionStorage < T extends Record < string , any > > implements SessionStorage < T > { constructor ( private readonly log , private readonly server , private readonly request ) { } public async get ( ) { try { const session = await this . server . auth . test ( 'security-cookie' , this . request ) ; if ( ! Array . isArray ( session ) ) { return session . credentials as T ; } if ( session . length === 1 ) { return session [ 0 ] as T ; } this . log . warn ( `Found ${ session . length } auth sessions when we were only expecting 1.` ) ; return null ; } catch ( error ) { this . log . debug ( String ( error ) ) ; return null ; } } public set ( sessionValue ) { return this . request . cookieAuth . set ( sessionValue ) ; } public clear ( ) { return this . request . cookieAuth . clear ( ) ; } } function validateOptions ( options ) { if ( options . sameSite === 'None' && options . isSecure !== true ) { throw new Error ( '"SameSite: None" requires Secure connection' ) ; } } param server param cookieOptions export async function createCookieSessionStorageFactory < T > ( log , server , cookieOptions , basePath ? ) { validateOptions ( cookieOptions ) ; function clearInvalidCookie ( req , path = basePath || '/' ) { log . debug ( 'Clearing invalid session cookie' ) ; if ( req ) { ( req . cookieAuth as any ) . h . unstate ( cookieOptions . name , { path } ) ; } } await server . register ( { plugin : hapiAuthCookie } ) ; server . auth . strategy ( 'security-cookie' , 'cookie' , { cookie : { name : cookieOptions . name , password : cookieOptions . encryptionKey , isSecure : cookieOptions . isSecure , path : basePath === undefined ? '/' : basePath , clearInvalid : false , isHttpOnly : true , isSameSite : cookieOptions . sameSite ?? false , } , validateFunc : async ( req , session ) => { const result = cookieOptions . validate ( session ) ; if ( ! result . isValid ) { clearInvalidCookie ( req , result . path ) ; } return { valid : result . isValid } ; } , } ) ; return { asScoped ( request ) { return new ScopedCookieSessionStorage < T > ( log , server , ensureRawRequest ( request ) ) ; } , } ; }