UNPKG

@hellocoop/api

Version:

Client API for Hellō https://hello.dev

182 lines (181 loc) 7.48 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const config_1 = __importDefault(require("../lib/config")); const oidc_1 = require("../lib/oidc"); const helper_server_1 = require("@hellocoop/helper-server"); const auth_1 = require("../lib/auth"); const definitions_1 = require("@hellocoop/definitions"); // export const getCallbackRequest = (req: HelloRequest): CallbackRequest => { // return { // getHeaders: () => { return req.headers() } // } // } // export const getCallbackResponse = (res: HelloResponse): CallbackResponse => { // return { // getHeaders: () => { return res.getHeaders() }, // setHeader: (key: string, value: string | string[]) => { res.setHeader(key, value) }, // setCookie: (key: string, value: string, options: any) => { res.setCookie(key, value, options) }, // } // } const sendErrorPage = (error, target_uri, res) => { (0, auth_1.clearAuthCookie)(res); // note that we send errors to the target_uri if it was passed in the original request const error_uri = target_uri || config_1.default.routes.error; if (error_uri) { const [pathString, queryString] = error_uri.split('?'); const searchParams = new URLSearchParams(queryString); for (const key in error) { if (key.startsWith('error')) { searchParams.set(key, error[key]); } } const url = pathString + '?' + searchParams.toString(); return res.redirect(url); } const params = { error: error.error, error_description: error.error_description, error_uri: error.error_uri, target_uri: config_1.default.routes.loggedIn || '/' }; const page = (0, helper_server_1.errorPage)(params); res.send(page); }; const handleCallback = async (req, res) => { const { code, error, same_site, wildcard_domain, app_name, } = req.query; if (config_1.default.sameSiteStrict && !same_site) // we need to bounce so we get cookies return res.send((0, helper_server_1.sameSiteCallback)()); const oidcState = await (0, oidc_1.getOidc)(req, res); if (!oidcState) return sendErrorPage({ error: 'invalid_request', error_description: 'OpenID Connect cookie lost' }, '', res); const { code_verifier, nonce, redirect_uri, } = oidcState; let { target_uri } = oidcState; if (error) return sendErrorPage(req.query, target_uri, res); if (!code) return sendErrorPage({ error: 'invalid_request', error_description: 'Missing code parameter' }, target_uri, res); if (Array.isArray(code)) return sendErrorPage({ error: 'invalid_request', error_description: 'Received more than one code', }, target_uri, res); if (!code_verifier) { sendErrorPage({ error: 'invalid_request', error_description: 'Missing code_verifier from session', }, target_uri, res); return; } try { (0, oidc_1.clearOidcCookie)(res); // clear cookie so we don't try to use code again const token = await (0, helper_server_1.fetchToken)({ code: code.toString(), wallet: config_1.default.helloWallet, code_verifier, redirect_uri, client_id: config_1.default.clientId }); const { payload } = (0, helper_server_1.parseToken)(token); if (payload.aud != config_1.default.clientId) { return sendErrorPage({ error: 'invalid_client', error_description: 'Wrong ID token audience', }, target_uri, res); } if (payload.nonce != nonce) { return sendErrorPage({ error: 'invalid_request', error_description: 'Wrong nonce in ID token', }, target_uri, res); } const currentTimeInt = Math.floor(Date.now() / 1000); if (payload.exp < currentTimeInt) { return sendErrorPage({ error: 'invalid_request', error_description: 'The ID token has expired.', }, target_uri, res); } if (payload.iat > currentTimeInt + 5) { // 5 seconds of clock skew return sendErrorPage({ error: 'invalid_request', error_description: 'The ID token is not yet valid', }, target_uri, res); } let auth = { isLoggedIn: true, sub: payload.sub, iat: payload.iat }; definitions_1.VALID_IDENTITY_CLAIMS.forEach((claim) => { const value = payload[claim]; if (value) auth[claim] = value; }); if (auth.isLoggedIn && payload.org) auth.org = payload.org; if (config_1.default?.loginSync) { try { if (config_1.default.logDebug) console.log('\n@hellocoop/api loginSync passing:\n', JSON.stringify({ payload, target_uri }, null, 2)); const cb = await req.loginSyncWrapper(config_1.default.loginSync, { token, payload, target_uri }); if (config_1.default.logDebug) console.log('\n@hellocoop/api loginSync returned:\n', JSON.stringify(cb, null, 2)); target_uri = cb?.target_uri || target_uri; if (cb?.accessDenied) { return sendErrorPage({ error: 'access_denied', error_description: 'loginSync denied access', }, target_uri, res); } else if (cb?.updatedAuth) { auth = { ...cb.updatedAuth, isLoggedIn: true, sub: payload.sub, iat: payload.iat }; } } catch (e) { console.error(new Error('callback faulted')); console.error(e); return sendErrorPage({ error: 'server_error', error_description: 'loginSync failed', }, target_uri, res); } } if (wildcard_domain) { // the redirect_uri is not registered at Hellō - prompt to add const appName = (Array.isArray(app_name) ? app_name[0] : app_name) || 'Your App'; const queryString = new URLSearchParams({ uri: Array.isArray(wildcard_domain) ? wildcard_domain[0] : wildcard_domain, appName, redirectURI: redirect_uri, targetURI: target_uri, wildcard_console: 'true' }).toString(); target_uri = config_1.default.apiRoute + '?' + queryString; } target_uri = target_uri || config_1.default.routes.loggedIn || '/'; await (0, auth_1.saveAuthCookie)(res, auth); if (config_1.default.sameSiteStrict) res.json({ target_uri }); else res.redirect(target_uri); } catch (error) { (0, oidc_1.clearOidcCookie)(res); return res.status(500).send(error.message); } }; exports.default = handleCallback;