@better-auth/sso 1.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,733 @@
1
+ import {
2
+ afterAll,
3
+ beforeAll,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ vi,
9
+ } from "vitest";
10
+ import { betterAuth } from "better-auth";
11
+ import { memoryAdapter } from "better-auth/adapters/memory";
12
+ import { createAuthClient } from "better-auth/client";
13
+ import { setCookieToHeader } from "better-auth/cookies";
14
+ import { bearer } from "better-auth/plugins";
15
+ import { IdentityProvider, ServiceProvider } from "samlify";
16
+ import { sso } from ".";
17
+ import { ssoClient } from "./client";
18
+ import { createServer } from "http";
19
+ import * as saml from "samlify";
20
+ import express from "express";
21
+ import bodyParser from "body-parser";
22
+ import { randomUUID } from "crypto";
23
+
24
+ const spMetadata = `
25
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:3001/api/sso/saml2/sp/metadata">
26
+ <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
27
+ <md:KeyDescriptor use="signing">
28
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
29
+ <ds:X509Data>
30
+ <ds:X509Certificate>MIIE3jCCAsYCCQDE5FzoAkixzzANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzAeFw0yMzExMTkxMjUyMTVaFw0zMzExMTYxMjUyMTVaMDExCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdGbG9yaWRhMRAwDgYDVQQHDAdPcmxhbmRvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ELJsLZs4yBH7a2U5pA7xw+Oiut7b/ROKh2BqSTKRbEG4xy7WwljT02Mh7GTjLvswtZSUObWFO5v14HNORa3+J9JT2DH+9F+FJ770HX8a3cKYBNQt3xP4IeUyjI3QWzrGtkYPwSZ74tDpAUtuqPAxtoCaZXFDtX6lvCJDqiPnfxRZrKkepYWINSwu4DRpg6KoiPWRCYTsEcCzImInzlACdM97jpG1gLGA6a4dmjalQbRtvC56N0Z56gIhYq2F5JdzB2a10pqoIY8ggXZGIJS9I++8mmdTj6So5pPxLwnCYUhwDew1/DMbi9xIwYozs9pEtHCTn1l34jldDwTziVAxGQZO7QUuoMl997zqcPS7pVWRnfz5odKuytLvQDA0lRVfzOxtqbM3qVhoLT2iDmnuEtlZzgfbt4WEuT2538qxZJkFRpZQIrTj3ybqmWAv36Cp49dfeMwaqjhfX7/mVfbsPMSC653DSZBB+n+Uz0FC3QhH+vIdNhXNAQ5tBseHUR6pXiMnLtI/WVbMvpvFwK2faFTcx1oaP/Qk6yCq66tJvPbnatT9qGF8rdBJmAk9aBdQTI+hAh5mDtDweCrgVL+Tm/+Q85hSl4HGzH/LhLVS478tZVX+o+0yorZ35LCW3e4v8iX+1VEGSdg2ooOWtbSSXK2cYZr8ilyUQp0KueenR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAsonAahruWuHlYbDNQVD0ryhL/b+ttKKqVeT87XYDkvVhlSSSVAKcCwK/UU6z8Ty9dODUkd93Qsbof8fGMlXeYCtDHMRanvWLtk4wVkAMyNkDYHzJ1FbO7v44ZBbqNzSLy2kosbRELlcz+P3/42xumlDqAw/k13tWUdlLDxb0pd8R5yBev6HkIdJBIWtKmUuI+e8F/yTNf5kY7HO1p0NeKdVeZw4Ydw33+BwVxVNmhIxzdP5ZFQv0XRFWhCMo/6RLEepCvWUp/T1WRFqgwAdURaQrvvfpjO/Ls+neht1SWDeP8RRgsDrXIc3gZfaD8q4liIDTZ6HsFi7FmLbZatU8jJ4pCstxQLCvmix+1zF6Fwa9V5OApSTbVqBOsDZbJxeAoSzy5Wx28wufAZT4Kc/OaViXPV5o/ordPs4EYKgd/eNFCgIsZYXe75rYXqnieAIfJEGddsLBpqlgLkwvf5KVS4QNqqX+2YubP63y+3sICq2ScdhO3LZs3nlqQ/SgMiJnCBbDUDZ9GGgJNJVVytcSz5IDQHeflrq/zTt1c4q1DO3CS7mimAnTCjetERRQ3mgY/2hRiuCDFj3Cy7QMjFs3vBsbWrjNWlqyveFmHDRkq34Om7eA2jl3LZ5u7vSm0/ylp/vtoysMjwEmw/0NA3hZPTG3OJxcvFcXBsz0SiFcd1U=</ds:X509Certificate>
31
+ </ds:X509Data>
32
+ </ds:KeyInfo>
33
+ </md:KeyDescriptor>
34
+ <md:KeyDescriptor use="encryption">
35
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
36
+ <ds:X509Data>
37
+ <ds:X509Certificate>MIIE3jCCAsYCCQDE5FzoAkixzzANBgkqhkiG9w0BAQsFADAxMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzAeFw0yMzExMTkxMjUyMTVaFw0zMzExMTYxMjUyMTVaMDExCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdGbG9yaWRhMRAwDgYDVQQHDAdPcmxhbmRvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ELJsLZs4yBH7a2U5pA7xw+Oiut7b/ROKh2BqSTKRbEG4xy7WwljT02Mh7GTjLvswtZSUObWFO5v14HNORa3+J9JT2DH+9F+FJ770HX8a3cKYBNQt3xP4IeUyjI3QWzrGtkYPwSZ74tDpAUtuqPAxtoCaZXFDtX6lvCJDqiPnfxRZrKkepYWINSwu4DRpg6KoiPWRCYTsEcCzImInzlACdM97jpG1gLGA6a4dmjalQbRtvC56N0Z56gIhYq2F5JdzB2a10pqoIY8ggXZGIJS9I++8mmdTj6So5pPxLwnCYUhwDew1/DMbi9xIwYozs9pEtHCTn1l34jldDwTziVAxGQZO7QUuoMl997zqcPS7pVWRnfz5odKuytLvQDA0lRVfzOxtqbM3qVhoLT2iDmnuEtlZzgfbt4WEuT2538qxZJkFRpZQIrTj3ybqmWAv36Cp49dfeMwaqjhfX7/mVfbsPMSC653DSZBB+n+Uz0FC3QhH+vIdNhXNAQ5tBseHUR6pXiMnLtI/WVbMvpvFwK2faFTcx1oaP/Qk6yCq66tJvPbnatT9qGF8rdBJmAk9aBdQTI+hAh5mDtDweCrgVL+Tm/+Q85hSl4HGzH/LhLVS478tZVX+o+0yorZ35LCW3e4v8iX+1VEGSdg2ooOWtbSSXK2cYZr8ilyUQp0KueenR0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAsonAahruWuHlYbDNQVD0ryhL/b+ttKKqVeT87XYDkvVhlSSSVAKcCwK/UU6z8Ty9dODUkd93Qsbof8fGMlXeYCtDHMRanvWLtk4wVkAMyNkDYHzJ1FbO7v44ZBbqNzSLy2kosbRELlcz+P3/42xumlDqAw/k13tWUdlLDxb0pd8R5yBev6HkIdJBIWtKmUuI+e8F/yTNf5kY7HO1p0NeKdVeZw4Ydw33+BwVxVNmhIxzdP5ZFQv0XRFWhCMo/6RLEepCvWUp/T1WRFqgwAdURaQrvvfpjO/Ls+neht1SWDeP8RRgsDrXIc3gZfaD8q4liIDTZ6HsFi7FmLbZatU8jJ4pCstxQLCvmix+1zF6Fwa9V5OApSTbVqBOsDZbJxeAoSzy5Wx28wufAZT4Kc/OaViXPV5o/ordPs4EYKgd/eNFCgIsZYXe75rYXqnieAIfJEGddsLBpqlgLkwvf5KVS4QNqqX+2YubP63y+3sICq2ScdhO3LZs3nlqQ/SgMiJnCBbDUDZ9GGgJNJVVytcSz5IDQHeflrq/zTt1c4q1DO3CS7mimAnTCjetERRQ3mgY/2hRiuCDFj3Cy7QMjFs3vBsbWrjNWlqyveFmHDRkq34Om7eA2jl3LZ5u7vSm0/ylp/vtoysMjwEmw/0NA3hZPTG3OJxcvFcXBsz0SiFcd1U=</ds:X509Certificate>
38
+ </ds:X509Data>
39
+ </ds:KeyInfo>
40
+ </md:KeyDescriptor>
41
+ <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:3001/api/sso/saml2/sp/sls"/>
42
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
43
+ <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:3001/api/sso/saml2/sp/acs" index="1"/>
44
+ <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:3001/api/sso/saml2/sp/acs" index="1"/>
45
+ </md:SPSSODescriptor>
46
+ <md:Organization>
47
+ <md:OrganizationName xml:lang="en-US">Organization Name</md:OrganizationName>
48
+ <md:OrganizationDisplayName xml:lang="en-US">Organization DisplayName</md:OrganizationDisplayName>
49
+ <md:OrganizationURL xml:lang="en-US">http://localhost:3001/</md:OrganizationURL>
50
+ </md:Organization>
51
+ <md:ContactPerson contactType="technical">
52
+ <md:GivenName>Technical Contact Name</md:GivenName>
53
+ <md:EmailAddress>technical_contact@gmail.com</md:EmailAddress>
54
+ </md:ContactPerson>
55
+ <md:ContactPerson contactType="support">
56
+ <md:GivenName>Support Contact Name</md:GivenName>
57
+ <md:EmailAddress>support_contact@gmail.com</md:EmailAddress>
58
+ </md:ContactPerson>
59
+ </md:EntityDescriptor>
60
+ `;
61
+ const idpMetadata = `
62
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8081/api/sso/saml2/idp/metadata">
63
+ <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
64
+ <md:KeyDescriptor use="signing">
65
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
66
+ <ds:X509Data>
67
+ <ds:X509Certificate>MIIFOjCCAyICCQCqP5DN+xQZDjANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzENMAsGA1UECgwEVGVzdDEdMBsGCSqGSIb3DQEJARYOdGVzdEBnbWFpbC5jb20wHhcNMjMxMTE5MTIzNzE3WhcNMzMxMTE2MTIzNzE3WjBfMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzENMAsGA1UECgwEVGVzdDEdMBsGCSqGSIb3DQEJARYOdGVzdEBnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD5giLoLyED41IHt0RxB/k6x4K0vzAKiGecPyedRNR1oyiv3OYkuG5jgTE2wcPZc7kD1Eg5d6th0BWHy/ovaNS5mkgnOV6jKkMaWW4sCMSnLnaWy0seftPK3O4mNeZpM5e9amj2gXnZvKrK8cqnJ/bsUUQvXxttXNVVmOHWg/t3c2vJ4XuUfph6wIKbrj297ILzuAFRNvAVxeS0tElwepvZ5Wbf7Hc1MORAqTpw/mp8cRjHRzYCA9y6OM4hgVs1gvTJS8WGoMmsdAZHaOnv9vLJvW3jDLQQecOheYIJncWgcESzJFIkmXadorYCEfWhwwBdVphknmeLr4BMpJBclAYaFjYDLIKpMcXYO5k/2r3BgSPlw4oqbxbR5geD05myKYtZ/wNUtku118NjhIfJFulU/kfDcp1rYYkvzgBfqr80wgNps4oQzVr1mnpgHsSTAhXMuZbaTByJRmPqecyvyQqRQcRIN0oTLJNGyzoUf0RkH6DKJ4+7qDhlq4Zhlfso9OFMv9xeONfIrJo5HtTfFZfidkXZqir2ZqwqNlNOMfK5DsYq37x2Gkgqig4nqLpITXyxfnQpL2HsaoFrlctt/OL+Zqba7NT4heYk9GX8qlAS+Ipsv6T2HSANbah55oSS3uvcrDOug2Zq7+GYMLKS1IKUKhwX+wLMxmMwSJQ9ZgFwfQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCkGPZdflocTSXIe5bbehsBn/IPdyb38eH2HaAvWqO2XNcDcq+6/uLc8BVK4JMa3AFS9xtBza7MOXN/lw/Ccb8uJGVNUE31+rTvsJaDtMCQkp+9aG04I1BonEHfSB0ANcTy/Gp+4hKyFCd6x35uyPO7CWX5Z8I87q9LF6Dte3/v1j7VZgDjAi9yHpBJv9Xje33AK1vF+WmEfDUOi8y2B8htVeoyS3owln3ZUbnmJdCmMp2BMRq63ymINwklEaYaNrp1L201bSqNdKZF2sNwROWyDX+WFYgufrnzPYb6HS8gYb4oEZmaG5cBM7Hs730/3BlbHKhxNTy1Io2TVCYcMQD+ieiVg5e5eGTwaPYGuVvY3NVhO8FaYBG7K2NT2hqutdCMaQpGyHEzbbbTY1afhbeMmWWqivRnVJNDv4kgBc2SE8JO82qHikIW9Om0cghC5xwTT+1JTtxxD1KeC1M1IwLzzuuMmwJSKAsv4duDqN+YRIP78J2SlrssqlsmoF8+48e7Vzr7JRT/Ya274P8RpUPNtxTR7WDmZ4tunqXjiBpz6l0uTtVXnj5UBo4HCyRjWJOGf15OCuQX03qz8tKn1IbZUf723qrmSF+cxBwHqpAywqhTSsaLjIXKnQ0UlMov7QWb0a5N07JZMdMSerbHvbXd/z9S1Ssea2+EGuTYuQur3A==</ds:X509Certificate>
68
+ </ds:X509Data>
69
+ </ds:KeyInfo>
70
+ </md:KeyDescriptor>
71
+ <md:KeyDescriptor use="encryption">
72
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
73
+ <ds:X509Data>
74
+ <ds:X509Certificate>MIIFOjCCAyICCQCqP5DN+xQZDjANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzENMAsGA1UECgwEVGVzdDEdMBsGCSqGSIb3DQEJARYOdGVzdEBnbWFpbC5jb20wHhcNMjMxMTE5MTIzNzE3WhcNMzMxMTE2MTIzNzE3WjBfMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHRmxvcmlkYTEQMA4GA1UEBwwHT3JsYW5kbzENMAsGA1UECgwEVGVzdDEdMBsGCSqGSIb3DQEJARYOdGVzdEBnbWFpbC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD5giLoLyED41IHt0RxB/k6x4K0vzAKiGecPyedRNR1oyiv3OYkuG5jgTE2wcPZc7kD1Eg5d6th0BWHy/ovaNS5mkgnOV6jKkMaWW4sCMSnLnaWy0seftPK3O4mNeZpM5e9amj2gXnZvKrK8cqnJ/bsUUQvXxttXNVVmOHWg/t3c2vJ4XuUfph6wIKbrj297ILzuAFRNvAVxeS0tElwepvZ5Wbf7Hc1MORAqTpw/mp8cRjHRzYCA9y6OM4hgVs1gvTJS8WGoMmsdAZHaOnv9vLJvW3jDLQQecOheYIJncWgcESzJFIkmXadorYCEfWhwwBdVphknmeLr4BMpJBclAYaFjYDLIKpMcXYO5k/2r3BgSPlw4oqbxbR5geD05myKYtZ/wNUtku118NjhIfJFulU/kfDcp1rYYkvzgBfqr80wgNps4oQzVr1mnpgHsSTAhXMuZbaTByJRmPqecyvyQqRQcRIN0oTLJNGyzoUf0RkH6DKJ4+7qDhlq4Zhlfso9OFMv9xeONfIrJo5HtTfFZfidkXZqir2ZqwqNlNOMfK5DsYq37x2Gkgqig4nqLpITXyxfnQpL2HsaoFrlctt/OL+Zqba7NT4heYk9GX8qlAS+Ipsv6T2HSANbah55oSS3uvcrDOug2Zq7+GYMLKS1IKUKhwX+wLMxmMwSJQ9ZgFwfQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCkGPZdflocTSXIe5bbehsBn/IPdyb38eH2HaAvWqO2XNcDcq+6/uLc8BVK4JMa3AFS9xtBza7MOXN/lw/Ccb8uJGVNUE31+rTvsJaDtMCQkp+9aG04I1BonEHfSB0ANcTy/Gp+4hKyFCd6x35uyPO7CWX5Z8I87q9LF6Dte3/v1j7VZgDjAi9yHpBJv9Xje33AK1vF+WmEfDUOi8y2B8htVeoyS3owln3ZUbnmJdCmMp2BMRq63ymINwklEaYaNrp1L201bSqNdKZF2sNwROWyDX+WFYgufrnzPYb6HS8gYb4oEZmaG5cBM7Hs730/3BlbHKhxNTy1Io2TVCYcMQD+ieiVg5e5eGTwaPYGuVvY3NVhO8FaYBG7K2NT2hqutdCMaQpGyHEzbbbTY1afhbeMmWWqivRnVJNDv4kgBc2SE8JO82qHikIW9Om0cghC5xwTT+1JTtxxD1KeC1M1IwLzzuuMmwJSKAsv4duDqN+YRIP78J2SlrssqlsmoF8+48e7Vzr7JRT/Ya274P8RpUPNtxTR7WDmZ4tunqXjiBpz6l0uTtVXnj5UBo4HCyRjWJOGf15OCuQX03qz8tKn1IbZUf723qrmSF+cxBwHqpAywqhTSsaLjIXKnQ0UlMov7QWb0a5N07JZMdMSerbHvbXd/z9S1Ssea2+EGuTYuQur3A==</ds:X509Certificate>
75
+ </ds:X509Data>
76
+ </ds:KeyInfo>
77
+ </md:KeyDescriptor>
78
+ <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/api/sso/saml2/idp/slo"/>
79
+ <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
80
+ <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8081/api/sso/saml2/idp/redirect"/>
81
+ <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/api/sso/saml2/idp/post"/>
82
+ </md:IDPSSODescriptor>
83
+ <md:Organization>
84
+ <md:OrganizationName xml:lang="en-US">Your Organization Name</md:OrganizationName>
85
+ <md:OrganizationDisplayName xml:lang="en-US">Your Organization DisplayName</md:OrganizationDisplayName>
86
+ <md:OrganizationURL xml:lang="en-US">http://localhost:8081</md:OrganizationURL>
87
+ </md:Organization>
88
+ <md:ContactPerson contactType="technical">
89
+ <md:GivenName>Technical Contact Name</md:GivenName>
90
+ <md:EmailAddress>technical_contact@gmail.com</md:EmailAddress>
91
+ </md:ContactPerson>
92
+ <md:ContactPerson contactType="support">
93
+ <md:GivenName>Support Contact Name</md:GivenName>
94
+ <md:EmailAddress>support_contact@gmail.com</md:EmailAddress>
95
+ </md:ContactPerson>
96
+ </md:EntityDescriptor>
97
+ `;
98
+ const idPk = `
99
+ -----BEGIN RSA PRIVATE KEY-----
100
+ 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
149
+ -----END RSA PRIVATE KEY-----
150
+
151
+ `;
152
+ const spPrivateKey = `
153
+ -----BEGIN RSA PRIVATE KEY-----
154
+ Proc-Type: 4,ENCRYPTED
155
+ DEK-Info: DES-EDE3-CBC,9C86371F0420A091
156
+
157
+ 77TqgiK/IYRgO5w3ZMyV81/gk0zN5wPTGWxoztNFjQKXCySFnrL30kUqlGituBxX
158
+ VgxwXbkoYMrd5MoDZKL5EJuf0H59hq92O0+3uwJA8QyZjOm4brQcjXKmIrkvihgs
159
+ FvpaJiGzp6kS/O7vFBDNTQWr9yY9Y3FBPcmOUWufpRp4Q5nhpSlqnMmIqZyWQUL/
160
+ YJSJETtzJVsk38lCqIxxDT3LtbGySahj0jRuRqspAZQeLTpnJqzNMC4vnJew9luC
161
+ R+UffrX7gVsnwOhNtyRzYaMsLnbRfXT8Jqx2gRHg36GxkOVgyU7e62nk9CzeC0WA
162
+ kHHCNVqqivRx9/EC0mQkkRgRzo3BZWp0o671sUsGTy57JhktiGfTnWMrl7ZfhAza
163
+ SZnjyTwuI1bTQipIkNI3aJBTP/o/gNUE1sj5D5FZlFdpq5ks2Vxww3GNx1FRrvWd
164
+ 98z5CNt78ZR0ihLmdz/EakEBKBUteQu/5zPLUlwmGuou4wPuEHG2BsjGzb/d5Zfc
165
+ ElIjUV+yrMmGHvBfPyPnDUrCUyLn18S1NZiCMCdN5PqCybjhk8oMPYZhWBqp8Ymr
166
+ yHIC7BCnTJhIvgQZR6M68NwVv0aBBgH/I/DB0jADo6/B5Eajwus9i6zSv8QIbqhw
167
+ fusKtI04vxc91aP0GWRr0J/O4mkxXYNPfa3a/I7sGTXGl0k0CygckE3fLXRy/WEk
168
+ ikZt4UHqg5ZQ8vc5NSAM5f5Yx/72CU1I6ehFtxHsyE5yndpZXWp2X2S4l31e8fLs
169
+ ddOoybroJgbyLrh7JT3Yac3XOEsKATWIvqU+hNYq6KwqLWev9jInHVgjzfyOKbmF
170
+ hkrzDDHaKULYZuTsUq5mLc1SzSu98lXYfXp1WE4XsH0X0VicPzf8ZH4Kutuig0VG
171
+ 5Kg9HB/Cin65VMm0ffEiTraO6johIlwFGRrtAs38ONKgsPCQUv7ee9SEGOHViNZq
172
+ NpWPr1KOzbI4wEB1ueKoZuEQ0a+tzfJgszJrM48bM82J6iEjN/PSOTsdTKJq9e47
173
+ dlUp+tqQsvGkbBOIOt5OOpkr8Z+8qbEd21ojF9Q0p0T4WMThRP6YBRKvt8mmFwRs
174
+ DjEhMiPa4L70Eqldfu2lWdI6ietfHrK97WXwQO1gF73LOnA+EdMXNxr1iLd0Tdke
175
+ z6fUSw3hKZL+I7nX6O40+KgkhXVSZOsRz5CEvo2iChIUrYGEGDl94K/ofqGu71Y+
176
+ G8KBvbha6EC7xcUrTYP5Gek5wsrw7cGgDZJjMsyXYFBZjQO1N6g9fncLmc5pB5Ix
177
+ W3gLfQS/My4daWNTvrYOgfA08J4M4ZWd0v5TglxOSV78psG4J4slppDySNFB2d/3
178
+ 7JiwWVm5SMk0StLWwb2azmTvBoinnrZJzPnPlOytxvE5uGJ/i0WAik7C99YgVJkS
179
+ 9hO3FJGasrOnHeiOvMZEdRuIVspKz9iMFx7hWHpVHTTyjwceEpaiEkhmqLM9QkKh
180
+ kCZqeWyVsKBIc0sse+CKNK8ik9eTeUlCklGMV1Q4kKjR6uuHUOLyjk/xhqslV4TS
181
+ jnnjCjsK5YzTa4hmbHhPZIW262KoFV9TqxYKkhP5ab7AXRSakrdrY2cwACWN4AMT
182
+ -----END RSA PRIVATE KEY-----
183
+ `;
184
+ const idpPrivateKey = `
185
+ -----BEGIN RSA PRIVATE KEY-----
186
+ Proc-Type: 4,ENCRYPTED
187
+ DEK-Info: DES-EDE3-CBC,116B0EBB2F2F0A9D
188
+
189
+ HMmUsJPVPTsq1e06yrrskfinY21OOHosfRzibLueBg9ByFFZ7+/oW/DKy1GcDeBc
190
+ ycL+3gylIoGUYuZ+DPC11ArjdxFqLFnHJb96rwy5h4sTP0lE+qHy+06AwsowUgp3
191
+ pdD2unPFeydpu5h/dqgoDzkGSucz0Ty/spHXNBvns0vJO18B7XlzXUtfH5aHco22
192
+ DyVY6FrJwMts9E4Rzs9JsxJJ7mi/6+Qsc0rOr8/6KKsRo1sKD6cvQIQ05dEvGrE9
193
+ /2fubHkRTl+zBqOVyQvC6iUtocwxlMP4KfmyYrD1wlQAnP/+smq2G+xf7uGc4X4P
194
+ 8q0jEy2P9n5ASlwZ3XCS9hZgp8VRAcXWOYjzzNouQp3NEP9d5D3wN4aFKa/JW6pk
195
+ a6VwraEweuyJqvZ7nnam1emW0ge0z7hJabR0+j0PnUxFIwkI5jO3HI5UiuUzuQFe
196
+ 2bTLA3XnJ7QD08ZKom0rmApbFrmm9BWBRTmt46NlQDy49VODPY4gFuQ/mpaFjaBy
197
+ fSNJaOSS/MDuAdPabNEh3l+yCGKtHIbPVIms76PxYf6o0VVxW96/Q25hrvyOJCxn
198
+ dVQyyJbQ1jGenu4ViDNrW9ZQfw4aJCPpY7lUQd09BGz2NMKgkrSl8bKSan4lvlF3
199
+ ok8BjfIw+pIrTyesPU5tF0YudDxwi8fbIG70iwrpsSt2wVIMa+Nz2lwFT1dV8be7
200
+ NARkkkhLWJYAsxsyVfdl+ucNSqhvo8xLITuG8CZnzKf0T2HMKnMNegFx/ipfM7ff
201
+ Mx5CjayN5Oy99MWsagYEutUGzCGPAuVpqYpJuuYa3lWbFk2XWihWkAiUwgRqIluE
202
+ M6LpO8l3LVXVjN1+6bK1GZpbfLay+E6vy4W38XMuXZSNpyhy6e+XggTPH2xbbwoi
203
+ OcAzcojhMaxVGpxm/aXyRxg9zBdrQjtqM/aCN91ri55bvOKxELVi+D/VcZKpd2CR
204
+ X/vWcqoGaK/6+vlPWMZSHCJkPa4KBT0aUcnEdeFWx2nmrwdrHvETzCYLAzVBSECV
205
+ ZoYH0xTkFr/RI2AOAzx701LSuYbnPoCq+w7TXtjPaooZdYVVgrYuI+j4JOlseFS7
206
+ 1c9iRiJVPBfnpUNIZdHLw19+k81IJ/FmumiuDhfLS5pwQmtuXkO3DWZDa3UPlV8e
207
+ 6dmZeP1XGwRLL9VpOKx7NCqZM+CdEt87CXpFFWXdw8tL+3K/2r8w4lHIzBKaVPSS
208
+ 5uFqXc1vzfP6Qeov31IjeLPE1pWTHNqRPdmvt9Scq9tKS3o18wmLBxOVinOE0cxQ
209
+ oddzPd0z5NxNYVayqZORwDdVv6CVXKnrvBSnOFFslZqv1G8/diE5BXxeaAPEMcZE
210
+ 3lD7MzdoEHK5oL2MXofLWZbNtMkOZLaLqY80zKT1UG3Gs8U44d44aLXO1dBL0HGX
211
+ dNfNUaH+IGZf2ccS6OR1RhwIazDZ8qk0XeUwQV588adwC3FUvscVA3eHZa95z4kX
212
+ xvHg+ylzRtKRfpSPzB2IVwgV9/rsOg0OmvwhV8+5IQpdcFr+hf2Bn6AVn6H9aX8A
213
+ JjycN6KMcHaFa0EUqagGm9tsQLmf/MGCj8sy9am1IbRmFCz5lB5A7P/YLPM2Csjg
214
+ -----END RSA PRIVATE KEY-----`;
215
+ const certificate = `
216
+ -----BEGIN CERTIFICATE-----
217
+ MIIDlzCCAn+gAwIBAgIJAO1ymQc33+bWMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
218
+ BAYTAkhLMRMwEQYDVQQIDApTb21lLVN0YXRlMRowGAYDVQQKDBFJZGVudGl0eSBQ
219
+ cm92aWRlcjEUMBIGA1UECwwLRGV2ZWxvcG1lbnQxDDAKBgNVBAMMA0lEUDAeFw0x
220
+ NTA3MDUxODAyMjdaFw0xODA3MDQxODAyMjdaMGIxCzAJBgNVBAYTAkhLMRMwEQYD
221
+ VQQIDApTb21lLVN0YXRlMRowGAYDVQQKDBFJZGVudGl0eSBQcm92aWRlcjEUMBIG
222
+ A1UECwwLRGV2ZWxvcG1lbnQxDDAKBgNVBAMMA0lEUDCCASIwDQYJKoZIhvcNAQEB
223
+ BQADggEPADCCAQoCggEBAODZsWhCe+yG0PalQPTUoD7yko5MTWMCRxJ8hSm2k7mG
224
+ 3Eg/Y2v0EBdCmTw7iDCevRqUmbmFnq7MROyV4eriJzh0KabAdZf7/k6koghst3ZU
225
+ tWOwzshyxkBtWDwGmBpQGTGsKxJ8M1js3aSqNRXBT4OBWM9w2Glt1+8ty30RhYv3
226
+ pSF+/HHLH7Ac+vLSIAlokaFW34RWTcJ/8rADuRWlXih4GfnIu0W/ncm5nTSaJiRA
227
+ vr3dGDRO/khiXoJdbbOj7dHPULxVGbH9IbPK76TCwLbF7ikIMsPovVbTrpyL6vsb
228
+ VUKeEl/5GKppTwp9DLAOeoSYpCYkkDkYKu9TRQjF02MCAwEAAaNQME4wHQYDVR0O
229
+ BBYEFP2ut2AQdy6D1dwdwK740IHmbh38MB8GA1UdIwQYMBaAFP2ut2AQdy6D1dwd
230
+ wK740IHmbh38MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBANMZUoPN
231
+ mHzgja2PYkbvBYMHmpvUkVoiuvQ9cJPlqGTB2CRfG68BNNs/Clz8P7cIrAdkhCUw
232
+ i1rSBhDuslGFNrSaIpv6B10FpBuKwef3G7YrPWFNEN6khY7aHNWSTHqKgs1DrGef
233
+ 2B9hvkrnHWbQVSVXrBFKe1wTCqcgGcOpYoSK7L8C6iX6uIA/uZYnVQ4NgBrizJ0a
234
+ zkjdegz3hwO/gt4malEURy8D85/AAVt6PAzhpb9VJUGxSXr/EfntVUEz3L2gUFWW
235
+ k1CnZFyz0rIOEt/zPmeAY8BLyd/Tjxm4Y+gwNazKq5y9AJS+m858b/nM4QdCnUE4
236
+ yyoWAJDUHiAmvFA=
237
+ -----END CERTIFICATE-----
238
+ `;
239
+ const idpEncyptionKey = `
240
+ -----BEGIN RSA PRIVATE KEY-----
241
+ Proc-Type: 4,ENCRYPTED
242
+ DEK-Info: DES-EDE3-CBC,860FDB9F3BE14699
243
+
244
+ bMpTdWaAEqNciUFQhHYNv1F9N12aqOQd6cFbMozfRnNR19HW6QIPDmEOPSSCaaRy
245
+ QCnJhbpcSnaz9pvI7EzeJzdykDmR8Boos+0NSK9qIX0buBO55mfPr7hjx7bLFEVl
246
+ kkHk+k9F1rLyjyAGJrVoTNoWjyuMOFUCWR7ZxoYticwM/sL+Rbhn1FsfdkdfhFW0
247
+ 08OHTouRK33Aifx0A3MWxR0ILvw49E6urtbbIrskEzKzfWQug8gY1TJhI3sbsMsI
248
+ 1bS5Vg88TvilFFBGn0Yv6GEJjgOrsrKDGKtYGhuBfK4fd4rwnQKKvC6gTKeNXIfV
249
+ 7Qm1R20LUJXC8zv35pdKoVk+NdS/MGNXJRFgO3Kkp01aVf3n1oo2+AllS02AYyWt
250
+ 1svHecsRwbibXip8gSQsOtDdpqQrEDyqZlFHXEw/IcJE9vQWEJmpHD5GFhbKtttp
251
+ E0B3ZtNl6YcyUz0rSf9zjuMx/wReWdRb6H2WoIqoRS7vAUONDRPt7wvfjtLlDRVi
252
+ bc2RTN8yce/57lGnA1n8bxPV5+9VxCJOEipV3io/nrj+uNO8i/0rUpkKdZy8wy2C
253
+ Rksoxq4TxwegONz1HQcJVpJu0iBdu7B+BXVjxQQScvMQlOTbua8k+YdaCeZAb83j
254
+ JVX89/PFy+Xj7eGyzzBTqz7dV0Xkxq9mpiMYUCoyNL5Iq1jD9Xb5TzVW1Gbh8zCZ
255
+ YXjcZEQKeartaBC4/fRWyxqK3gJRX4SJkl4gYMQrPS2pbTzVCO+WLxSwIh3dOZpo
256
+ eErXLSrylIv9cE2Xrs0McXAR+hfGrqgtILBWwgbh2NhmUiFfLwUTUxU51eu7QZ2T
257
+ V1VFBX0QTmn2kM0JLSSC96mDUzbs6qfURUaXbuffF5cqdUjXgtzZj5SFEbIv4UFS
258
+ 0DAS+6i/jTGSz7aAp/uofOxhYkCqK/s2Cex2jQbDpcKXKiWzPdULOCjAh3fdCAp0
259
+ 3ua3fdAI7H8PslSDiPFrcY78OxZaWXzazEiun77WKbzrMloLMP5dpCPlUCOqxbZ0
260
+ ykSuo0M7p/UPY34yi3AMHS9grvQQ1DykMPoqKKEheI6nUGcQ1AFcdr307ILWRsPO
261
+ T6gHOLXZaR4+UEeYfkTKsjrMUhozx7JIyuLgTXA9TWC+tZ9WZpbJ7i3bpQ+RNwX2
262
+ AxQSwc9ZOcNxg8YCbGlJgJHnRVhA202kNT5ORplcRKqaOaO9LK7491gaaShjaspg
263
+ 4THDnH+HHFORmbgwyO9P74wuw+n6tI40Ia3qzRLVz6sJBQMtLEN+cvNoNi3KYkNj
264
+ GJM1iWfSz6PjrEGxbzQZKoFPPiZrVRnVfPhBNyT2OZj+TJii9CaukhmkkA2/AJmS
265
+ 5XoO3GNIaqOGYV9HLyh1++cn3NhjgFYe/Q3ORCTIg2Ltd8Qr6mYe0LcONQFgiv4c
266
+ AUOZtOq05fJDXE74R1JjYHPaQF6uZEbTF98jN9QZIfCEvDdv1nC83MvSwATi0j5S
267
+ LvdU/MSPaZ0VKzPc4JPwv72dveEPME6QyswKx9izioJVrQJr36YtmrhDlKR1WBny
268
+ ISbutnQPUN5fsaIsgKDIV3T7n6519t6brobcW5bdigmf5ebFeZJ16/lYy6V77UM5
269
+ -----END RSA PRIVATE KEY-----
270
+ `;
271
+ const spEncyptionKey = `
272
+ -----BEGIN RSA PRIVATE KEY-----
273
+ Proc-Type: 4,ENCRYPTED
274
+ DEK-Info: DES-EDE3-CBC,860FDB9F3BE14699
275
+
276
+ bMpTdWaAEqNciUFQhHYNv1F9N12aqOQd6cFbMozfRnNR19HW6QIPDmEOPSSCaaRy
277
+ QCnJhbpcSnaz9pvI7EzeJzdykDmR8Boos+0NSK9qIX0buBO55mfPr7hjx7bLFEVl
278
+ kkHk+k9F1rLyjyAGJrVoTNoWjyuMOFUCWR7ZxoYticwM/sL+Rbhn1FsfdkdfhFW0
279
+ 08OHTouRK33Aifx0A3MWxR0ILvw49E6urtbbIrskEzKzfWQug8gY1TJhI3sbsMsI
280
+ 1bS5Vg88TvilFFBGn0Yv6GEJjgOrsrKDGKtYGhuBfK4fd4rwnQKKvC6gTKeNXIfV
281
+ 7Qm1R20LUJXC8zv35pdKoVk+NdS/MGNXJRFgO3Kkp01aVf3n1oo2+AllS02AYyWt
282
+ 1svHecsRwbibXip8gSQsOtDdpqQrEDyqZlFHXEw/IcJE9vQWEJmpHD5GFhbKtttp
283
+ E0B3ZtNl6YcyUz0rSf9zjuMx/wReWdRb6H2WoIqoRS7vAUONDRPt7wvfjtLlDRVi
284
+ bc2RTN8yce/57lGnA1n8bxPV5+9VxCJOEipV3io/nrj+uNO8i/0rUpkKdZy8wy2C
285
+ Rksoxq4TxwegONz1HQcJVpJu0iBdu7B+BXVjxQQScvMQlOTbua8k+YdaCeZAb83j
286
+ JVX89/PFy+Xj7eGyzzBTqz7dV0Xkxq9mpiMYUCoyNL5Iq1jD9Xb5TzVW1Gbh8zCZ
287
+ YXjcZEQKeartaBC4/fRWyxqK3gJRX4SJkl4gYMQrPS2pbTzVCO+WLxSwIh3dOZpo
288
+ eErXLSrylIv9cE2Xrs0McXAR+hfGrqgtILBWwgbh2NhmUiFfLwUTUxU51eu7QZ2T
289
+ V1VFBX0QTmn2kM0JLSSC96mDUzbs6qfURUaXbuffF5cqdUjXgtzZj5SFEbIv4UFS
290
+ 0DAS+6i/jTGSz7aAp/uofOxhYkCqK/s2Cex2jQbDpcKXKiWzPdULOCjAh3fdCAp0
291
+ 3ua3fdAI7H8PslSDiPFrcY78OxZaWXzazEiun77WKbzrMloLMP5dpCPlUCOqxbZ0
292
+ ykSuo0M7p/UPY34yi3AMHS9grvQQ1DykMPoqKKEheI6nUGcQ1AFcdr307ILWRsPO
293
+ T6gHOLXZaR4+UEeYfkTKsjrMUhozx7JIyuLgTXA9TWC+tZ9WZpbJ7i3bpQ+RNwX2
294
+ AxQSwc9ZOcNxg8YCbGlJgJHnRVhA202kNT5ORplcRKqaOaO9LK7491gaaShjaspg
295
+ 4THDnH+HHFORmbgwyO9P74wuw+n6tI40Ia3qzRLVz6sJBQMtLEN+cvNoNi3KYkNj
296
+ GJM1iWfSz6PjrEGxbzQZKoFPPiZrVRnVfPhBNyT2OZj+TJii9CaukhmkkA2/AJmS
297
+ 5XoO3GNIaqOGYV9HLyh1++cn3NhjgFYe/Q3ORCTIg2Ltd8Qr6mYe0LcONQFgiv4c
298
+ AUOZtOq05fJDXE74R1JjYHPaQF6uZEbTF98jN9QZIfCEvDdv1nC83MvSwATi0j5S
299
+ LvdU/MSPaZ0VKzPc4JPwv72dveEPME6QyswKx9izioJVrQJr36YtmrhDlKR1WBny
300
+ ISbutnQPUN5fsaIsgKDIV3T7n6519t6brobcW5bdigmf5ebFeZJ16/lYy6V77UM5
301
+ -----END RSA PRIVATE KEY-----
302
+ `;
303
+ const generateRequestID = () => {
304
+ return "_" + randomUUID();
305
+ };
306
+ const createTemplateCallback =
307
+ (idp: any, sp: any, email: string) => (template: any) => {
308
+ const assertionConsumerServiceUrl =
309
+ sp.entityMeta.getAssertionConsumerService(
310
+ saml.Constants.wording.binding.post,
311
+ );
312
+
313
+ const nameIDFormat = idp.entitySetting.nameIDFormat;
314
+ const selectedNameIDFormat = Array.isArray(nameIDFormat)
315
+ ? nameIDFormat[0]
316
+ : nameIDFormat;
317
+
318
+ const id = generateRequestID();
319
+ const now = new Date();
320
+ const fiveMinutesLater = new Date(now.getTime() + 5 * 60 * 1000);
321
+ const tagValues = {
322
+ ID: id,
323
+ AssertionID: generateRequestID(),
324
+ Destination: assertionConsumerServiceUrl,
325
+ Audience: sp.entityMeta.getEntityID(),
326
+ EntityID: sp.entityMeta.getEntityID(),
327
+ SubjectRecipient: assertionConsumerServiceUrl,
328
+ Issuer: idp.entityMeta.getEntityID(),
329
+ IssueInstant: now.toISOString(),
330
+ AssertionConsumerServiceURL: assertionConsumerServiceUrl,
331
+ StatusCode: "urn:oasis:names:tc:SAML:2.0:status:Success",
332
+ ConditionsNotBefore: now.toISOString(),
333
+ ConditionsNotOnOrAfter: fiveMinutesLater.toISOString(),
334
+ SubjectConfirmationDataNotOnOrAfter: fiveMinutesLater.toISOString(),
335
+ NameIDFormat: selectedNameIDFormat,
336
+ NameID: email,
337
+ InResponseTo: "null",
338
+ AuthnStatement: "",
339
+ attrFirstName: "Test",
340
+ attrLastName: "User",
341
+ attrEmail: "test@email.com",
342
+ };
343
+
344
+ return {
345
+ id,
346
+ context: saml.SamlLib.replaceTagsByValue(template, tagValues),
347
+ };
348
+ };
349
+ class MockSAMLIdP {
350
+ private app: express.Application;
351
+ private server: ReturnType<typeof createServer> | undefined;
352
+ private port: number;
353
+ private idp: ReturnType<typeof IdentityProvider>;
354
+ private sp: ReturnType<typeof ServiceProvider>;
355
+ constructor(port: number) {
356
+ this.port = port;
357
+ this.app = express();
358
+ this.app.use(bodyParser.urlencoded({ extended: true }));
359
+ this.app.use(bodyParser.json());
360
+
361
+ this.idp = IdentityProvider({
362
+ metadata: idpMetadata,
363
+ privateKey: idPk,
364
+ isAssertionEncrypted: false,
365
+ privateKeyPass: "jXmKf9By6ruLnUdRo90G",
366
+ loginResponseTemplate: {
367
+ context:
368
+ '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="{ID}" Version="2.0" IssueInstant="{IssueInstant}" Destination="{Destination}" InResponseTo="{InResponseTo}"><saml:Issuer>{Issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="{StatusCode}"/></samlp:Status><saml:Assertion ID="{AssertionID}" Version="2.0" IssueInstant="{IssueInstant}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"><saml:Issuer>{Issuer}</saml:Issuer><saml:Subject><saml:NameID Format="{NameIDFormat}">{NameID}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="{SubjectConfirmationDataNotOnOrAfter}" Recipient="{SubjectRecipient}" InResponseTo="{InResponseTo}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="{ConditionsNotBefore}" NotOnOrAfter="{ConditionsNotOnOrAfter}"><saml:AudienceRestriction><saml:Audience>{Audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions>{AttributeStatement}</saml:Assertion></samlp:Response>',
369
+ attributes: [
370
+ {
371
+ name: "firstName",
372
+ valueTag: "firstName",
373
+ nameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
374
+ valueXsiType: "xs:string",
375
+ },
376
+ {
377
+ name: "lastName",
378
+ valueTag: "lastName",
379
+ nameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
380
+ valueXsiType: "xs:string",
381
+ },
382
+ {
383
+ name: "email",
384
+ valueTag: "email",
385
+ nameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic",
386
+ valueXsiType: "xs:string",
387
+ },
388
+ ],
389
+ },
390
+ });
391
+ this.sp = ServiceProvider({
392
+ metadata: spMetadata,
393
+ });
394
+ this.app.get("/api/sso/saml2/idp/post", async (req, res) => {
395
+ const user = { emailAddress: "test@email.com", famName: "hello world" };
396
+ const { context, entityEndpoint } = await this.idp.createLoginResponse(
397
+ this.sp,
398
+ {} as any,
399
+ saml.Constants.wording.binding.post,
400
+ user,
401
+ createTemplateCallback(this.idp, this.sp, user.emailAddress),
402
+ );
403
+ res.status(200).send({ samlResponse: context, entityEndpoint });
404
+ });
405
+ this.app.get("/api/sso/saml2/idp/redirect", async (req, res) => {
406
+ const user = { emailAddress: "test@email.com", famName: "hello world" };
407
+ const { context, entityEndpoint } = await this.idp.createLoginResponse(
408
+ this.sp,
409
+ {} as any,
410
+ saml.Constants.wording.binding.post,
411
+ user,
412
+ createTemplateCallback(this.idp, this.sp, user.emailAddress),
413
+ );
414
+ res.status(200).send({ samlResponse: context, entityEndpoint });
415
+ });
416
+ // @ts-ignore
417
+ this.app.post("/api/sso/saml2/sp/acs", async (req, res) => {
418
+ try {
419
+ const parseResult = await this.sp.parseLoginResponse(
420
+ this.idp,
421
+ saml.Constants.wording.binding.post,
422
+ req,
423
+ );
424
+ const { extract } = parseResult;
425
+ const { attributes } = extract;
426
+ const relayState = req.body.RelayState;
427
+ if (relayState) {
428
+ return res.status(200).send({ relayState, attributes });
429
+ } else {
430
+ return res
431
+ .status(200)
432
+ .send({ extract, message: "RelayState is missing." });
433
+ }
434
+ } catch (error) {
435
+ console.error("Error handling SAML ACS endpoint:", error);
436
+ res.status(500).send({ error: "Failed to process SAML response." });
437
+ }
438
+ });
439
+ }
440
+
441
+ start() {
442
+ return new Promise<void>((resolve) => {
443
+ this.app.use(bodyParser.urlencoded({ extended: true }));
444
+ this.server = this.app.listen(this.port, () => {
445
+ console.log(`Mock SAML IdP running on port ${this.port}`);
446
+ resolve();
447
+ });
448
+ });
449
+ }
450
+
451
+ stop() {
452
+ return new Promise<void>((resolve, reject) => {
453
+ this.app.use(bodyParser.urlencoded({ extended: true }));
454
+ this.server?.close((err) => {
455
+ if (err) reject(err);
456
+ else resolve();
457
+ });
458
+ });
459
+ }
460
+
461
+ get metadataUrl() {
462
+ return `http://localhost:${this.port}/idp/metadata`;
463
+ }
464
+ }
465
+
466
+ describe("SAML SSO", async () => {
467
+ const data = {
468
+ user: [],
469
+ session: [],
470
+ verification: [],
471
+ account: [],
472
+ ssoProvider: [],
473
+ };
474
+
475
+ const memory = memoryAdapter(data);
476
+ const mockIdP = new MockSAMLIdP(8081); // Different port from your main app
477
+
478
+ const ssoOptions = {
479
+ provisionUser: vi
480
+ .fn()
481
+ .mockImplementation(async ({ user, userInfo, token, provider }) => {
482
+ return {
483
+ id: "provisioned-user-id",
484
+ email: userInfo.email,
485
+ name: userInfo.name,
486
+ attributes: userInfo.attributes,
487
+ };
488
+ }),
489
+ };
490
+
491
+ const auth = betterAuth({
492
+ database: memory,
493
+ baseURL: "http://localhost:3000",
494
+ emailAndPassword: {
495
+ enabled: true,
496
+ },
497
+ plugins: [sso(ssoOptions)],
498
+ });
499
+
500
+ const ctx = await auth.$context;
501
+
502
+ const authClient = createAuthClient({
503
+ baseURL: "http://localhost:3000",
504
+ plugins: [bearer(), ssoClient()],
505
+ fetchOptions: {
506
+ customFetchImpl: async (url, init) => {
507
+ return auth.handler(new Request(url, init));
508
+ },
509
+ },
510
+ });
511
+
512
+ const testUser = {
513
+ email: "test@email.com",
514
+ password: "password",
515
+ name: "Test User",
516
+ };
517
+
518
+ beforeAll(async () => {
519
+ await mockIdP.start();
520
+ const res = await authClient.signUp.email({
521
+ email: testUser.email,
522
+ password: testUser.password,
523
+ name: testUser.name,
524
+ });
525
+ });
526
+
527
+ afterAll(async () => {
528
+ await mockIdP.stop();
529
+ });
530
+
531
+ beforeEach(() => {
532
+ data.user = [];
533
+ data.session = [];
534
+ data.verification = [];
535
+ data.account = [];
536
+ data.ssoProvider = [];
537
+
538
+ vi.clearAllMocks();
539
+ });
540
+
541
+ async function getAuthHeaders() {
542
+ const headers = new Headers();
543
+ await authClient.signUp.email({
544
+ email: testUser.email,
545
+ password: testUser.password,
546
+ name: testUser.name,
547
+ });
548
+ const res = await authClient.signIn.email(testUser, {
549
+ throw: true,
550
+ onSuccess: setCookieToHeader(headers),
551
+ });
552
+ return headers;
553
+ }
554
+
555
+ it("should register a new SAML provider", async () => {
556
+ const headers = await getAuthHeaders();
557
+ const res = await authClient.signIn.email(testUser, {
558
+ throw: true,
559
+ onSuccess: setCookieToHeader(headers),
560
+ });
561
+
562
+ const provider = await auth.api.registerSSOProvider({
563
+ body: {
564
+ providerId: "saml-provider-1",
565
+ issuer: "http://localhost:8081",
566
+ domain: "http://localhost:8081",
567
+ samlConfig: {
568
+ entryPoint: mockIdP.metadataUrl,
569
+ cert: certificate,
570
+ callbackUrl: "http://localhost:8081/api/sso/saml2/callback",
571
+ wantAssertionsSigned: false,
572
+ signatureAlgorithm: "sha256",
573
+ digestAlgorithm: "sha256",
574
+ idpMetadata: {
575
+ metadata: idpMetadata,
576
+ privateKey: idpPrivateKey,
577
+ privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
578
+ isAssertionEncrypted: true,
579
+ encPrivateKey: idpEncyptionKey,
580
+ encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
581
+ },
582
+ spMetadata: {
583
+ metadata: idpMetadata,
584
+ binding: "post",
585
+ privateKey: spPrivateKey,
586
+ privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
587
+ isAssertionEncrypted: true,
588
+ encPrivateKey: spEncyptionKey,
589
+ encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
590
+ },
591
+ identifierFormat:
592
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
593
+ },
594
+ },
595
+ headers,
596
+ });
597
+ expect(provider).toMatchObject({
598
+ id: expect.any(String),
599
+ issuer: "http://localhost:8081",
600
+ samlConfig: {
601
+ entryPoint: mockIdP.metadataUrl,
602
+ cert: expect.any(String),
603
+ callbackUrl: "http://localhost:8081/api/sso/saml2/callback",
604
+ wantAssertionsSigned: false,
605
+ signatureAlgorithm: "sha256",
606
+ digestAlgorithm: "sha256",
607
+ identifierFormat:
608
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
609
+ },
610
+ });
611
+ });
612
+ it("Should fetch sp metadata", async () => {
613
+ const headers = await getAuthHeaders();
614
+ await authClient.signIn.email(testUser, {
615
+ throw: true,
616
+ onSuccess: setCookieToHeader(headers),
617
+ });
618
+ const provider = await auth.api.registerSSOProvider({
619
+ body: {
620
+ providerId: "saml-provider-1",
621
+ issuer: "http://localhost:8081",
622
+ domain: "http://localhost:8081",
623
+ samlConfig: {
624
+ entryPoint: mockIdP.metadataUrl,
625
+ cert: certificate,
626
+ callbackUrl: "http://localhost:8081/api/sso/saml2/sp/acs",
627
+ wantAssertionsSigned: false,
628
+ signatureAlgorithm: "sha256",
629
+ digestAlgorithm: "sha256",
630
+ idpMetadata: {
631
+ metadata: idpMetadata,
632
+ privateKey: idpPrivateKey,
633
+ privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
634
+ isAssertionEncrypted: true,
635
+ encPrivateKey: idpEncyptionKey,
636
+ encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
637
+ },
638
+ spMetadata: {
639
+ metadata: spMetadata,
640
+ binding: "post",
641
+ privateKey: spPrivateKey,
642
+ privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
643
+ isAssertionEncrypted: true,
644
+ encPrivateKey: spEncyptionKey,
645
+ encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
646
+ },
647
+ identifierFormat:
648
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
649
+ },
650
+ },
651
+ headers,
652
+ });
653
+
654
+ const spMetadataRes = await auth.api.spMetadata({
655
+ query: {
656
+ providerId: provider.providerId,
657
+ },
658
+ });
659
+ const spMetadataResResValue = await spMetadataRes.text();
660
+ expect(spMetadataRes.status).toBe(200);
661
+ expect(spMetadataResResValue).toBe(spMetadata);
662
+ });
663
+ it("should initiate SAML login and handle response", async () => {
664
+ const headers = await getAuthHeaders();
665
+ const res = await authClient.signIn.email(testUser, {
666
+ throw: true,
667
+ onSuccess: setCookieToHeader(headers),
668
+ });
669
+ const provider = await auth.api.registerSSOProvider({
670
+ body: {
671
+ providerId: "saml-provider-1",
672
+ issuer: "http://localhost:8081",
673
+ domain: "http://localhost:8081",
674
+ samlConfig: {
675
+ entryPoint: mockIdP.metadataUrl,
676
+ cert: certificate,
677
+ callbackUrl: "http://localhost:8081/api/sso/saml2/sp/acs",
678
+ wantAssertionsSigned: false,
679
+ signatureAlgorithm: "sha256",
680
+ digestAlgorithm: "sha256",
681
+ idpMetadata: {
682
+ metadata: idpMetadata,
683
+ privateKey: idpPrivateKey,
684
+ privateKeyPass: "q9ALNhGT5EhfcRmp8Pg7e9zTQeP2x1bW",
685
+ isAssertionEncrypted: true,
686
+ encPrivateKey: idpEncyptionKey,
687
+ encPrivateKeyPass: "g7hGcRmp8PxT5QeP2q9Ehf1bWe9zTALN",
688
+ },
689
+ spMetadata: {
690
+ metadata: idpMetadata,
691
+ binding: "post",
692
+ // we can do a mapping of property here
693
+ privateKey: spPrivateKey,
694
+ privateKeyPass: "VHOSp5RUiBcrsjrcAuXFwU1NKCkGA8px",
695
+ isAssertionEncrypted: true,
696
+ encPrivateKey: spEncyptionKey,
697
+ encPrivateKeyPass: "BXFNKpxrsjrCkGA8cAu5wUVHOSpci1RU",
698
+ },
699
+ identifierFormat:
700
+ "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
701
+ },
702
+ },
703
+ headers,
704
+ });
705
+
706
+ const signInResponse = await auth.api.signInSSO({
707
+ body: {
708
+ providerId: "saml-provider-1",
709
+ callbackURL: "http://localhost:3000/dashboard",
710
+ },
711
+ });
712
+ expect(signInResponse).toEqual({
713
+ url: expect.stringContaining("http://localhost:8081"),
714
+ redirect: true,
715
+ });
716
+ const loginResponse = await fetch(signInResponse?.url as string);
717
+ const resultValue = await loginResponse.json();
718
+ const result = await auth.api.callbackSSOSAML({
719
+ body: {
720
+ SAMLResponse: resultValue.samlResponse,
721
+ RelayState: "http://localhost:3001/dashboard",
722
+ },
723
+ params: {
724
+ providerId: provider.providerId,
725
+ },
726
+ });
727
+
728
+ expect(result).toEqual({
729
+ redirect: true,
730
+ url: "http://localhost:3001/dashboard",
731
+ });
732
+ });
733
+ });