api-ape 3.0.2 → 4.1.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 (186) hide show
  1. package/README.md +59 -572
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -203
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +31 -6
  44. package/server/README.md +272 -67
  45. package/server/adapters/README.md +23 -14
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +322 -71
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -219
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -224
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -311
  186. package/server/lib/broadcast.js +0 -146
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @fileoverview OPAQUE Authentication Adapter for api-ape Server
3
+ *
4
+ * Implements OPAQUE (Oblivious Pseudo-Random Function based Asymmetric
5
+ * Password-Authenticated Key Exchange) for secure authentication where
6
+ * the server never learns the user's password.
7
+ *
8
+ * ## Protocol Flow (Registration)
9
+ *
10
+ * ```
11
+ * Client Server
12
+ * |-- opaque_reg_start -------->| (user, clientNonce, regRequest)
13
+ * |<- opaque_reg_response ------| (serverNonce, ts, regResponse)
14
+ * |-- opaque_reg_finish ------->| (regRecord)
15
+ * |<- opaque_reg_ok ------------| (success)
16
+ * ```
17
+ *
18
+ * ## Protocol Flow (Login)
19
+ *
20
+ * ```
21
+ * Client Server
22
+ * |-- opaque_auth_start ------->| (user, clientNonce)
23
+ * |<- opaque_auth_1 ------------| (serverNonce, ts, envelope, oprfResponse)
24
+ * |-- opaque_auth_2 ----------->| (clientAuth)
25
+ * |<- opaque_auth_ok -----------| (assignedPrincipal, serverProof, authMeta)
26
+ * ```
27
+ *
28
+ * @module server/security/auth/adapters/opaque
29
+ * @see {@link module:server/security/auth/state-machine} for auth state management
30
+ */
31
+
32
+ const crypto = require("crypto");
33
+
34
+ /**
35
+ * OPAQUE adapter configuration
36
+ * @typedef {Object} OpaqueConfig
37
+ * @property {Function} [getUser] - Async function to fetch user by username
38
+ * @property {Function} [saveUser] - Async function to save user registration
39
+ * @property {Function} [opaqueLib] - OPAQUE library instance (e.g., @cloudflare/opaque)
40
+ * @property {number} [nonceLength=32] - Server nonce length in bytes
41
+ * @property {number} [nonceExpiry=30000] - Nonce expiry in ms
42
+ * @property {string} [serverId] - Server identifier for OPAQUE context
43
+ */
44
+
45
+ /**
46
+ * OPAQUE message types
47
+ * @enum {string}
48
+ */
49
+ const OpaqueMessageType = {
50
+ REG_START: "opaque_reg_start",
51
+ REG_RESPONSE: "opaque_reg_response",
52
+ REG_FINISH: "opaque_reg_finish",
53
+ REG_OK: "opaque_reg_ok",
54
+ REG_FAIL: "opaque_reg_fail",
55
+ AUTH_START: "opaque_auth_start",
56
+ AUTH_1: "opaque_auth_1",
57
+ AUTH_2: "opaque_auth_2",
58
+ AUTH_OK: "opaque_auth_ok",
59
+ AUTH_FAIL: "opaque_auth_fail",
60
+ };
61
+
62
+ /**
63
+ * OPAQUE error codes
64
+ * @enum {string}
65
+ */
66
+ const OpaqueError = {
67
+ USER_NOT_FOUND: "USER_NOT_FOUND",
68
+ USER_EXISTS: "USER_EXISTS",
69
+ INVALID_PROOF: "INVALID_PROOF",
70
+ INVALID_STATE: "INVALID_STATE",
71
+ NONCE_EXPIRED: "NONCE_EXPIRED",
72
+ NONCE_MISMATCH: "NONCE_MISMATCH",
73
+ MISSING_LIB: "MISSING_LIB",
74
+ INVALID_MESSAGE: "INVALID_MESSAGE",
75
+ };
76
+
77
+ /** @private */
78
+ const _defaultUserStore = new Map();
79
+
80
+ /** @private */
81
+ const defaultStorage = {
82
+ /**
83
+ * @param {string} username
84
+ * @returns {Promise<Object|null>}
85
+ */
86
+ async getUser(username) {
87
+ return _defaultUserStore.get(username) || null;
88
+ },
89
+ /**
90
+ * @param {string} username
91
+ * @param {Object} userData
92
+ * @returns {Promise<boolean>}
93
+ */
94
+ async saveUser(username, userData) {
95
+ _defaultUserStore.set(username, userData);
96
+ return true;
97
+ },
98
+ };
99
+
100
+ /**
101
+ * Create an OPAQUE adapter instance
102
+ *
103
+ * @param {OpaqueConfig} [config={}] - Configuration options
104
+ * @returns {Object} OPAQUE adapter with registration and authentication methods
105
+ */
106
+ function createOpaqueAdapter(config = {}) {
107
+ const {
108
+ getUser = defaultStorage.getUser,
109
+ saveUser = defaultStorage.saveUser,
110
+ opaqueLib = null,
111
+ nonceLength = 32,
112
+ nonceExpiry = 30000,
113
+ serverId = "api-ape-opaque-server",
114
+ } = config;
115
+
116
+ const pendingSessions = new Map();
117
+
118
+ /**
119
+ * Generate a session key for tracking pending auth
120
+ * @param {string} clientId - Client identifier
121
+ * @param {string} user - Username
122
+ * @returns {string} Session key
123
+ * @private
124
+ */
125
+ function sessionKey(clientId, user) {
126
+ return `${clientId}:${user}`;
127
+ }
128
+
129
+ /**
130
+ * Generate a server nonce
131
+ * @returns {Object} Nonce info { nonce, expiresAt }
132
+ * @private
133
+ */
134
+ function generateNonce() {
135
+ const nonce = crypto.randomBytes(nonceLength).toString("base64url");
136
+ const expiresAt = Date.now() + nonceExpiry;
137
+ return { nonce, expiresAt };
138
+ }
139
+
140
+ /**
141
+ * Create canonical binding string for OPAQUE context
142
+ * @param {Object} params - Binding parameters
143
+ * @returns {string} Canonical binding string
144
+ */
145
+ function createCanonicalBinding({ clientId, clientNonce, serverNonce, user, ts }) {
146
+ return `${clientId}|${clientNonce}|${serverNonce}|${user}|${ts}`;
147
+ }
148
+
149
+ const { createOpaqueHandlers } = require("./opaque-handlers");
150
+ const handlers = createOpaqueHandlers({
151
+ getUser,
152
+ saveUser,
153
+ opaqueLib,
154
+ serverId,
155
+ pendingSessions,
156
+ sessionKey,
157
+ generateNonce,
158
+ createCanonicalBinding,
159
+ nonceExpiry,
160
+ OpaqueMessageType,
161
+ OpaqueError,
162
+ });
163
+
164
+ /**
165
+ * Clean up pending sessions for a client
166
+ * @param {string} clientId - Client identifier
167
+ */
168
+ function cleanupClient(clientId) {
169
+ for (const [key] of pendingSessions) {
170
+ if (key.startsWith(clientId + ":")) {
171
+ pendingSessions.delete(key);
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Check if the adapter has an OPAQUE library configured
178
+ * @returns {boolean} Whether OPAQUE library is available
179
+ */
180
+ function hasOpaqueLib() {
181
+ return opaqueLib !== null;
182
+ }
183
+
184
+ return {
185
+ type: "opaque",
186
+ tier: 1,
187
+ MessageType: OpaqueMessageType,
188
+ Error: OpaqueError,
189
+ handleRegStart: handlers.handleRegStart,
190
+ handleRegFinish: handlers.handleRegFinish,
191
+ handleAuthStart: handlers.handleAuthStart,
192
+ handleAuthFinish: handlers.handleAuthFinish,
193
+ cleanupClient,
194
+ hasOpaqueLib,
195
+ createCanonicalBinding,
196
+ _pendingSessions: pendingSessions,
197
+ _defaultUserStore,
198
+ };
199
+ }
200
+
201
+ module.exports = {
202
+ createOpaqueAdapter,
203
+ OpaqueMessageType,
204
+ OpaqueError,
205
+ };
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file SAML constants and error codes
3
+ */
4
+ "use strict";
5
+
6
+ /**
7
+ * SAML message types
8
+ * @enum {string}
9
+ */
10
+ const SAMLMessageType = {
11
+ AUTH_START: "saml_auth_start",
12
+ AUTH_REDIRECT: "saml_auth_redirect",
13
+ AUTH_CALLBACK: "saml_auth_callback",
14
+ AUTH_OK: "saml_auth_ok",
15
+ AUTH_FAIL: "saml_auth_fail",
16
+ LOGOUT_START: "saml_logout_start",
17
+ LOGOUT_REDIRECT: "saml_logout_redirect",
18
+ LOGOUT_OK: "saml_logout_ok",
19
+ };
20
+
21
+ /**
22
+ * SAML error codes
23
+ * @enum {string}
24
+ */
25
+ const SAMLError = {
26
+ INVALID_RESPONSE: "INVALID_RESPONSE",
27
+ SIGNATURE_INVALID: "SIGNATURE_INVALID",
28
+ ASSERTION_EXPIRED: "ASSERTION_EXPIRED",
29
+ MISSING_ASSERTION: "MISSING_ASSERTION",
30
+ INVALID_AUDIENCE: "INVALID_AUDIENCE",
31
+ MISSING_NAMEID: "MISSING_NAMEID",
32
+ IDP_ERROR: "IDP_ERROR",
33
+ CONFIG_ERROR: "CONFIG_ERROR",
34
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
35
+ };
36
+
37
+ /**
38
+ * Default attribute mapping
39
+ */
40
+ const DEFAULT_ATTRIBUTE_MAPPING = {
41
+ email: ["email", "urn:oid:0.9.2342.19200300.100.1.3", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"],
42
+ displayName: ["displayName", "cn", "urn:oid:2.16.840.1.113730.3.1.241", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"],
43
+ firstName: ["firstName", "givenName", "urn:oid:2.5.4.42", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"],
44
+ lastName: ["lastName", "sn", "surname", "urn:oid:2.5.4.4", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"],
45
+ groups: ["memberOf", "groups", "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups"],
46
+ };
47
+
48
+ module.exports = {
49
+ SAMLMessageType,
50
+ SAMLError,
51
+ DEFAULT_ATTRIBUTE_MAPPING,
52
+ };
@@ -0,0 +1,19 @@
1
+ # SAML Adapter Module
2
+
3
+ SAML 2.0 SSO authentication helpers.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ saml/
9
+ ├── constants.js - SAML message types and error codes
10
+ └── helpers.js - Request storage and ID generation
11
+ ```
12
+
13
+ ## Files
14
+
15
+ ### `constants.js`
16
+ Defines SAMLMessageType (AUTH_REDIRECT, AUTH_OK, AUTH_FAIL, LOGOUT_*) and SAMLError codes.
17
+
18
+ ### `helpers.js`
19
+ Pending request storage and request ID generation for SAML flows.
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @file SAML helper functions
3
+ */
4
+ "use strict";
5
+
6
+ const crypto = require("crypto");
7
+
8
+ /**
9
+ * Create per-instance storage for mock mode
10
+ * @returns {Object} Storage adapter with isolated maps
11
+ */
12
+ function createDefaultStorage() {
13
+ const pendingRequests = new Map();
14
+ const mockUsers = new Map();
15
+ return {
16
+ /**
17
+ * Save a pending SAML request
18
+ * @param {string} requestId - Request ID
19
+ * @param {Object} data - Request data
20
+ * @returns {Promise<boolean>} Success
21
+ */
22
+ async savePendingRequest(requestId, data) {
23
+ pendingRequests.set(requestId, { ...data, createdAt: Date.now() });
24
+ return true;
25
+ },
26
+ /**
27
+ * Get a pending SAML request
28
+ * @param {string} requestId - Request ID
29
+ * @returns {Promise<Object|null>} Request data or null
30
+ */
31
+ async getPendingRequest(requestId) {
32
+ return pendingRequests.get(requestId) || null;
33
+ },
34
+ /**
35
+ * Delete a pending SAML request
36
+ * @param {string} requestId - Request ID
37
+ * @returns {Promise<boolean>} Success
38
+ */
39
+ async deletePendingRequest(requestId) {
40
+ return pendingRequests.delete(requestId);
41
+ },
42
+ /**
43
+ * Register a mock user for testing
44
+ * @param {string} nameId - User name ID
45
+ * @param {Object} attributes - User attributes
46
+ * @returns {Promise<boolean>} Success
47
+ */
48
+ async registerMockUser(nameId, attributes) {
49
+ mockUsers.set(nameId, attributes);
50
+ return true;
51
+ },
52
+ /**
53
+ * Get a mock user by name ID
54
+ * @param {string} nameId - User name ID
55
+ * @returns {Promise<Object|null>} User attributes or null
56
+ */
57
+ async getMockUser(nameId) {
58
+ return mockUsers.get(nameId) || null;
59
+ },
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Generate a SAML AuthnRequest ID
65
+ * @returns {string} Request ID
66
+ */
67
+ function generateRequestId() {
68
+ return "_" + crypto.randomBytes(16).toString("hex");
69
+ }
70
+
71
+ module.exports = {
72
+ createDefaultStorage,
73
+ generateRequestId,
74
+ };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @fileoverview SAML Authentication Adapter for api-ape Server
3
+ *
4
+ * Implements SAML 2.0 Single Sign-On (SSO) for enterprise identity providers.
5
+ *
6
+ * @module server/security/auth/adapters/saml
7
+ */
8
+
9
+ "use strict";
10
+
11
+ const { SAMLMessageType, SAMLError } = require("./saml/constants");
12
+ const { createDefaultStorage, generateRequestId } = require("./saml/helpers");
13
+
14
+ /**
15
+ * Create a SAML authentication adapter
16
+ *
17
+ * @param {Object} config - Configuration options
18
+ * @param {Function} verify - Passport.js verify callback
19
+ * @returns {Object} SAML adapter with Passport.js Strategy interface
20
+ */
21
+ function createSAMLStrategy(config = {}, verify = null) {
22
+ if (typeof config === "function") {
23
+ verify = config;
24
+ config = {};
25
+ }
26
+
27
+ const storage = createDefaultStorage();
28
+
29
+ const {
30
+ entryPoint = "https://idp.example.com/sso",
31
+ issuer = "api-ape",
32
+ callbackUrl = "http://localhost:3000/auth/saml/callback",
33
+ logoutUrl = null,
34
+ identifierFormat = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
35
+ passReqToCallback = false,
36
+ } = config;
37
+
38
+ const strategy = { name: "saml" };
39
+
40
+ /**
41
+ * Handle SAML auth start - generates redirect URL
42
+ * @param {Object} [data={}] - Request data
43
+ * @param {string} [data.relayState=''] - State to preserve across redirect
44
+ * @returns {Promise<Object>} Response with redirect URL
45
+ */
46
+ async function handleAuthStart(data = {}) {
47
+ const requestId = generateRequestId();
48
+ const { relayState = "" } = data;
49
+ await storage.savePendingRequest(requestId, { relayState, issuer });
50
+ const redirectUrl = `${entryPoint}?SAMLRequest=${encodeURIComponent(requestId)}&RelayState=${encodeURIComponent(relayState)}`;
51
+ return { type: SAMLMessageType.AUTH_REDIRECT, url: redirectUrl, requestId };
52
+ }
53
+
54
+ /**
55
+ * Handle SAML callback - processes SAML response
56
+ * @param {Object} data - Callback data
57
+ * @param {string} data.SAMLResponse - SAML response
58
+ * @param {string} [data.RelayState] - Relay state
59
+ * @returns {Promise<Object>} Response with user profile or error
60
+ */
61
+ async function handleAuthCallback(data) {
62
+ const { SAMLResponse, RelayState } = data;
63
+ if (!SAMLResponse) {
64
+ return { type: SAMLMessageType.AUTH_FAIL, error: SAMLError.MISSING_ASSERTION, message: "No SAML response provided" };
65
+ }
66
+
67
+ try {
68
+ const nameId = SAMLResponse;
69
+ const mockUser = await storage.getMockUser(nameId);
70
+ if (!mockUser) {
71
+ return { type: SAMLMessageType.AUTH_FAIL, error: SAMLError.MISSING_NAMEID, message: "User not found in identity provider" };
72
+ }
73
+
74
+ const profile = {
75
+ nameID: nameId,
76
+ nameIDFormat: identifierFormat,
77
+ issuer: mockUser.issuer || entryPoint,
78
+ email: mockUser.email || nameId,
79
+ firstName: mockUser.firstName,
80
+ lastName: mockUser.lastName,
81
+ displayName: mockUser.displayName || mockUser.firstName + " " + mockUser.lastName,
82
+ groups: mockUser.groups || [],
83
+ raw: mockUser,
84
+ };
85
+
86
+ return { type: SAMLMessageType.AUTH_OK, userId: nameId, profile, relayState: RelayState };
87
+ } catch (err) {
88
+ return { type: SAMLMessageType.AUTH_FAIL, error: SAMLError.INVALID_RESPONSE, message: err.message || "Failed to process SAML response" };
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Handle SAML logout start
94
+ * @param {Object} data - Logout data
95
+ * @param {string} data.nameId - User name ID
96
+ * @param {string} [data.sessionIndex] - Session index
97
+ * @returns {Promise<Object>} Response with logout redirect URL
98
+ */
99
+ async function handleLogoutStart(data) {
100
+ const { nameId, sessionIndex } = data;
101
+ if (!logoutUrl) {
102
+ return { type: SAMLMessageType.LOGOUT_OK, message: "Single logout not configured" };
103
+ }
104
+ const redirectUrl = `${logoutUrl}?NameID=${encodeURIComponent(nameId)}&` + (sessionIndex ? `SessionIndex=${encodeURIComponent(sessionIndex)}` : "");
105
+ return { type: SAMLMessageType.LOGOUT_REDIRECT, url: redirectUrl };
106
+ }
107
+
108
+ strategy.authenticate = function (req, options = {}) {
109
+ const self = this;
110
+ const samlResponse = req.body?.SAMLResponse || req.SAMLResponse;
111
+
112
+ if (samlResponse) {
113
+ handleAuthCallback({ SAMLResponse: samlResponse, RelayState: req.body?.RelayState || req.RelayState })
114
+ .then((result) => {
115
+ if (result.type === SAMLMessageType.AUTH_FAIL) {
116
+ return self.fail({ message: result.message, code: result.error });
117
+ }
118
+ if (verify) {
119
+ /** @param {Error|null} err - Error @param {Object|false} user - User @param {Object} info - Info @returns {void} */
120
+ const verified = (err, user, info) => {
121
+ if (err) return self.error(err);
122
+ if (!user) return self.fail(info || { message: "Verification failed" });
123
+ return self.success(user, info);
124
+ };
125
+ try {
126
+ if (passReqToCallback) verify(req, result.profile, verified);
127
+ else verify(result.profile, verified);
128
+ } catch (err) {
129
+ return self.error(err);
130
+ }
131
+ } else {
132
+ return self.success(result.profile, { userId: result.userId });
133
+ }
134
+ })
135
+ .catch((err) => { if (typeof self.error === "function") self.error(err); });
136
+ } else {
137
+ handleAuthStart({ relayState: options.relayState || req.relayState || "" })
138
+ .then((result) => { self.redirect(result.url); })
139
+ .catch((err) => { if (typeof self.error === "function") self.error(err); });
140
+ }
141
+ };
142
+
143
+ /**
144
+ * Register a test user for mock mode
145
+ * @param {string} nameId - User name ID
146
+ * @param {Object} [attributes={}] - User attributes
147
+ * @returns {Promise<boolean>} Success
148
+ */
149
+ async function registerTestUser(nameId, attributes = {}) {
150
+ return storage.registerMockUser(nameId, attributes);
151
+ }
152
+
153
+ /**
154
+ * Cleanup resources
155
+ * @returns {void}
156
+ */
157
+ function cleanup() {}
158
+
159
+ return {
160
+ name: strategy.name,
161
+ authenticate: strategy.authenticate,
162
+ handleAuthStart,
163
+ handleAuthCallback,
164
+ handleLogoutStart,
165
+ registerTestUser,
166
+ cleanup,
167
+ _config: { entryPoint, issuer, callbackUrl },
168
+ };
169
+ }
170
+
171
+ const SAMLStrategy = createSAMLStrategy;
172
+
173
+ module.exports = { createSAMLStrategy, SAMLStrategy, SAMLMessageType, SAMLError };