UNPKG

@megaads/express-sso-client

Version:

SSO client middleware for Express applications

215 lines (199 loc) 7.81 kB
import https from 'https'; import express from 'express'; import { Request, Response, NextFunction, RequestHandler } from "express"; import querystring from 'querystring'; import jwt from 'jsonwebtoken'; import { match } from 'path-to-regexp'; const ssoRouter = express.Router(); const tokenExpires = new Set(); declare global { namespace Express { interface Request { session?: { user?: { [key: string]: any } }; user?: { [key: string]: any } } } } export interface ssoOptions { active: boolean; provider: 'session' | 'jwt'; token_options?: { [key: string]: any }; secret?: any; app_id: number; login_url: string; logout_url: string; callback_url: string; auth_url: string; redirect_url?: string; } export function ssoMiddleware(options: ssoOptions, routes: Array<string> = []): RequestHandler { let { active, app_id, login_url, callback_url, provider, secret } = options; if (active && (!app_id || !login_url || !callback_url || !provider)) { throw new Error("Both app_id, login_url, callback_url, provider are required for SSO middleware."); } if (active && provider === 'jwt' && !secret) { throw new Error("Secret is required for JWT provider."); } return function (req: Request, res: Response, next: NextFunction) { if (!active) return next(); const isPathMatched = routes.some(routePath => { const matchPath = match(routePath, { decode: decodeURIComponent }); return matchPath(req.path); }); if (!isPathMatched) { if (provider === 'session' && req.session && req.session.user) { req.user = req.session.user; } return next(); } let params = { app_id: app_id, continue: callback_url }; if (provider === 'session') { if (req.session && req.session.user) { req.user = req.session.user; return next(); } else if (req.xhr) { return res.status(401).json({ error: 'Unauthorized.' }); } else { return res.redirect(login_url + '?' + querystring.stringify(params)); } } else { let token: any = req.headers['x-access-token'] || req.headers['authorization']; if (token) { jwt.verify(token, secret, (err: any, user: any) => { if (err || tokenExpires.has(token)) { return res.status(401).json({ error: 'Unauthorized.' }); } req.user = user; return next(); }); } else if (req.xhr) { return res.status(401).json({ error: 'Unauthorized.' }); } else { return res.redirect(login_url + '?' + querystring.stringify(params)); } } } as RequestHandler; }; export const ssoRouterUrl = (options: ssoOptions) => { ssoRouter.get('/sso/callback', (req: Request, res: Response) => { let { active, app_id, redirect_url, auth_url, provider, secret, token_options } = options; if (!app_id || !auth_url) { throw new Error("Both app_id, auth_url are required for SSO callback."); } provider = provider || 'session'; if (provider === 'jwt' && !secret) { throw new Error("Secret is required for JWT provider."); } if (!active) { res.status(406).send("SSO is disabled."); } else { let postData = JSON.stringify({ app_id: app_id, token: req.query.token, ip: req.ip, domain: req.get('host'), userAgent: req.get('User-Agent') }); let request_url = new URL(auth_url); const request = https.request({ hostname: request_url.hostname, port: request_url.port || 443, path: request_url.pathname, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData), } }, (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { try { const verify = JSON.parse(data); if (verify.status === 'success' && verify.user) { if (provider === 'session') { if (req.session) { req.session.user = verify.user; } if (redirect_url) { res.redirect(redirect_url) } else { res.json(verify.user) } } else { // jwt token_options = token_options || { expiresIn: '24h' }; const token = jwt.sign(verify.user, secret, token_options); res.json({ token: token, user: verify.user }); } } else { res.status(401).send("Unauthorized"); } } catch (error) { console.error("Error parsing response:", error); res.status(500).send("An error occurred during token verification."); } }); }); request.on('error', (error) => { console.error("Request error verify SSO:", error); res.status(500).send("An error occurred while verifying the token."); }); request.write(postData); request.end(); } }); ssoRouter.get('/sso/logout', (req: Request, res: Response) => { let { active, app_id, logout_url, redirect_url, provider, secret } = options; if (!app_id || !logout_url || !redirect_url) { throw new Error("Both app_id, logout_url, redirect_url are required for SSO callback."); } provider = provider || 'session'; if (!active) { res.status(406).send("SSO is disabled."); } else { if (provider === 'session') { if (req.session && req.session.user) { delete req.session.user; } } else { // jwt let token: any = req.headers['x-access-token'] || req.headers['authorization']; if (token) { jwt.verify(token, secret, (err: any, user: any) => { if (!err && !tokenExpires.has(token)) { tokenExpires.add(token); } }); } } let params = { app_id: app_id, continue: redirect_url }; res.redirect(logout_url + '?' + querystring.stringify(params)); } }); ssoRouter.get('/sso/reset-token', (req: Request, res: Response) => { tokenExpires.clear(); }); return ssoRouter; }; export default { ssoMiddleware, ssoRouterUrl };