api-ape 3.0.1 → 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 +58 -570
  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 -202
  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 +32 -7
  44. package/server/README.md +287 -53
  45. package/server/adapters/README.md +28 -19
  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 +332 -27
  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 -221
  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 -225
  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 -308
  186. package/server/lib/broadcast.js +0 -146
@@ -0,0 +1,95 @@
1
+ # Authentication Adapters Files
2
+
3
+ This directory contains protocol-specific authentication adapters. Each adapter implements a specific authentication protocol and is registered with the auth framework.
4
+
5
+ ## Guidelines
6
+
7
+ - **Session isolation** — Pending sessions are keyed by `clientId:username` for concurrent auth support
8
+ - **Nonce binding** — Server nonces bind auth transcript to specific connection parameters
9
+ - **Cleanup on disconnect** — Call `cleanupClient()` when socket disconnects
10
+
11
+ ## Directory Structure
12
+
13
+ ```
14
+ adapters/
15
+ ├── opaque.js # OPAQUE adapter factory and enums
16
+ ├── opaque-handlers.js # OPAQUE message handlers
17
+ ├── ldap.js # LDAP/AD adapter (Passport.js compatible)
18
+ ├── saml.js # SAML 2.0 SSO adapter (Passport.js compatible)
19
+ ├── oauth2.js # OAuth2 adapter (Passport.js compatible)
20
+ ├── webauthn.js # WebAuthn/FIDO2 adapter (Passport.js compatible)
21
+ └── totp.js # TOTP RFC 6238 adapter (Passport.js compatible)
22
+ ```
23
+
24
+ ## Files
25
+
26
+ ### `opaque.js`
27
+
28
+ OPAQUE protocol adapter for zero-knowledge password authentication:
29
+
30
+ - Server never learns the user's raw password
31
+ - `createOpaqueAdapter(config)` — Creates adapter with user storage functions
32
+ - Exports `OpaqueMessageType` and `OpaqueError` enums
33
+ - Supports optional `@cloudflare/opaque` library for cryptographic operations
34
+
35
+ ### `opaque-handlers.js`
36
+
37
+ OPAQUE message handler implementations:
38
+
39
+ - Registration flow: `handleRegStart`, `handleRegFinish`
40
+ - Auth flow: `handleAuthStart`, `handleAuthFinish`
41
+ - Manages pending sessions with automatic expiry cleanup
42
+ - Creates user principal on successful authentication
43
+
44
+ ### `webauthn.js`
45
+
46
+ WebAuthn/FIDO2 adapter for hardware security key MFA (Tier 2):
47
+
48
+ - `createWebAuthnStrategy(config, verify)` — Passport.js-compatible factory
49
+ - Registration flow: `handleRegStart`, `handleRegFinish`
50
+ - Auth flow: `handleAuthStart`, `handleAuthFinish`
51
+ - Exports `WebAuthnMessageType` and `WebAuthnError` enums
52
+ - Counter validation prevents authenticator cloning
53
+ - Mock mode for testing (real verification requires `@simplewebauthn/server`)
54
+
55
+ ### `ldap.js`
56
+
57
+ LDAP/Active Directory adapter for enterprise authentication (Tier 1):
58
+
59
+ - `createLDAPStrategy(config, verify)` — Passport.js-compatible factory
60
+ - Simple bind and search-then-bind modes
61
+ - Group membership extraction for role mapping
62
+ - Exports `LDAPMessageType` and `LDAPError` enums
63
+ - Mock mode for testing (real bind requires `ldapjs`)
64
+
65
+ ### `saml.js`
66
+
67
+ SAML 2.0 SSO adapter for enterprise identity providers (Tier 1):
68
+
69
+ - `createSAMLStrategy(config, verify)` — Passport.js-compatible factory
70
+ - SP-initiated and IdP-initiated SSO support
71
+ - Handles `handleAuthStart`, `handleAuthCallback`, `handleLogoutStart`
72
+ - Exports `SAMLMessageType` and `SAMLError` enums
73
+ - Mock mode for testing (real validation requires `passport-saml`)
74
+
75
+ ### `oauth2.js`
76
+
77
+ OAuth2 Authorization Code adapter for identity providers (Tier 1):
78
+
79
+ - `createOAuth2Strategy(config, verify)` — Passport.js-compatible factory
80
+ - PKCE support for enhanced security
81
+ - Handles `handleAuthStart`, `handleAuthCallback`, `handleTokenRefresh`
82
+ - Exports `OAuth2MessageType` and `OAuth2Error` enums
83
+ - State parameter for CSRF protection
84
+ - Mock mode for testing (real token exchange requires HTTP client)
85
+
86
+ ### `totp.js`
87
+
88
+ TOTP RFC 6238 adapter for authenticator app MFA (Tier 2):
89
+
90
+ - `createTOTPStrategy(config, verify)` — Passport.js-compatible factory
91
+ - Setup flow: `handleSetupStart`, `handleSetupVerify`
92
+ - Verify flow: `handleVerify`, `handleDisable`
93
+ - Exports `TOTPMessageType` and `TOTPError` enums
94
+ - Built-in base32 encoding, HMAC-SHA1 code generation
95
+ - Counter tracking prevents code replay within window
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @fileoverview LDAP Constants
3
+ * @module server/security/auth/adapters/ldap/constants
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * LDAP message types
10
+ * @enum {string}
11
+ */
12
+ const LDAPMessageType = {
13
+ AUTH: "ldap_auth",
14
+ AUTH_OK: "ldap_auth_ok",
15
+ AUTH_FAIL: "ldap_auth_fail",
16
+ };
17
+
18
+ /**
19
+ * LDAP error codes
20
+ * @enum {string}
21
+ */
22
+ const LDAPError = {
23
+ INVALID_CREDENTIALS: "INVALID_CREDENTIALS",
24
+ USER_NOT_FOUND: "USER_NOT_FOUND",
25
+ CONNECTION_ERROR: "CONNECTION_ERROR",
26
+ BIND_ERROR: "BIND_ERROR",
27
+ SEARCH_ERROR: "SEARCH_ERROR",
28
+ TIMEOUT: "TIMEOUT",
29
+ TLS_ERROR: "TLS_ERROR",
30
+ MISSING_CREDENTIALS: "MISSING_CREDENTIALS",
31
+ SERVER_UNAVAILABLE: "SERVER_UNAVAILABLE",
32
+ };
33
+
34
+ module.exports = {
35
+ LDAPMessageType,
36
+ LDAPError,
37
+ };
@@ -0,0 +1,19 @@
1
+ # LDAP Adapter Module
2
+
3
+ LDAP/Active Directory authentication helpers.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ ldap/
9
+ ├── constants.js - LDAP message types and error codes
10
+ └── helpers.js - Storage and mock LDAP client
11
+ ```
12
+
13
+ ## Files
14
+
15
+ ### `constants.js`
16
+ Defines LDAPMessageType (AUTH, AUTH_OK, AUTH_FAIL) and LDAPError codes.
17
+
18
+ ### `helpers.js`
19
+ Creates mock LDAP client for testing with bind, search, and unbind operations.
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @fileoverview LDAP Helpers
3
+ * @module server/security/auth/adapters/ldap/helpers
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * Create per-instance storage for mock mode
10
+ * @returns {Object} Storage adapter with isolated user map
11
+ */
12
+ function createDefaultStorage() {
13
+ const userStore = new Map();
14
+ return {
15
+ /**
16
+ * Get user by username
17
+ * @param {string} username - Username to look up
18
+ * @returns {Promise<Object|null>} User object or null
19
+ */
20
+ async getUser(username) {
21
+ return userStore.get(username) || null;
22
+ },
23
+ /**
24
+ * Save user (for mock registration)
25
+ * @param {string} username - Username
26
+ * @param {Object} userData - User data including password hash
27
+ * @returns {Promise<boolean>} Success
28
+ */
29
+ async saveUser(username, userData) {
30
+ userStore.set(username, userData);
31
+ return true;
32
+ },
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Create mock LDAP client for testing
38
+ * @param {Object} storage - Storage adapter
39
+ * @returns {Object} Mock LDAP client
40
+ */
41
+ function createMockLDAPClient(storage) {
42
+ return {
43
+ /**
44
+ * Mock bind operation
45
+ * @param {string} dn - Distinguished name
46
+ * @param {string} password - Password
47
+ * @returns {Promise<void>}
48
+ */
49
+ async bind(dn, password) {
50
+ const match = dn.match(/uid=([^,]+)/i) || dn.match(/cn=([^,]+)/i);
51
+ const username = match ? match[1] : dn;
52
+
53
+ const user = await storage.getUser(username);
54
+ if (!user) {
55
+ const err = new Error("User not found");
56
+ err.code = "LDAP_NO_SUCH_OBJECT";
57
+ throw err;
58
+ }
59
+ if (user.password !== password) {
60
+ const err = new Error("Invalid credentials");
61
+ err.code = "LDAP_INVALID_CREDENTIALS";
62
+ throw err;
63
+ }
64
+ },
65
+
66
+ /**
67
+ * Mock search operation
68
+ * @param {string} base - Search base DN
69
+ * @param {Object} options - Search options
70
+ * @returns {Promise<Object[]>} Search results
71
+ */
72
+ async search(base, options) {
73
+ const results = [];
74
+ const filter = options.filter || "";
75
+
76
+ const match = filter.match(/\(uid=([^)]+)\)/i) || filter.match(/\(cn=([^)]+)\)/i);
77
+ if (match) {
78
+ const username = match[1];
79
+ const user = await storage.getUser(username);
80
+ if (user) {
81
+ results.push({
82
+ dn: `uid=${username},${base}`,
83
+ uid: username,
84
+ cn: user.cn || username,
85
+ mail: user.mail || `${username}@example.com`,
86
+ memberOf: user.memberOf || [],
87
+ ...user.attributes,
88
+ });
89
+ }
90
+ }
91
+ return results;
92
+ },
93
+
94
+ /**
95
+ * Mock unbind operation
96
+ * @returns {Promise<void>}
97
+ */
98
+ async unbind() {},
99
+
100
+ /**
101
+ * Mock destroy operation
102
+ * @returns {void}
103
+ */
104
+ destroy() {},
105
+ };
106
+ }
107
+
108
+ module.exports = {
109
+ createDefaultStorage,
110
+ createMockLDAPClient,
111
+ };
@@ -0,0 +1,353 @@
1
+ /**
2
+ * @fileoverview LDAP Authentication Adapter for api-ape Server
3
+ *
4
+ * Implements LDAP/Active Directory authentication for enterprise integration.
5
+ * This is a Tier 1 adapter providing primary identity verification.
6
+ *
7
+ * ## Protocol Flow
8
+ *
9
+ * ```
10
+ * Client Server
11
+ * |-- ldap_auth ---------------->| (username, password)
12
+ * |<- ldap_auth_ok / _fail ------| (principal / error)
13
+ * ```
14
+ *
15
+ * ## Features
16
+ *
17
+ * - Simple bind authentication
18
+ * - Search-then-bind for flexible user lookup
19
+ * - Group membership extraction for role mapping
20
+ * - Connection pooling support
21
+ * - TLS/STARTTLS support
22
+ * - Passport.js Strategy interface compatibility
23
+ *
24
+ * @module server/security/auth/adapters/ldap
25
+ * @see {@link module:server/security/auth} for the auth framework
26
+ */
27
+
28
+ "use strict";
29
+
30
+ const { LDAPMessageType, LDAPError } = require("./ldap/constants");
31
+ const { createDefaultStorage, createMockLDAPClient } = require("./ldap/helpers");
32
+
33
+ /**
34
+ * Create an LDAP authentication adapter
35
+ *
36
+ * @param {LDAPConfig} [config={}] - Configuration options
37
+ * @param {Function} [verify] - Passport.js verify callback
38
+ * @returns {Object} LDAP adapter with Passport.js Strategy interface
39
+ *
40
+ * @example
41
+ * // Basic usage
42
+ * const ldap = createLDAPStrategy({
43
+ * url: 'ldap://ldap.example.com',
44
+ * baseDN: 'ou=users,dc=example,dc=com'
45
+ * });
46
+ *
47
+ * // With verify callback (Passport.js style)
48
+ * const ldap = createLDAPStrategy({
49
+ * url: 'ldaps://ldap.example.com',
50
+ * baseDN: 'ou=users,dc=example,dc=com',
51
+ * bindDN: 'cn=admin,dc=example,dc=com',
52
+ * bindPassword: 'secret'
53
+ * }, (profile, done) => {
54
+ * User.findOrCreate({ ldapId: profile.dn }, done);
55
+ * });
56
+ */
57
+ function createLDAPStrategy(config = {}, verify = null) {
58
+ // Handle Passport.js style: (verify) or (config, verify)
59
+ if (typeof config === "function") {
60
+ verify = config;
61
+ config = {};
62
+ }
63
+
64
+ // Create per-instance storage for isolation
65
+ const instanceStorage = createDefaultStorage();
66
+
67
+ const {
68
+ url = "ldap://localhost:389",
69
+ baseDN = "dc=example,dc=com",
70
+ bindDN = null,
71
+ bindPassword = null,
72
+ searchFilter = "(uid={{username}})",
73
+ usernameField = "uid",
74
+ groupSearchBase = null,
75
+ groupSearchFilter = "(member={{dn}})",
76
+ groupAttribute = "cn",
77
+ tlsOptions = null,
78
+ timeout = 5000,
79
+ connectTimeout = 10000,
80
+ passReqToCallback = false,
81
+ ldapClient = null,
82
+ getUser = instanceStorage.getUser,
83
+ saveUser = instanceStorage.saveUser,
84
+ } = config;
85
+
86
+ const storage = { getUser, saveUser };
87
+
88
+ // Create or use provided LDAP client
89
+ const client = ldapClient || createMockLDAPClient(storage);
90
+
91
+ // Passport.js Strategy interface
92
+ const strategy = {
93
+ name: "ldap",
94
+ };
95
+
96
+ /**
97
+ * Perform user search
98
+ * @private
99
+ * @param {string} username - Username to search for
100
+ * @returns {Promise<Object|null>} User entry or null
101
+ */
102
+ async function searchUser(username) {
103
+ const filter = searchFilter.replace(/\{\{username\}\}/g, username);
104
+ const results = await client.search(baseDN, {
105
+ filter,
106
+ scope: "sub",
107
+ attributes: ["dn", usernameField, "cn", "mail", "memberOf"],
108
+ });
109
+ return results.length > 0 ? results[0] : null;
110
+ }
111
+
112
+ /**
113
+ * Get group memberships for a user
114
+ * @private
115
+ * @param {string} userDN - User's distinguished name
116
+ * @returns {Promise<string[]>} Array of group names
117
+ */
118
+ async function getGroups(userDN) {
119
+ if (!groupSearchBase) return [];
120
+
121
+ const bases = Array.isArray(groupSearchBase) ? groupSearchBase : [groupSearchBase];
122
+ const groups = [];
123
+
124
+ for (const base of bases) {
125
+ const filter = groupSearchFilter.replace(/\{\{dn\}\}/g, userDN);
126
+ const results = await client.search(base, {
127
+ filter,
128
+ scope: "sub",
129
+ attributes: [groupAttribute],
130
+ });
131
+ for (const entry of results) {
132
+ if (entry[groupAttribute]) {
133
+ groups.push(entry[groupAttribute]);
134
+ }
135
+ }
136
+ }
137
+
138
+ return groups;
139
+ }
140
+
141
+ /**
142
+ * Handle LDAP authentication message
143
+ *
144
+ * @param {Object} data - Message data
145
+ * @param {string} data.username - Username
146
+ * @param {string} data.password - Password
147
+ * @returns {Promise<Object>} Response message
148
+ */
149
+ async function handleAuth(data) {
150
+ const { username, password } = data;
151
+
152
+ if (!username || !password) {
153
+ return {
154
+ type: LDAPMessageType.AUTH_FAIL,
155
+ error: LDAPError.MISSING_CREDENTIALS,
156
+ message: "Username and password are required",
157
+ };
158
+ }
159
+
160
+ try {
161
+ let userEntry;
162
+ let userDN;
163
+
164
+ // Search-then-bind mode (more flexible)
165
+ if (bindDN) {
166
+ // First bind as service account
167
+ await client.bind(bindDN, bindPassword);
168
+
169
+ // Search for user
170
+ userEntry = await searchUser(username);
171
+ if (!userEntry) {
172
+ return {
173
+ type: LDAPMessageType.AUTH_FAIL,
174
+ error: LDAPError.USER_NOT_FOUND,
175
+ message: "User not found",
176
+ };
177
+ }
178
+ userDN = userEntry.dn;
179
+
180
+ // Rebind as user to verify password
181
+ await client.bind(userDN, password);
182
+ } else {
183
+ // Simple bind mode - construct DN from username
184
+ userDN = `${usernameField}=${username},${baseDN}`;
185
+ await client.bind(userDN, password);
186
+
187
+ // Optionally search for additional user info
188
+ userEntry = await searchUser(username);
189
+ }
190
+
191
+ // Get group memberships
192
+ const groups = userEntry ? await getGroups(userEntry.dn || userDN) : [];
193
+
194
+ // Build profile
195
+ const profile = {
196
+ dn: userDN,
197
+ username: userEntry?.[usernameField] || username,
198
+ displayName: userEntry?.cn || username,
199
+ email: userEntry?.mail,
200
+ groups,
201
+ memberOf: userEntry?.memberOf || [],
202
+ raw: userEntry,
203
+ };
204
+
205
+ return {
206
+ type: LDAPMessageType.AUTH_OK,
207
+ userId: username,
208
+ profile,
209
+ groups,
210
+ };
211
+ } catch (err) {
212
+ // Map LDAP errors to our error codes
213
+ let errorCode = LDAPError.BIND_ERROR;
214
+ let message = err.message || "Authentication failed";
215
+
216
+ if (err.code === "LDAP_INVALID_CREDENTIALS" || err.message?.includes("Invalid credentials")) {
217
+ errorCode = LDAPError.INVALID_CREDENTIALS;
218
+ message = "Invalid username or password";
219
+ } else if (err.code === "LDAP_NO_SUCH_OBJECT" || err.message?.includes("not found")) {
220
+ errorCode = LDAPError.USER_NOT_FOUND;
221
+ message = "User not found";
222
+ } else if (err.code === "ETIMEDOUT" || err.code === "ECONNREFUSED") {
223
+ errorCode = LDAPError.CONNECTION_ERROR;
224
+ message = "Could not connect to LDAP server";
225
+ } else if (err.code === "ENOTFOUND") {
226
+ errorCode = LDAPError.SERVER_UNAVAILABLE;
227
+ message = "LDAP server unavailable";
228
+ }
229
+
230
+ return {
231
+ type: LDAPMessageType.AUTH_FAIL,
232
+ error: errorCode,
233
+ message,
234
+ };
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Passport.js authenticate method
240
+ *
241
+ * @param {Object} req - Request object with username/password
242
+ * @param {Object} [options] - Authentication options
243
+ */
244
+ strategy.authenticate = function (req, options = {}) {
245
+ const self = this;
246
+ const username = req.username || req.body?.username;
247
+ const password = req.password || req.body?.password;
248
+
249
+ if (!username || !password) {
250
+ return self.fail({ message: "Missing credentials" }, 400);
251
+ }
252
+
253
+ handleAuth({ username, password })
254
+ .then((result) => {
255
+ if (result.type === LDAPMessageType.AUTH_FAIL) {
256
+ return self.fail({ message: result.message, code: result.error });
257
+ }
258
+
259
+ // Call verify callback if provided (Passport.js pattern)
260
+ if (verify) {
261
+ /**
262
+ * Passport.js verified callback
263
+ * @param {Error|null} err - Error if verification failed
264
+ * @param {Object|false} user - User object or false
265
+ * @param {Object} [info] - Additional info
266
+ * @returns {void}
267
+ */
268
+ const verified = (err, user, info) => {
269
+ if (err) return self.error(err);
270
+ if (!user) return self.fail(info || { message: "Verification failed" });
271
+ return self.success(user, info);
272
+ };
273
+
274
+ try {
275
+ if (passReqToCallback) {
276
+ verify(req, result.profile, verified);
277
+ } else {
278
+ verify(result.profile, verified);
279
+ }
280
+ } catch (err) {
281
+ return self.error(err);
282
+ }
283
+ } else {
284
+ // No verify callback, return profile directly
285
+ return self.success(result.profile, { userId: result.userId });
286
+ }
287
+ })
288
+ .catch((err) => {
289
+ if (typeof self.error === "function") {
290
+ self.error(err);
291
+ }
292
+ });
293
+ };
294
+
295
+ /**
296
+ * Register a test user (for mock mode)
297
+ *
298
+ * @param {string} username - Username
299
+ * @param {string} password - Password
300
+ * @param {Object} [attributes={}] - Additional attributes
301
+ * @returns {Promise<boolean>} Success
302
+ */
303
+ async function registerTestUser(username, password, attributes = {}) {
304
+ return storage.saveUser(username, {
305
+ password,
306
+ cn: attributes.cn || username,
307
+ mail: attributes.mail || `${username}@example.com`,
308
+ memberOf: attributes.memberOf || [],
309
+ attributes,
310
+ });
311
+ }
312
+
313
+ /**
314
+ * Cleanup resources
315
+ */
316
+ function cleanup() {
317
+ if (client.destroy) {
318
+ client.destroy();
319
+ }
320
+ }
321
+
322
+ return {
323
+ // Passport.js Strategy interface
324
+ name: strategy.name,
325
+ authenticate: strategy.authenticate,
326
+
327
+ // Direct message handlers
328
+ handleAuth,
329
+
330
+ // Test utilities
331
+ registerTestUser,
332
+
333
+ // Lifecycle
334
+ cleanup,
335
+
336
+ // Config access (for framework integration)
337
+ _config: {
338
+ url,
339
+ baseDN,
340
+ usernameField,
341
+ },
342
+ };
343
+ }
344
+
345
+ // Passport.js style alias
346
+ const LDAPStrategy = createLDAPStrategy;
347
+
348
+ module.exports = {
349
+ createLDAPStrategy,
350
+ LDAPStrategy,
351
+ LDAPMessageType,
352
+ LDAPError,
353
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @fileoverview OAuth2 Constants
3
+ * @module server/security/auth/adapters/oauth2/constants
4
+ */
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * OAuth2 message types
10
+ * @enum {string}
11
+ */
12
+ const OAuth2MessageType = {
13
+ AUTH_START: "oauth2_auth_start",
14
+ AUTH_REDIRECT: "oauth2_auth_redirect",
15
+ AUTH_CALLBACK: "oauth2_callback",
16
+ AUTH_OK: "oauth2_auth_ok",
17
+ AUTH_FAIL: "oauth2_auth_fail",
18
+ TOKEN_REFRESH: "oauth2_token_refresh",
19
+ TOKEN_REFRESHED: "oauth2_token_refreshed",
20
+ };
21
+
22
+ /**
23
+ * OAuth2 error codes
24
+ * @enum {string}
25
+ */
26
+ const OAuth2Error = {
27
+ INVALID_CODE: "INVALID_CODE",
28
+ INVALID_STATE: "INVALID_STATE",
29
+ INVALID_TOKEN: "INVALID_TOKEN",
30
+ TOKEN_EXPIRED: "TOKEN_EXPIRED",
31
+ SCOPE_ERROR: "SCOPE_ERROR",
32
+ PROVIDER_ERROR: "PROVIDER_ERROR",
33
+ CONFIG_ERROR: "CONFIG_ERROR",
34
+ NETWORK_ERROR: "NETWORK_ERROR",
35
+ MISSING_CODE: "MISSING_CODE",
36
+ };
37
+
38
+ module.exports = {
39
+ OAuth2MessageType,
40
+ OAuth2Error,
41
+ };
@@ -0,0 +1,19 @@
1
+ # OAuth2 Adapter Module
2
+
3
+ OAuth2 Authorization Code flow helpers.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ oauth2/
9
+ ├── constants.js - OAuth2 message types and error codes
10
+ └── helpers.js - State management and PKCE utilities
11
+ ```
12
+
13
+ ## Files
14
+
15
+ ### `constants.js`
16
+ Defines OAuth2MessageType (AUTH_START, AUTH_REDIRECT, AUTH_CALLBACK, etc.) and OAuth2Error codes.
17
+
18
+ ### `helpers.js`
19
+ State storage, PKCE code verifier/challenge generation, and mock token management.