89 lines
3.7 KiB
JavaScript
89 lines
3.7 KiB
JavaScript
// OIDC endpoints served by oidc-provider, mounted under IDP_BASE_PATH via
|
|
// delegation. We strip the /idp prefix so oidc-provider's mount-relative router
|
|
// matches, keep req.originalUrl so it derives the mount path, and (for POST)
|
|
// re-stream the body that Saltcorn/Express already parsed, since oidc-provider
|
|
// reads the raw request stream.
|
|
|
|
const { Readable } = require("stream");
|
|
|
|
const constants = require("../constants");
|
|
|
|
const { getProviderEntry } = require("./provider");
|
|
|
|
|
|
// Saltcorn's body parser drains the POST stream, so rebuild a readable carrying
|
|
// the body (the exact rawBody if available, else re-encoded req.body) for
|
|
// oidc-provider's own parser. Copies the IncomingMessage props Koa/raw-body use,
|
|
// with the mount-relative url and a corrected content-length.
|
|
const restreamBody = (req, strippedUrl, fullUrl) => {
|
|
let buf;
|
|
let contentLength;
|
|
if (req.rawBody && req.rawBody.length) {
|
|
buf = req.rawBody;
|
|
contentLength = String(req.headers["content-length"] || buf.length);
|
|
} else if (req.body && typeof req.body === "object" && Object.keys(req.body).length) {
|
|
const ct = String(req.headers["content-type"] || "");
|
|
buf = ct.includes("application/json")
|
|
? Buffer.from(JSON.stringify(req.body))
|
|
: Buffer.from(new URLSearchParams(req.body).toString());
|
|
contentLength = String(buf.length);
|
|
} else {
|
|
buf = Buffer.alloc(0);
|
|
contentLength = "0";
|
|
}
|
|
const stream = new Readable({ read() {} });
|
|
stream.push(buf);
|
|
stream.push(null);
|
|
stream.headers = Object.assign({}, req.headers, { "content-length": contentLength });
|
|
stream.rawHeaders = req.rawHeaders;
|
|
stream.method = req.method;
|
|
stream.url = strippedUrl;
|
|
stream.originalUrl = fullUrl;
|
|
stream.httpVersion = req.httpVersion;
|
|
stream.httpVersionMajor = req.httpVersionMajor;
|
|
stream.httpVersionMinor = req.httpVersionMinor;
|
|
stream.socket = req.socket;
|
|
stream.connection = req.connection;
|
|
stream.complete = false;
|
|
return stream;
|
|
};
|
|
|
|
|
|
const delegate = async (req, res) => {
|
|
try {
|
|
const entry = await getProviderEntry(req);
|
|
const fullUrl = req.originalUrl || req.url;
|
|
const stripped = fullUrl.slice(constants.IDP_BASE_PATH.length) || "/";
|
|
let target = req;
|
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
req.url = stripped;
|
|
} else {
|
|
target = restreamBody(req, stripped, fullUrl);
|
|
}
|
|
await entry.handler(target, res);
|
|
} catch (e) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(`[${constants.PLUGIN_NAME}] oidc delegate failed:`, e);
|
|
if (!res.headersSent) {
|
|
res.status(500).json({ error: "server_error" });
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
const oidcRoutes = [
|
|
{ url: constants.WELL_KNOWN_OPENID, method: "get", callback: delegate, noCsrf: true },
|
|
{ url: constants.JWKS_PATH, method: "get", callback: delegate, noCsrf: true },
|
|
{ url: constants.AUTH_PATH, method: "get", callback: delegate, noCsrf: true },
|
|
{ url: constants.AUTH_PATH, method: "post", callback: delegate, noCsrf: true },
|
|
{ url: constants.AUTH_RESUME_PATH, method: "get", callback: delegate, noCsrf: true },
|
|
{ url: constants.AUTH_RESUME_PATH, method: "post", callback: delegate, noCsrf: true },
|
|
{ url: constants.TOKEN_PATH, method: "post", callback: delegate, noCsrf: true },
|
|
{ url: constants.USERINFO_PATH, method: "get", callback: delegate, noCsrf: true },
|
|
{ url: constants.USERINFO_PATH, method: "post", callback: delegate, noCsrf: true }
|
|
];
|
|
|
|
|
|
module.exports = {
|
|
oidcRoutes
|
|
};
|