From 7a0a918e3faf6f7bec301cabceb2058a3992527c Mon Sep 17 00:00:00 2001 From: Simon Canning Date: Tue, 24 Dec 2024 09:44:27 +1000 Subject: [PATCH 1/3] Create saml.ts Add saml support using @node-saml/node-saml --- packages/openauth/src/adapter/saml.ts | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 packages/openauth/src/adapter/saml.ts diff --git a/packages/openauth/src/adapter/saml.ts b/packages/openauth/src/adapter/saml.ts new file mode 100644 index 00000000..04909a0b --- /dev/null +++ b/packages/openauth/src/adapter/saml.ts @@ -0,0 +1,101 @@ +import { SAML } from "@node-saml/node-saml" +import { Adapter } from "./adapter.js" +import { getRelativeUrl } from "../util.js" +import { Context } from "hono" +import { InvalidSubjectError } from "../error.js" + +export interface SamlConfig { + type?: string + idpCert: string | string[]; + idpIssuer: string; + idpSignonUrl: string; +} + +export interface SamlClaims { + nameID: string + attributes?: Record, +} + +interface AdapterState { + relayState: string +} + +export function SamlAdapter( + config: SamlConfig, +): Adapter<{ claims: SamlClaims}> { + return { + type: config.type || "saml", + init(routes, ctx) { + routes.get("/authorize", async (c) => { + const saml = getSaml(c, config) + const relayState = crypto.randomUUID() + await ctx.set(c, "adapter", 60 * 10, { + relayState, + }) + const form = await saml.getAuthorizeFormAsync(relayState) + return c.html(form); + }) + + routes.post("/callback", async (c) => { + const saml = getSaml(c, config) + const adapter = (await ctx.get(c, "adapter")) as AdapterState + const formData = await c.req.formData(); + + const relayState = formData.get('RelayState') + if (!adapter || (adapter.relayState && relayState !== adapter.relayState)) { + return c.redirect(getRelativeUrl(c, "./authorize")) + } + + const samlResponse = formData.get('SAMLResponse') + if(samlResponse) { + const p = await saml.validatePostResponseAsync({ + SAMLResponse: samlResponse.toString(), + RelayState: relayState ? relayState.toString() : "", + }) + + if(!p.profile) { + throw new InvalidSubjectError(); + } + + return ctx.success(c, { + claims: { + get nameID() { + return p.profile?.nameID || "" + }, + get attributes() { + const attributes = > {}; + if(p.profile && p.profile["attributes"]) { + for(const attr of Object.entries(p.profile["attributes"])) { + const key = attr[0] + const val = attr[1] + if(typeof val === 'string') { + attributes[key] = [val] + } + if(isNonEmptyArrayOfStrings(val)) { + attributes[key] = val + } + } + return attributes; + } + } + }, + }) + } + }) + }, + } +} + +const isNonEmptyArrayOfStrings = (value: unknown): value is string[] => { + return Array.isArray(value) && value.length > 0 && value.every(item => typeof item === "string"); +} + +const getSaml = (c: Context, config: SamlConfig) => { + return new SAML({ + idpCert: config.idpCert, + issuer: config.idpIssuer, + entryPoint: config.idpSignonUrl, + audience: getRelativeUrl(c, "./authorize"), + callbackUrl: getRelativeUrl(c, "."), + }) +} From 254b68fd05827b467e89730ee9e70856265c838f Mon Sep 17 00:00:00 2001 From: Simon Canning Date: Tue, 24 Dec 2024 09:45:51 +1000 Subject: [PATCH 2/3] Update package.json Add @node-saml/node-saml dependency --- packages/openauth/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/openauth/package.json b/packages/openauth/package.json index 12a712fa..ac3d60b7 100644 --- a/packages/openauth/package.json +++ b/packages/openauth/package.json @@ -37,7 +37,8 @@ "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", - "jose": "5.9.6" + "jose": "5.9.6", + "@node-saml/node-saml": "5.0.0" }, "files": [ "src", From 63c6425db9cef83059d691fcf23fec3210ede8ff Mon Sep 17 00:00:00 2001 From: Simon Canning Date: Tue, 24 Dec 2024 10:40:47 +1000 Subject: [PATCH 3/3] Create loud-bags-grab.md --- .changeset/loud-bags-grab.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-bags-grab.md diff --git a/.changeset/loud-bags-grab.md b/.changeset/loud-bags-grab.md new file mode 100644 index 00000000..5283db0e --- /dev/null +++ b/.changeset/loud-bags-grab.md @@ -0,0 +1,5 @@ +--- +"@openauthjs/openauth": patch +--- + +feat: Add SAML support (browser-post)