UNPKG

openam-agent-custom

Version:

Customized ForgeRock AM Policy Agent for Node.js from Zoltan Tarcsay

138 lines (109 loc) 4.45 kB
import { IncomingMessage, ServerResponse } from 'http'; import { ShieldEvaluationError } from '../error/shield-evaluation-error'; import { PolicyAgent } from '../policyagent/policy-agent'; import { Deferred } from '../utils/deferred'; import { redirect } from '../utils/http-utils'; import { SessionData } from './session-data'; import { Shield } from './shield'; export interface CookieShieldOptions { /** * If true, the agent will not redirect to OpenAM's login page for authentication, only return a 401 response */ noRedirect?: boolean; /** * If true, the agent will fetch and cache the user's profile when validating the session */ getProfiles?: boolean; /** * If true, the shield will not enforce valid sessions. This is useful in conjunction with {getProfiles:true * when a route is public but you want fetch identity information for any logged in users. */ passThrough?: boolean; /** * Enable CDSSO mode (you must also mount the agent.cdsso() middleware to your application) */ cdsso?: boolean; } /** * Shield implementation for validating session cookies. This shield checks if the request contains a session cookie * and validates it against OpenAM. The session is cached if notifications are enabled, otherwise it's re-validated for * every request. * * The returned Promise is wrapped in a Deferred object. This is because in case of a redirect to login, the Promise * must not be resolved (if the Promise is resolved, the agent treats it as success). A redirect is neither success * nor failure. */ export class CookieShield implements Shield { constructor(readonly options: CookieShieldOptions = {}) {} async evaluate(req: IncomingMessage, res: ServerResponse, agent: PolicyAgent): Promise<SessionData> { const deferred = new Deferred<SessionData>(); try { const sessionId = await agent.getSessionIdFromRequest(req); const sessionData = await this.handleSessionCookie(req, res, agent, sessionId); if (sessionData) { deferred.resolve({ key: sessionId, data: sessionData }); } else { this.redirectToLogin(req, res, agent); } } catch (err) { agent.logger.debug(`CookieShield: ${err.message} ${err.stack}`); if (err instanceof ShieldEvaluationError) { throw err; } throw new ShieldEvaluationError( err.statusCode || err.status || 500, err.name || err.message, err.stack ); } return await deferred.promise; } private async handleSessionCookie(req: IncomingMessage, res: ServerResponse, agent: PolicyAgent, sessionId: string): Promise<any> { const validationResponse = await agent.validateSession(sessionId); const { valid, dn, uid, realm } = validationResponse; if (valid) { agent.logger.info(`CookieShield: ${req.url} => allow`); if (dn && this.options.getProfiles) { const profile = await agent.getUserProfile(uid, realm, sessionId); return { ...validationResponse, ...profile }; } return validationResponse; } // pass-through: no need to enforce a valid session if (this.options.passThrough) { agent.logger.info(`CookieShield: ${req.url} => pass-through`); return {}; } agent.logger.info(`CookieShield: ${req.url} => deny`); // no-redirect: return a 401 error response if (this.options.noRedirect) { throw new ShieldEvaluationError(401, 'Unauthorized', 'Invalid session'); } // cdsso: return false so can be redirected to login page. if (this.options.cdsso) { return false; } if (!(await this.checkDomainMatch(req, agent))) { throw new ShieldEvaluationError(400, 'Bad Request', 'Domain mismatch'); } } private async checkDomainMatch(req: IncomingMessage, agent: PolicyAgent): Promise<boolean> { let domainMatch = false; const { domains } = await agent.getServerInfo(); for (const domain of domains) { if (req.headers.host.includes(domain)) { domainMatch = true; break; } } return domainMatch; } private redirectToLogin(req: IncomingMessage, res: ServerResponse, agent: PolicyAgent): void { redirect(res, this.options.cdsso ? agent.getCDSSOUrl(req) : agent.getLoginUrl(req)); } }