saml-test-server 0.0.0-stage → 1.0.0

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.
Files changed (43) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +134 -2
  3. package/assets/idp-private-key.pem +27 -0
  4. package/assets/idp-public-cert.pem +20 -0
  5. package/dist/cli.js +106 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.js +2170 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/types/app.d.ts +16 -0
  10. package/dist/types/cli-program.d.ts +10 -0
  11. package/dist/types/cli.d.ts +2 -0
  12. package/dist/types/default-config.d.ts +12 -0
  13. package/dist/types/index.d.ts +41 -0
  14. package/dist/types/log.d.ts +26 -0
  15. package/dist/types/options.d.ts +111 -0
  16. package/dist/types/profile-mapper.d.ts +10 -0
  17. package/dist/types/session.d.ts +36 -0
  18. package/dist/types/state.d.ts +49 -0
  19. package/dist/types/vendor/saml/encrypt.d.ts +16 -0
  20. package/dist/types/vendor/saml/index.d.ts +7 -0
  21. package/dist/types/vendor/saml/saml20.d.ts +47 -0
  22. package/dist/types/vendor/saml/sign.d.ts +30 -0
  23. package/dist/types/vendor/saml/utils.d.ts +18 -0
  24. package/dist/types/vendor/samlp/auth.d.ts +87 -0
  25. package/dist/types/vendor/samlp/constants.d.ts +30 -0
  26. package/dist/types/vendor/samlp/encoders.d.ts +5 -0
  27. package/dist/types/vendor/samlp/index.d.ts +14 -0
  28. package/dist/types/vendor/samlp/logout.d.ts +66 -0
  29. package/dist/types/vendor/samlp/metadata.d.ts +22 -0
  30. package/dist/types/vendor/samlp/profile-mapper.d.ts +38 -0
  31. package/dist/types/vendor/samlp/session-participants.d.ts +22 -0
  32. package/dist/types/vendor/samlp/signers.d.ts +43 -0
  33. package/dist/types/vendor/samlp/templates.d.ts +52 -0
  34. package/dist/types/vendor/samlp/trim-xml.d.ts +2 -0
  35. package/dist/types/vendor/samlp/utils.d.ts +55 -0
  36. package/dist/types/vendor/samlp/xml.d.ts +7 -0
  37. package/dist/types/views/AutoPostForm.d.ts +12 -0
  38. package/dist/types/views/ErrorPage.d.ts +8 -0
  39. package/dist/types/views/Layout.d.ts +4 -0
  40. package/dist/types/views/SettingsPage.d.ts +5 -0
  41. package/dist/types/views/UserPage.d.ts +20 -0
  42. package/dist/types/views/index.d.ts +5 -0
  43. package/package.json +58 -4
