sc-idp/lib/saml/sps.js
2026-06-01 16:40:54 -05:00

82 lines
2.3 KiB
JavaScript

// SAML service-provider (relying-party) registry. Backed by _idp_saml_sps. The
// SSO endpoint only issues an assertion to a registered SP and only to one of
// its allow-listed ACS URLs -- this is what prevents a forged AuthnRequest from
// having a signed assertion delivered to an attacker-chosen Destination. The
// signing_cert is a PUBLIC X.509 cert (no sealing, unlike client secrets); when
// present and want_authn_requests_signed is set, the SSO endpoint verifies the
// AuthnRequest signature against it.
const db = require("@saltcorn/data/db");
const { TABLE_SAML_SPS } = require("../constants");
// True if the requested ACS exactly matches one of the SP's allow-listed URLs.
// Exact-string match is the secure default (no trailing-slash/scheme fuzzing).
const acsAllowed = (row, acs) => {
if (!row || !acs) {
return false;
}
let list = [];
try {
list = JSON.parse(row.acs_urls);
} catch (e) {
return false;
}
return Array.isArray(list) && list.includes(acs);
};
const acsUrls = (row) => {
try {
const list = JSON.parse(row.acs_urls);
return Array.isArray(list) ? list : [];
} catch (e) {
return [];
}
};
const createSp = async (opts) => {
await db.insert(TABLE_SAML_SPS, {
entity_id: opts.entityId,
label: opts.label || null,
acs_urls: JSON.stringify(opts.acsUrls || []),
signing_cert: opts.signingCert || null,
want_authn_requests_signed: opts.wantSigned ? 1 : 0,
created_at: new Date().toISOString()
}, { noid: true });
return { entity_id: opts.entityId };
};
const deleteSp = async (entityId) => {
await db.deleteWhere(TABLE_SAML_SPS, { entity_id: entityId });
};
const getSp = async (entityId) => {
return await db.selectMaybeOne(TABLE_SAML_SPS, { entity_id: entityId });
};
const listSps = async () => {
return await db.select(TABLE_SAML_SPS, {}, { orderBy: "entity_id" });
};
// Coerce the stored INTEGER 0/1 (sqlite) or boolean (pg driver) to a real bool.
const wantsSignedRequests = (row) => {
return !!(row && row.want_authn_requests_signed);
};
module.exports = {
acsAllowed,
acsUrls,
createSp,
deleteSp,
getSp,
listSps,
wantsSignedRequests
};