@hellocoop/api
Version:
Client API for Hellō https://hello.dev
182 lines (181 loc) • 7.48 kB
JavaScript
"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;