package/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,135 @@
1
- # Temporary Holding Version
1
+ # saml-test-server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A simple SAML 2.0 Identity Provider (IdP) to test Service Providers (SPs) in **E2E tests**.
4
+
5
+ > **Not intended for use with production systems!** The bundled signing key is public.
6
+
7
+ ## Usage
8
+
9
+ ```ts
10
+ import { runServer } from 'saml-test-server';
11
+
12
+ export default async function globalSetup() {
13
+ const idp = await runServer({
14
+ port: 7000,
15
+ acsUrl: 'http://localhost:3000/auth/saml/callback',
16
+ sloUrl: 'http://localhost:3000/auth/saml/logout',
17
+ audience: 'http://localhost:3000/auth/saml/metadata',
18
+ logLevel: 'silent',
19
+ });
20
+
21
+ // Change the signed-in user at any time, without going through the UI:
22
+ idp.state.user.userName = 'jane@example.com';
23
+ idp.state.user.email = 'jane@example.com';
24
+ // ...and restore the defaults between tests:
25
+ idp.state.reset();
26
+
27
+ return () => idp.close();
28
+ }
29
+ ```
30
+
31
+ Custom claims (the user is merged with the defaults):
32
+
33
+ ```ts
34
+ await runServer({
35
+ acsUrl,
36
+ audience,
37
+ config: {
38
+ user: { userName: 'jane@example.com', email: 'jane@example.com', userType: 'Admin' },
39
+ metadata: [
40
+ { id: 'email', displayName: 'E-Mail Address', optional: false },
41
+ { id: 'userType', displayName: 'User Type', optional: true, options: ['Admin', 'Editor'] },
42
+ ],
43
+ },
44
+ });
45
+ ```
46
+
47
+ `configFile` (a `.js`, `.mjs` or `.json` module exporting `{ user, metadata }`) is also supported.
48
+
49
+ ### In-process (no port)
50
+
51
+ ```ts
52
+ import { createIdp } from 'saml-test-server';
53
+
54
+ const { app, state } = await createIdp({ acsUrl, audience, logLevel: 'silent' });
55
+ const res = await app.request('/metadata');
56
+ ```
57
+
58
+ ### Command line
59
+
60
+ ```shell
61
+ saml-idp --acsUrl {POST URL} --audience {audience}
62
+ saml-idp --acsUrl {POST URL} --sloUrl {POST URL} --audience {audience}
63
+ ```
64
+
65
+ Open `http://localhost:7000` in your browser to start an IdP initiated flow to your SP.
66
+ All flags of the original CLI and their aliases (`--acs`, `--aud`, `--slo`, `--iss`, `--spId`, `--rs`, `--static`,
67
+ `--enc`, `--encCert`, `--encKey`, `--conf`, `--acr`, `--acd`) are supported. Run `saml-idp --help`.
68
+ `HOST` and `PORT` environment variables override `--host` and `--port`.
69
+
70
+ ### Options
71
+
72
+ Passed as `--<option>` to the CLI or in the options object of `runServer` / `createIdp`.
73
+
74
+ | Option (* required) | Description | Default |
75
+ | ---------------------- | ------------------------------------------------------------------------ | ---------------------------------------- |
76
+ | `host` | IdP web server listener host | `localhost` |
77
+ | `port` | IdP web server listener port (`0` picks a free port) | `7000` |
78
+ | `cert` | IdP signature public key certificate (PEM or path) | bundled test certificate |
79
+ | `key` | IdP signature private key (PEM or path, PKCS#1 or PKCS#8) | bundled test key |
80
+ | `issuer` | IdP issuer URI | `urn:example:idp` |
81
+ | `acsUrl` * | SP Assertion Consumer Service URL | |
82
+ | `sloUrl` | SP Single Logout URL (enables SLO) | |
83
+ | `audience` * | SP audience URI | |
84
+ | `serviceProviderId` | SP issuer / entity URI | `audience` |
85
+ | `relayState` | Default RelayState | |
86
+ | `disableRequestAcsUrl` | Ignore the ACS URL of the AuthnRequest | `false` |
87
+ | `encryptAssertion` | Encrypt the assertion with the SP public key | `false` |
88
+ | `encryptionCert` | SP certificate (PEM or path) for assertion encryption | |
89
+ | `encryptionPublicKey` | SP RSA public key (PEM or path); derived from `encryptionCert` if absent | |
90
+ | `https` | Enable the HTTPS listener (needs `httpsPrivateKey` and `httpsCert`) | `false` |
91
+ | `httpsPrivateKey` | Web server TLS private key (PEM or path) | |
92
+ | `httpsCert` | Web server TLS certificate (PEM or path) | |
93
+ | `signResponse` | Sign the SAML Response (the Assertion is always signed) | `true` |
94
+ | `configFile` | Module exporting `{ user, metadata }` | built-in defaults |
95
+ | `config` | Inline `{ user, metadata }` (library only) | |
96
+ | `rollSession` | New session for every authn request | `false` |
97
+ | `authnContextClassRef` | Authentication Context Class Reference | `...:PasswordProtectedTransport` |
98
+ | `authnContextDecl` | Authentication Context Declaration (XML or path to an XML file) | |
99
+ | `logLevel` | `silent` (no output), `error` (unexpected errors only) or `info`. Env: `SAML_IDP_LOG_LEVEL` | `info` |
100
+ | `silent` | Deprecated, same as `logLevel: 'silent'` | `false` |
101
+
102
+ ### Endpoints
103
+
104
+ | Path | Description |
105
+ | ------------------------------ | -------------------------------------------------------------------- |
106
+ | `/`, `/idp`, `/saml/sso` | SSO (HTTP-Redirect and HTTP-POST). Shows the sign in form |
107
+ | `/signin` (POST) | Mints the SAML Response and POSTs it to the ACS URL |
108
+ | `/saml/slo` | Single Logout (SP or IdP initiated, HTTP-Redirect and HTTP-POST) |
109
+ | `/signout` | Starts IdP initiated logout (or just ends the session without SLO) |
110
+ | `/metadata` | IdP metadata. `POST` adds an attribute (`attributeName`, `displayName`, ...) |
111
+ | `/settings` | Edit the IdP settings at runtime |
112
+
113
+ Useful element ids for browser tests: `#userName`, `#nameIdFormat`, one input per attribute (`#email`, `#groups`, ...),
114
+ `#btn-sign-in`, `#btn-sign-out`.
115
+
116
+ ## Development
117
+
118
+ ```shell
119
+ npm install
120
+ npm run dev # Vite dev server (SAML_ACS_URL, SAML_AUDIENCE, SAML_SLO_URL env vars)
121
+ npm test # Vitest
122
+ npm run typecheck
123
+ npm run build # dist/index.js, dist/cli.js, dist/types
124
+ ```
125
+
126
+ Generate your own IdP key pair with:
127
+
128
+ ```shell
129
+ openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider' \
130
+ -keyout idp-private-key.pem -out idp-public-cert.pem -days 7300
131
+ ```
132
+
133
+ ## License
134
+
135
+ MIT, see [LICENSE](./LICENSE). Vendored code keeps its own license in `src/vendor/*/LICENSE`.
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAwa8mlFNNqxFRI+pxuoIb5mdMC6ue7S9Wkgb2XMTGeHXWj8Ea
3
+ bO7uUH7IsdwR3CsoEGal6kME8JjGlVqO+4SFl0oXH8V3LqPmYfeutbQGH/N3foKh
4
+ Q2NtuEfJON4zsU3bIXe72VzK5u687OvcHeBfrojAmVTjr7DNxBWFI6UyTa1dH58g
5
+ pATEWs64r21tfckgFSoi2kj4ecCjDnT0KczXfiy/Dw9MUuRHr8JwyqcN8MrWb33c
6
+ /KgLVj43hf1eSFKYRCgCUAc8jkseu+66MzXee+2hVi8piiPtJyC6aSlY0S9frnYt
7
+ 9c6cpiWQHxnJu7GYV8iTN51CtsmzI6F8vTdwwQIDAQABAoIBABoE0YXzeueUN/Y/
8
+ NG/9sonNLqYStBLg3RxY2xezR4Y/Lke5hTdFEba2kXdzPOL62TL61ycVQcCghWzi
9
+ 8XpQXzHnvhuCh3Q4iGZ4w3udZ4U7S74vBkLrRH4RBrMhBoi3EMz6LL4SfR9wyuh4
10
+ 9uT8k136+QSgJsYfVkUVLwGO26cbepPHH9e7/MatV3iItEs7zZhF6CKYiexhZTte
11
+ y9HTRgkxiAUesSpO7/oXBAB7hTdrFbQOClxcW4peRTS9/834M3PdwE7OCwsznKp3
12
+ EDTsX1IcXojaYTZSb/Ftsc/WOQTOWo8jV7vVMU515zKCF/5YQntWfRwpT8PdABXa
13
+ TdT/OJECgYEA/87wQUT7ACCcdf/ZzBdRGZakRbPI0bGLnj+YKMCwgPMhptUzjJKg
14
+ k2LwkPyya1xB09XI6E6WHadjPgsBPYGpXc+SeKknT3iz7Q283iqn6jMgHq7NyLeY
15
+ Au0AlTGDaSNmDfUaT8luh+EtKIh+UDHtcJ/GZgw8Kr0RsSZG0cS1s3cCgYEAwdRM
16
+ Ir+UvjfO/CWghFMKyRR2RC/m9IJS+uY9eO2dcnGzPqXVDYiqOVRoyOfoI/Lavnei
17
+ n++XG24iSRDQ9fl9Gu7odlP+Or5liIES9Z9Gw9Ov1RUmspGJ/PT9RjSBvRymGHSu
18
+ IePjA5DFEDoaTAxCcdIIrURQz/HnWQYCDFfZ24cCgYEA0nrPLKU5ca1jj6Po7Qjy
19
+ bf+JQuDfB8ryek8RP9bsY+2pbsBWHgA3p43tKx8BH11VvJtXJ+4hjlzAR7h+WMFr
20
+ PgkhF6727Wj/grmfAZ5Pc2CFuTlTkSefKCkiJaAL5GKAAlP+RlATZjJdGdDVPd8W
21
+ 42hTsnu2tePDvuMjuk4Ew5kCgYBE/Ccg0ujAjqJXcZjgn6GeEW9KM3ZSnbOk4p8M
22
+ VB2OZtMxv9nFGFflUZp9rrQbX0rlMTUrER3JwHfpY1fLw8D3/gG0Y45/H9aJL88s
23
+ pkSMW+Ifx4hfU5c19YfTHcrzIKgo2hZDr5TTZwoMDXHgkRhw5Bz8de0BJYp+T7oY
24
+ 5IeALwKBgQDyYbssjxY2TJYr0Z+mvGNLnmEvuOT5z1Ek0XewQfCsGbC+3TOZ43NS
25
+ kXJHUt26kh/Jh7pZDmiEiRDS1IZSn2ytMqJT233EDn9jrPS0efpqzx8uGlRVROZN
26
+ oSMw69DxVGpBjGfLtlGF1i2TFcBEXD8jqaVSKzoKhXZkuHsP+q3WWg==
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDPDCCAiQCCQDydJgOlszqbzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJV
3
+ UzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEQ
4
+ MA4GA1UEChMHSmFua3lDbzESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDMxMjE5
5
+ NDYzM1oXDTI3MTExOTE5NDYzM1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNh
6
+ bGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB0phbmt5
7
+ Q28xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
8
+ AQoCggEBAMGvJpRTTasRUSPqcbqCG+ZnTAurnu0vVpIG9lzExnh11o/BGmzu7lB+
9
+ yLHcEdwrKBBmpepDBPCYxpVajvuEhZdKFx/Fdy6j5mH3rrW0Bh/zd36CoUNjbbhH
10
+ yTjeM7FN2yF3u9lcyubuvOzr3B3gX66IwJlU46+wzcQVhSOlMk2tXR+fIKQExFrO
11
+ uK9tbX3JIBUqItpI+HnAow509CnM134svw8PTFLkR6/CcMqnDfDK1m993PyoC1Y+
12
+ N4X9XkhSmEQoAlAHPI5LHrvuujM13nvtoVYvKYoj7ScgumkpWNEvX652LfXOnKYl
13
+ kB8ZybuxmFfIkzedQrbJsyOhfL03cMECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
14
+ eHwzqwnzGEkxjzSD47imXaTqtYyETZow7XwBc0ZaFS50qRFJUgKTAmKS1xQBP/qH
15
+ pStsROT35DUxJAE6NY1Kbq3ZbCuhGoSlY0L7VzVT5tpu4EY8+Dq/u2EjRmmhoL7U
16
+ kskvIZ2n1DdERtd+YUMTeqYl9co43csZwDno/IKomeN5qaPc39IZjikJ+nUC6kPF
17
+ Keu/3j9rgHNlRtocI6S1FdtFz9OZMQlpr0JbUt2T3xS/YoQJn6coDmJL5GTiiKM6
18
+ cOe+Ur1VwzS1JEDbSS2TWWhzq8ojLdrotYLGd9JOsoQhElmz+tMfCFQUFLExinPA
19
+ yy7YHlSiVX13QH2XTu/iQQ==
20
+ -----END CERTIFICATE-----
package/dist/cli.js ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option, InvalidArgumentError } from "commander";
3
+ import { L as LOG_LEVELS, OptionsError, runServer } from "./index.js";
4
+ import pc from "picocolors";
5
+ const ALIASES = {
6
+ "--iss": "--issuer",
7
+ "--acs": "--acsUrl",
8
+ "--slo": "--sloUrl",
9
+ "--aud": "--audience",
10
+ "--spId": "--serviceProviderId",
11
+ "--rs": "--relayState",
12
+ "--static": "--disableRequestAcsUrl",
13
+ "--enc": "--encryptAssertion",
14
+ "--encCert": "--encryptionCert",
15
+ "--encKey": "--encryptionPublicKey",
16
+ "--conf": "--configFile",
17
+ "--acr": "--authnContextClassRef",
18
+ "--acd": "--authnContextDecl"
19
+ };
20
+ const BOOLEAN_OPTIONS = [
21
+ "disableRequestAcsUrl",
22
+ "encryptAssertion",
23
+ "https",
24
+ "signResponse",
25
+ "rollSession",
26
+ "silent"
27
+ ];
28
+ function normalizeArgv(argv) {
29
+ return argv.map((arg) => {
30
+ if (!arg.startsWith("--")) return arg;
31
+ const [flag, ...rest] = arg.split("=");
32
+ const value = rest.length ? `=${rest.join("=")}` : "";
33
+ const canonical = ALIASES[flag] ?? flag;
34
+ const negated = /^--no-(.+)$/.exec(canonical);
35
+ if (negated && BOOLEAN_OPTIONS.includes(negated[1]) && !value) {
36
+ return `--${negated[1]}=false`;
37
+ }
38
+ return canonical + value;
39
+ });
40
+ }
41
+ function parseBoolean(value) {
42
+ if (["true", "yes", "1"].includes(value.toLowerCase())) return true;
43
+ if (["false", "no", "0"].includes(value.toLowerCase())) return false;
44
+ throw new InvalidArgumentError("Expected true or false.");
45
+ }
46
+ function booleanOption(flag, description, defaultValue = false) {
47
+ return new Option(`${flag} [bool]`, description).argParser(parseBoolean).preset(true).default(defaultValue);
48
+ }
49
+ function buildProgram(onRun) {
50
+ const program2 = new Command("saml-idp").description(
51
+ "Simple IdP for SAML 2.0 WebSSO & SLO Profile\n\nLaunches an IdP web server that mints SAML assertions or logout responses for a Service Provider (SP)"
52
+ ).usage("--acsUrl {url} --audience {uri} [options]").showHelpAfterError().configureHelp({ sortOptions: false }).addHelpText(
53
+ "after",
54
+ "\nAliases: --iss, --acs, --slo, --aud, --spId, --rs, --static, --enc, --encCert, --encKey, --conf, --acr, --acd\n\nExample:\n saml-idp --acsUrl http://acme.okta.com/auth/saml20/exampleidp --audience https://www.okta.com/saml2/service-provider/spf5aFRRXFGIMAYXQPNV"
55
+ );
56
+ program2.addOption(new Option("--host <host>", "IdP Web Server Listener Host").default("localhost")).addOption(
57
+ new Option("-p, --port <port>", "IdP Web Server Listener Port").default(7e3).argParser((v) => {
58
+ const port = Number.parseInt(v, 10);
59
+ if (Number.isNaN(port) || port < 0 || port > 65535) throw new InvalidArgumentError("Invalid port.");
60
+ return port;
61
+ })
62
+ ).option("--cert <cert>", "IdP Signature PublicKey Certificate (PEM or path) [default: bundled test certificate]").option("--key <key>", "IdP Signature PrivateKey Certificate (PEM or path) [default: bundled test key]").addOption(new Option("--issuer <uri>", "IdP Issuer URI").default("urn:example:idp")).option("--acsUrl <url>", "SP Assertion Consumer URL").option("--sloUrl <url>", "SP Single Logout URL").option("--audience <uri>", "SP Audience URI").option("--serviceProviderId <uri>", "SP Issuer/Entity URI").option("--relayState <state>", "Default SAML RelayState for SAMLResponse").addOption(
63
+ booleanOption(
64
+ "--disableRequestAcsUrl",
65
+ "Disables ability for SP AuthnRequest to specify Assertion Consumer URL"
66
+ )
67
+ ).addOption(booleanOption("--encryptAssertion", "Encrypts assertion with SP Public Key")).option("--encryptionCert <cert>", "SP Certificate (pem) for Assertion Encryption").option(
68
+ "--encryptionPublicKey <key>",
69
+ "SP RSA Public Key (pem) for Assertion Encryption (derived from the certificate when omitted)"
70
+ ).option("--httpsPrivateKey <key>", "Web Server TLS/SSL Private Key (pem)").option("--httpsCert <cert>", "Web Server TLS/SSL Certificate (pem)").addOption(booleanOption("--https", "Enables HTTPS Listener (requires httpsPrivateKey and httpsCert)")).addOption(booleanOption("--signResponse", "Enables signing of responses", true)).addOption(
71
+ new Option("--configFile <path>", "Path to a SAML attribute config file").default("saml-idp/config.js")
72
+ ).addOption(
73
+ booleanOption(
74
+ "--rollSession",
75
+ "Create a new session for every authn request instead of reusing an existing session"
76
+ )
77
+ ).addOption(
78
+ new Option("--authnContextClassRef <uri>", "Authentication Context Class Reference").default(
79
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
80
+ )
81
+ ).option("--authnContextDecl <path>", "Authentication Context Declaration (XML FilePath)").addOption(
82
+ new Option("--logLevel <level>", "Verbosity of the logs (env: SAML_IDP_LOG_LEVEL)").choices(LOG_LEVELS)
83
+ ).addOption(booleanOption("--silent", "Disables logging (same as --logLevel silent)")).action(async (opts) => {
84
+ if (!opts.acsUrl) program2.error("error: required option '--acsUrl <url>' not specified");
85
+ if (!opts.audience) program2.error("error: required option '--audience <uri>' not specified");
86
+ try {
87
+ await onRun({
88
+ ...opts,
89
+ host: process.env.HOST ?? opts.host,
90
+ port: process.env.PORT ? Number.parseInt(process.env.PORT, 10) : opts.port
91
+ });
92
+ } catch (error) {
93
+ if (error instanceof OptionsError) {
94
+ console.error(pc.red(error.message));
95
+ process.exit(1);
96
+ }
97
+ throw error;
98
+ }
99
+ });
100
+ return program2;
101
+ }
102
+ const program = buildProgram(async (options) => {
103
+ await runServer(options);
104
+ });
105
+ await program.parseAsync(normalizeArgv(process.argv.slice(2)), { from: "user" });
106
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sources":["../src/cli-program.ts","../src/cli.ts"],"sourcesContent":["import { Command, InvalidArgumentError, Option } from 'commander';\nimport { LOG_LEVELS } from './log.js';\nimport pc from 'picocolors';\nimport { OptionsError, type IdpOptions } from './options.js';\n\n/** Long option aliases of the original `saml-idp` CLI. */\nexport const ALIASES: Record<string, string> = {\n '--iss': '--issuer',\n '--acs': '--acsUrl',\n '--slo': '--sloUrl',\n '--aud': '--audience',\n '--spId': '--serviceProviderId',\n '--rs': '--relayState',\n '--static': '--disableRequestAcsUrl',\n '--enc': '--encryptAssertion',\n '--encCert': '--encryptionCert',\n '--encKey': '--encryptionPublicKey',\n '--conf': '--configFile',\n '--acr': '--authnContextClassRef',\n '--acd': '--authnContextDecl',\n};\n\nconst BOOLEAN_OPTIONS = [\n 'disableRequestAcsUrl',\n 'encryptAssertion',\n 'https',\n 'signResponse',\n 'rollSession',\n 'silent',\n];\n\n/**\n * Expands the aliases (`--acs` => `--acsUrl`) and the `--no-<flag>` form of boolean\n * flags (`--no-signResponse` => `--signResponse=false`).\n */\nexport function normalizeArgv(argv: string[]): string[] {\n return argv.map((arg) => {\n if (!arg.startsWith('--')) return arg;\n const [flag, ...rest] = arg.split('=');\n const value = rest.length ? `=${rest.join('=')}` : '';\n const canonical = ALIASES[flag] ?? flag;\n const negated = /^--no-(.+)$/.exec(canonical);\n if (negated && BOOLEAN_OPTIONS.includes(negated[1]) && !value) {\n return `--${negated[1]}=false`;\n }\n return canonical + value;\n });\n}\n\nfunction parseBoolean(value: string): boolean {\n if (['true', 'yes', '1'].includes(value.toLowerCase())) return true;\n if (['false', 'no', '0'].includes(value.toLowerCase())) return false;\n throw new InvalidArgumentError('Expected true or false.');\n}\n\nfunction booleanOption(flag: string, description: string, defaultValue = false): Option {\n return new Option(`${flag} [bool]`, description)\n .argParser(parseBoolean)\n .preset(true)\n .default(defaultValue);\n}\n\nexport function buildProgram(onRun: (options: IdpOptions) => Promise<void>): Command {\n const program = new Command('saml-idp')\n .description(\n 'Simple IdP for SAML 2.0 WebSSO & SLO Profile\\n\\n' +\n 'Launches an IdP web server that mints SAML assertions or logout responses for a Service Provider (SP)',\n )\n .usage('--acsUrl {url} --audience {uri} [options]')\n .showHelpAfterError()\n .configureHelp({ sortOptions: false })\n .addHelpText(\n 'after',\n '\\nAliases: --iss, --acs, --slo, --aud, --spId, --rs, --static, --enc, --encCert, --encKey, --conf, --acr, --acd\\n' +\n '\\nExample:\\n saml-idp --acsUrl http://acme.okta.com/auth/saml20/exampleidp ' +\n '--audience https://www.okta.com/saml2/service-provider/spf5aFRRXFGIMAYXQPNV',\n );\n\n program\n .addOption(new Option('--host <host>', 'IdP Web Server Listener Host').default('localhost'))\n .addOption(\n new Option('-p, --port <port>', 'IdP Web Server Listener Port')\n .default(7000)\n .argParser((v) => {\n const port = Number.parseInt(v, 10);\n if (Number.isNaN(port) || port < 0 || port > 65535) throw new InvalidArgumentError('Invalid port.');\n return port;\n }),\n )\n .option('--cert <cert>', 'IdP Signature PublicKey Certificate (PEM or path) [default: bundled test certificate]')\n .option('--key <key>', 'IdP Signature PrivateKey Certificate (PEM or path) [default: bundled test key]')\n .addOption(new Option('--issuer <uri>', 'IdP Issuer URI').default('urn:example:idp'))\n .option('--acsUrl <url>', 'SP Assertion Consumer URL')\n .option('--sloUrl <url>', 'SP Single Logout URL')\n .option('--audience <uri>', 'SP Audience URI')\n .option('--serviceProviderId <uri>', 'SP Issuer/Entity URI')\n .option('--relayState <state>', 'Default SAML RelayState for SAMLResponse')\n .addOption(\n booleanOption(\n '--disableRequestAcsUrl',\n 'Disables ability for SP AuthnRequest to specify Assertion Consumer URL',\n ),\n )\n .addOption(booleanOption('--encryptAssertion', 'Encrypts assertion with SP Public Key'))\n .option('--encryptionCert <cert>', 'SP Certificate (pem) for Assertion Encryption')\n .option(\n '--encryptionPublicKey <key>',\n 'SP RSA Public Key (pem) for Assertion Encryption (derived from the certificate when omitted)',\n )\n .option('--httpsPrivateKey <key>', 'Web Server TLS/SSL Private Key (pem)')\n .option('--httpsCert <cert>', 'Web Server TLS/SSL Certificate (pem)')\n .addOption(booleanOption('--https', 'Enables HTTPS Listener (requires httpsPrivateKey and httpsCert)'))\n .addOption(booleanOption('--signResponse', 'Enables signing of responses', true))\n .addOption(\n new Option('--configFile <path>', 'Path to a SAML attribute config file').default('saml-idp/config.js'),\n )\n .addOption(\n booleanOption(\n '--rollSession',\n 'Create a new session for every authn request instead of reusing an existing session',\n ),\n )\n .addOption(\n new Option('--authnContextClassRef <uri>', 'Authentication Context Class Reference').default(\n 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',\n ),\n )\n .option('--authnContextDecl <path>', 'Authentication Context Declaration (XML FilePath)')\n .addOption(\n new Option('--logLevel <level>', 'Verbosity of the logs (env: SAML_IDP_LOG_LEVEL)').choices(LOG_LEVELS),\n )\n .addOption(booleanOption('--silent', 'Disables logging (same as --logLevel silent)'))\n .action(async (opts: IdpOptions) => {\n if (!opts.acsUrl) program.error(\"error: required option '--acsUrl <url>' not specified\");\n if (!opts.audience) program.error(\"error: required option '--audience <uri>' not specified\");\n try {\n await onRun({\n ...opts,\n host: process.env.HOST ?? opts.host,\n port: process.env.PORT ? Number.parseInt(process.env.PORT, 10) : opts.port,\n });\n } catch (error) {\n if (error instanceof OptionsError) {\n console.error(pc.red(error.message));\n process.exit(1);\n }\n throw error;\n }\n });\n\n return program;\n}\n","#!/usr/bin/env node\nimport { buildProgram, normalizeArgv } from './cli-program.js';\nimport { runServer } from './index.js';\n\nconst program = buildProgram(async (options) => {\n await runServer(options);\n});\n\nawait program.parseAsync(normalizeArgv(process.argv.slice(2)), { from: 'user' });\n"],"names":["program"],"mappings":"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"}