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.
- package/README.md +58 -570
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -202
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +32 -7
- package/server/README.md +287 -53
- package/server/adapters/README.md +28 -19
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +332 -27
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -221
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -225
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -308
- 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.
|