ldap-rest 0.1.2 → 0.1.4
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/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/errors.js +1 -0
- package/dist/lib/expressFormatedResponses.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/plugins/auth/trustedProxy.js +1 -0
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/organization.js +1 -1
- package/dist/plugins/priority.json.js +1 -1
- package/dist/plugins/twake/james.js +1 -1
- package/dist/plugins/weblogs.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +9 -0
- package/dist/src/abstract/plugin.d.ts +1 -0
- package/dist/src/bin/index.d.ts +5 -0
- package/dist/src/browser/ldap-user-editor/components/UserTree.d.ts +2 -1
- package/dist/src/browser/shared/components/DisposableComponent.d.ts +29 -0
- package/dist/src/browser/shared/components/Modal.d.ts +6 -1
- package/dist/src/config/args.d.ts +2 -0
- package/dist/src/lib/errors.d.ts +47 -0
- package/dist/src/lib/ldapActions.d.ts +9 -0
- package/dist/src/lib/utils.d.ts +17 -0
- package/dist/src/plugins/auth/trustedProxy.d.ts +67 -0
- package/dist/src/plugins/ldap/groups.d.ts +9 -0
- package/dist/src/plugins/twake/james.d.ts +9 -0
- package/dist/src/plugins/weblogs.d.ts +1 -0
- package/docs/authentication.md +133 -0
- package/docs/twakeJames.md +61 -0
- package/package.json +12 -1
- package/static/browser/ldap-user-editor/components/UserTree.js +6 -3
- package/static/browser/ldap-user-editor/components/UserTree.js.map +1 -1
- package/static/browser/ldap-user-editor.esm.js +62 -3
- package/static/browser/ldap-user-editor.esm.js.map +1 -1
- package/static/browser/ldap-user-editor.js +62 -3
- package/static/browser/ldap-user-editor.js.map +1 -1
- package/static/browser/ldap-user-editor.min.js +1 -1
- package/static/browser/ldap-user-editor.min.js.map +1 -1
- package/static/browser/shared/components/DisposableComponent.js +59 -0
- package/static/browser/shared/components/DisposableComponent.js.map +1 -0
- package/static/browser/shared/components/Modal.js +16 -6
- package/static/browser/shared/components/Modal.js.map +1 -1
- package/static/browser/shared.esm.js +72 -6
- package/static/browser/shared.esm.js.map +1 -1
- package/static/browser/shared.js +72 -6
- package/static/browser/shared.js.map +1 -1
- package/static/browser/shared.min.js +1 -1
- package/static/browser/shared.min.js.map +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom HTTP Error classes with status codes
|
|
3
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Base HTTP Error class with status code
|
|
7
|
+
*/
|
|
8
|
+
export declare class HttpError extends Error {
|
|
9
|
+
statusCode: number;
|
|
10
|
+
constructor(message: string, statusCode?: number);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Client errors (4xx)
|
|
14
|
+
*/
|
|
15
|
+
export declare class BadRequestError extends HttpError {
|
|
16
|
+
constructor(message?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class UnauthorizedError extends HttpError {
|
|
19
|
+
constructor(message?: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class ForbiddenError extends HttpError {
|
|
22
|
+
constructor(message?: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class NotFoundError extends HttpError {
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class ConflictError extends HttpError {
|
|
28
|
+
constructor(message?: string);
|
|
29
|
+
}
|
|
30
|
+
export declare class UriTooLongError extends HttpError {
|
|
31
|
+
constructor(message?: string);
|
|
32
|
+
}
|
|
33
|
+
export declare class TooManyRequestsError extends HttpError {
|
|
34
|
+
constructor(message?: string);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Server errors (5xx)
|
|
38
|
+
*/
|
|
39
|
+
export declare class BadGatewayError extends HttpError {
|
|
40
|
+
constructor(message?: string);
|
|
41
|
+
}
|
|
42
|
+
export declare class ServiceUnavailableError extends HttpError {
|
|
43
|
+
constructor(message?: string);
|
|
44
|
+
}
|
|
45
|
+
export declare class GatewayTimeoutError extends HttpError {
|
|
46
|
+
constructor(message?: string);
|
|
47
|
+
}
|
|
@@ -32,6 +32,9 @@ declare class ldapActions {
|
|
|
32
32
|
private connectionTtl;
|
|
33
33
|
private ldapUrls;
|
|
34
34
|
private currentUrlIndex;
|
|
35
|
+
private attrSignatureCache;
|
|
36
|
+
private waitingResolvers;
|
|
37
|
+
private availableConnections;
|
|
35
38
|
constructor(server: DM);
|
|
36
39
|
/**
|
|
37
40
|
* Create a new LDAP connection with failover support
|
|
@@ -43,12 +46,18 @@ declare class ldapActions {
|
|
|
43
46
|
private cleanupExpiredConnections;
|
|
44
47
|
/**
|
|
45
48
|
* Acquire a connection from the pool or create a new one
|
|
49
|
+
* Optimized for O(1) lookup using separate available connections queue
|
|
46
50
|
*/
|
|
47
51
|
private acquireConnection;
|
|
48
52
|
/**
|
|
49
53
|
* Release a connection back to the pool
|
|
54
|
+
* If there are waiting requests, hand off the connection directly
|
|
50
55
|
*/
|
|
51
56
|
private releaseConnection;
|
|
57
|
+
/**
|
|
58
|
+
* Get a sorted signature for attribute list, using cache for performance
|
|
59
|
+
*/
|
|
60
|
+
private getAttributeSignature;
|
|
52
61
|
/**
|
|
53
62
|
* Generate cache key for LDAP search
|
|
54
63
|
*/
|
package/dist/src/lib/utils.d.ts
CHANGED
|
@@ -7,6 +7,23 @@ import type { Config } from '../bin';
|
|
|
7
7
|
export declare const launchHooks: (hooks: Function[] | undefined, ...args: unknown[]) => Promise<void>;
|
|
8
8
|
export declare const launchHooksChained: <T>(hooks: Function[] | undefined, args: T) => Promise<T>;
|
|
9
9
|
export declare const transformSchemas: (schemas: string | Buffer, config: Config) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Escape special characters in LDAP filter values according to RFC 4515
|
|
12
|
+
* Prevents LDAP injection attacks by escaping characters that have special meaning in LDAP filters
|
|
13
|
+
*
|
|
14
|
+
* @param value - The value to escape
|
|
15
|
+
* @returns The escaped value safe for use in LDAP filters
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* escapeLdapFilter('user*')
|
|
20
|
+
* // => 'user\\2a'
|
|
21
|
+
*
|
|
22
|
+
* escapeLdapFilter('Smith, John (admin)')
|
|
23
|
+
* // => 'Smith, John \\28admin\\29'
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function escapeLdapFilter(value: string): string;
|
|
10
27
|
/**
|
|
11
28
|
* Parse a Distinguished Name (DN) into its component parts (RDNs)
|
|
12
29
|
* Handles escaped commas and other special characters
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/auth/trustedProxy
|
|
3
|
+
* Trusted proxy plugin to validate X-Forwarded-For headers
|
|
4
|
+
*
|
|
5
|
+
* This plugin validates that X-Forwarded-For headers only come from trusted
|
|
6
|
+
* upstream reverse proxies. If the request comes from an untrusted IP,
|
|
7
|
+
* the X-Forwarded-For header is removed to prevent IP spoofing attacks.
|
|
8
|
+
*
|
|
9
|
+
* This plugin MUST be loaded BEFORE any plugin that uses X-Forwarded-For
|
|
10
|
+
* (like crowdsec and rateLimit) to ensure they receive sanitized headers.
|
|
11
|
+
*
|
|
12
|
+
* Configuration:
|
|
13
|
+
* - trusted_proxy: Array of trusted proxy IPs or CIDR ranges (required)
|
|
14
|
+
* Example: ["127.0.0.1", "10.0.0.0/8", "192.168.1.0/24", "::1"]
|
|
15
|
+
* - trusted_proxy_auth_header: Header name containing the authenticated user
|
|
16
|
+
* from trusted proxy (optional, default: "Auth-User")
|
|
17
|
+
*
|
|
18
|
+
* When a request comes from a trusted proxy:
|
|
19
|
+
* - X-Forwarded-For header is preserved
|
|
20
|
+
* - The request is marked as trusted (req.trustedProxy = true)
|
|
21
|
+
* - If trusted_proxy_auth_header is set and the header is present,
|
|
22
|
+
* req.proxyAuthUser is set to its value for use by other plugins (e.g., weblogs)
|
|
23
|
+
*
|
|
24
|
+
* @author Xavier Guimard <xguimard@linagora.com>
|
|
25
|
+
*/
|
|
26
|
+
import type { Express } from 'express';
|
|
27
|
+
import DmPlugin, { type Role } from '../../abstract/plugin';
|
|
28
|
+
declare global {
|
|
29
|
+
namespace Express {
|
|
30
|
+
interface Request {
|
|
31
|
+
trustedProxy?: boolean;
|
|
32
|
+
proxyAuthUser?: string;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export default class TrustedProxy extends DmPlugin {
|
|
37
|
+
name: string;
|
|
38
|
+
roles: Role[];
|
|
39
|
+
private trustedNetworks;
|
|
40
|
+
private authHeader;
|
|
41
|
+
constructor(...args: ConstructorParameters<typeof DmPlugin>);
|
|
42
|
+
api(app: Express): void;
|
|
43
|
+
/**
|
|
44
|
+
* Check if an IP address belongs to a trusted proxy network
|
|
45
|
+
*/
|
|
46
|
+
private isTrustedProxy;
|
|
47
|
+
/**
|
|
48
|
+
* Normalize IP address (handle IPv4-mapped IPv6 addresses)
|
|
49
|
+
*/
|
|
50
|
+
private normalizeIP;
|
|
51
|
+
/**
|
|
52
|
+
* Parse an IP address into a bigint for comparison
|
|
53
|
+
*/
|
|
54
|
+
private parseIP;
|
|
55
|
+
/**
|
|
56
|
+
* Parse a CIDR notation string into network address and mask
|
|
57
|
+
*/
|
|
58
|
+
private parseCIDR;
|
|
59
|
+
/**
|
|
60
|
+
* Parse an IPv4 address into a bigint
|
|
61
|
+
*/
|
|
62
|
+
private parseIPv4;
|
|
63
|
+
/**
|
|
64
|
+
* Parse an IPv6 address into a bigint
|
|
65
|
+
*/
|
|
66
|
+
private parseIPv6;
|
|
67
|
+
}
|
|
@@ -17,6 +17,15 @@ export default class LdapGroups extends DmPlugin {
|
|
|
17
17
|
ldap: ldapActions;
|
|
18
18
|
cn: string;
|
|
19
19
|
schema?: Schema;
|
|
20
|
+
private regexCache;
|
|
21
|
+
/**
|
|
22
|
+
* Get a compiled RegExp from cache, or compile and cache it
|
|
23
|
+
*/
|
|
24
|
+
private getCompiledRegex;
|
|
25
|
+
/**
|
|
26
|
+
* Escape special regex characters in a string
|
|
27
|
+
*/
|
|
28
|
+
private escapeRegex;
|
|
20
29
|
constructor(server: DM);
|
|
21
30
|
/**
|
|
22
31
|
* Catch all deletion to remove deleted users from groups
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
1
2
|
import TwakePlugin from '../../abstract/twakePlugin';
|
|
2
3
|
import { type Role } from '../../abstract/plugin';
|
|
3
4
|
import type { DM } from '../../bin';
|
|
@@ -16,6 +17,14 @@ export default class James extends TwakePlugin {
|
|
|
16
17
|
private mailboxTypeAttr;
|
|
17
18
|
private initDelay;
|
|
18
19
|
constructor(server: DM);
|
|
20
|
+
/**
|
|
21
|
+
* Register API routes
|
|
22
|
+
*/
|
|
23
|
+
api(app: Express): void;
|
|
24
|
+
/**
|
|
25
|
+
* Get quota information for a user
|
|
26
|
+
*/
|
|
27
|
+
private getUserQuota;
|
|
19
28
|
hooks: Hooks;
|
|
20
29
|
/**
|
|
21
30
|
* Handle mailbox type transitions when twakeMailboxType changes
|
package/docs/authentication.md
CHANGED
|
@@ -11,6 +11,139 @@ LDAP-Rest provides multiple authentication plugins to secure API access. These p
|
|
|
11
11
|
5. **OpenID Connect** (`core/auth/openidconnect`) - OAuth 2.0 / OpenID Connect authentication
|
|
12
12
|
6. **Authorization Per Branch** (`core/auth/authzPerBranch`) - Branch-level access control (see [authzPerBranch.md](authzPerBranch.md))
|
|
13
13
|
|
|
14
|
+
## Security Plugins
|
|
15
|
+
|
|
16
|
+
1. **Trusted Proxy** (`core/auth/trustedProxy`) - Validate X-Forwarded-For headers from reverse proxies
|
|
17
|
+
2. **Rate Limiting** (`core/auth/rateLimit`) - Prevent brute force attacks
|
|
18
|
+
3. **CrowdSec** (`core/auth/crowdsec`) - IP reputation and blocking
|
|
19
|
+
|
|
20
|
+
## Trusted Proxy
|
|
21
|
+
|
|
22
|
+
When running behind a reverse proxy (nginx, Apache, HAProxy, etc.), client IP addresses are typically passed via `X-Forwarded-For` headers. The `trustedProxy` plugin validates these headers to prevent IP spoofing attacks.
|
|
23
|
+
|
|
24
|
+
### Configuration
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
--plugin core/auth/trustedProxy \
|
|
28
|
+
--trusted-proxy "127.0.0.1" \
|
|
29
|
+
--trusted-proxy "10.0.0.0/8" \
|
|
30
|
+
--trusted-proxy "192.168.0.0/16"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Environment Variables:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Comma-separated list of trusted proxy IPs or CIDR ranges
|
|
37
|
+
DM_TRUSTED_PROXIES="127.0.0.1,10.0.0.0/8,192.168.0.0/16,::1"
|
|
38
|
+
|
|
39
|
+
# Optional: Header name for authenticated user from proxy (default: Auth-User)
|
|
40
|
+
DM_TRUSTED_PROXY_AUTH_HEADER="Auth-User"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### How It Works
|
|
44
|
+
|
|
45
|
+
1. **Request arrives** from reverse proxy
|
|
46
|
+
2. **Plugin checks** if `req.socket.remoteAddress` matches a trusted proxy
|
|
47
|
+
3. **If trusted:**
|
|
48
|
+
- `X-Forwarded-For` header is preserved
|
|
49
|
+
- `Auth-User` header (or custom header) is extracted for logging
|
|
50
|
+
- Request is marked as `req.trustedProxy = true`
|
|
51
|
+
4. **If untrusted:**
|
|
52
|
+
- `X-Forwarded-For` header is **removed** to prevent IP spoofing
|
|
53
|
+
- Request is marked as `req.trustedProxy = false`
|
|
54
|
+
5. **Other plugins** (rate limiting, CrowdSec, weblogs) see sanitized headers
|
|
55
|
+
|
|
56
|
+
### Supported Formats
|
|
57
|
+
|
|
58
|
+
- **IPv4 addresses:** `192.168.1.1`
|
|
59
|
+
- **IPv6 addresses:** `::1`, `fe80::1`
|
|
60
|
+
- **CIDR ranges:** `10.0.0.0/8`, `192.168.0.0/16`, `2001:db8::/32`
|
|
61
|
+
- **IPv4-mapped IPv6:** Automatically handled (e.g., `::ffff:127.0.0.1` matches `127.0.0.1`)
|
|
62
|
+
|
|
63
|
+
### Auth-User Header
|
|
64
|
+
|
|
65
|
+
When requests come from a trusted proxy, the plugin can extract an authenticated username from a header set by the proxy. This is useful for:
|
|
66
|
+
|
|
67
|
+
- Logging who made the request (even if LDAP-Rest doesn't handle authentication)
|
|
68
|
+
- Passing identity from an upstream authentication system
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Custom header name (default: Auth-User)
|
|
72
|
+
DM_TRUSTED_PROXY_AUTH_HEADER="X-Remote-User"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The extracted username is:
|
|
76
|
+
- Available as `req.proxyAuthUser` for other plugins
|
|
77
|
+
- Used by the `weblogs` plugin to log the user field
|
|
78
|
+
|
|
79
|
+
### Example: nginx Configuration
|
|
80
|
+
|
|
81
|
+
```nginx
|
|
82
|
+
upstream ldap_rest {
|
|
83
|
+
server 127.0.0.1:8081;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
server {
|
|
87
|
+
listen 443 ssl;
|
|
88
|
+
server_name api.example.com;
|
|
89
|
+
|
|
90
|
+
location / {
|
|
91
|
+
proxy_pass http://ldap_rest;
|
|
92
|
+
proxy_set_header Host $host;
|
|
93
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
94
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
95
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
96
|
+
|
|
97
|
+
# Optional: Pass authenticated user from nginx auth
|
|
98
|
+
proxy_set_header Auth-User $remote_user;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
LDAP-Rest configuration:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
DM_TRUSTED_PROXIES="127.0.0.1"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Example: HAProxy Configuration
|
|
110
|
+
|
|
111
|
+
```haproxy
|
|
112
|
+
frontend https
|
|
113
|
+
bind *:443 ssl crt /etc/ssl/cert.pem
|
|
114
|
+
default_backend ldap_rest
|
|
115
|
+
|
|
116
|
+
backend ldap_rest
|
|
117
|
+
option forwardfor
|
|
118
|
+
http-request set-header X-Forwarded-Proto https
|
|
119
|
+
server ldap_rest 127.0.0.1:8081
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Use Cases
|
|
123
|
+
|
|
124
|
+
- **Reverse Proxy Deployments:** Ensure correct client IP for rate limiting and logging
|
|
125
|
+
- **Load Balancers:** Trust headers from known load balancer IPs
|
|
126
|
+
- **CDN Integration:** Trust headers from CDN edge servers
|
|
127
|
+
- **Kubernetes:** Trust headers from ingress controllers
|
|
128
|
+
- **Security:** Prevent attackers from spoofing `X-Forwarded-For` to bypass rate limits
|
|
129
|
+
|
|
130
|
+
### Security Considerations
|
|
131
|
+
|
|
132
|
+
- **Only trust known proxies:** Never use `0.0.0.0/0` or trust all IPs
|
|
133
|
+
- **Use specific IPs/ranges:** Limit to your actual proxy infrastructure
|
|
134
|
+
- **HTTPS between proxy and LDAP-Rest:** Prevent header injection attacks
|
|
135
|
+
- **Monitor logs:** Watch for warnings about removed X-Forwarded-For headers
|
|
136
|
+
|
|
137
|
+
### Plugin Load Order
|
|
138
|
+
|
|
139
|
+
The `trustedProxy` plugin is automatically loaded **first** (via `priority.json`) to ensure all other plugins see sanitized headers:
|
|
140
|
+
|
|
141
|
+
1. `core/auth/trustedProxy` - Sanitizes X-Forwarded-For headers
|
|
142
|
+
2. `core/weblogs` - Logs requests with correct client IP
|
|
143
|
+
3. `core/auth/crowdsec` - Checks IP reputation
|
|
144
|
+
4. `core/auth/rateLimit` - Rate limits by IP
|
|
145
|
+
5. Authentication plugins...
|
|
146
|
+
|
|
14
147
|
## Token Authentication
|
|
15
148
|
|
|
16
149
|
Simple stateless authentication using bearer tokens.
|
package/docs/twakeJames.md
CHANGED
|
@@ -243,6 +243,66 @@ proxyAddresses: SMTP:john.doe@company.com
|
|
|
243
243
|
|
|
244
244
|
The `smtp:` prefix is automatically stripped. The capitalization (SMTP vs smtp) is ignored - only the email address after the colon is used.
|
|
245
245
|
|
|
246
|
+
## REST API
|
|
247
|
+
|
|
248
|
+
The James plugin exposes a REST API to retrieve quota usage and limits for users.
|
|
249
|
+
|
|
250
|
+
### GET /api/v1/users/:user/quota-usage
|
|
251
|
+
|
|
252
|
+
Retrieve quota usage and limits for a user from James. This endpoint returns both the quota limits configured for the user and their current mailbox usage.
|
|
253
|
+
|
|
254
|
+
**Request:**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
GET /api/v1/users/jdoe/quota-usage
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Response (200 OK):**
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"global": { "count": 1000, "size": 1000000000 },
|
|
265
|
+
"domain": { "count": 800, "size": 800000000 },
|
|
266
|
+
"user": { "count": 500, "size": 75000000 },
|
|
267
|
+
"computed": { "count": 500, "size": 75000000 },
|
|
268
|
+
"occupation": {
|
|
269
|
+
"size": 50000000,
|
|
270
|
+
"count": 250,
|
|
271
|
+
"ratio": {
|
|
272
|
+
"size": 0.67,
|
|
273
|
+
"count": 0.5,
|
|
274
|
+
"max": 0.67
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Response Fields:**
|
|
281
|
+
|
|
282
|
+
- `global`: Global quota limit (server-wide)
|
|
283
|
+
- `domain`: Domain-specific quota limit
|
|
284
|
+
- `user`: User-specific quota limit from LDAP
|
|
285
|
+
- `computed`: Effective quota limit applied (resolved from upper values)
|
|
286
|
+
- `occupation`: **Current mailbox usage statistics**
|
|
287
|
+
- `size`: Storage currently used (bytes)
|
|
288
|
+
- `count`: Number of messages in mailbox
|
|
289
|
+
- `ratio`: Usage ratios (used / limit)
|
|
290
|
+
|
|
291
|
+
**Error Responses:**
|
|
292
|
+
|
|
293
|
+
- `404`: User not found in LDAP or quota not found in James
|
|
294
|
+
- `400`: User has no mail attribute
|
|
295
|
+
- `502`: Failed to retrieve quota from James
|
|
296
|
+
- `500`: Internal server error
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Get quota usage for user 'jdoe'
|
|
302
|
+
curl http://localhost:8081/api/v1/users/jdoe/quota-usage \
|
|
303
|
+
-H "Authorization: Bearer admin-token"
|
|
304
|
+
```
|
|
305
|
+
|
|
246
306
|
## James WebAdmin API
|
|
247
307
|
|
|
248
308
|
### Endpoints Used
|
|
@@ -251,6 +311,7 @@ The `smtp:` prefix is automatically stripped. The capitalization (SMTP vs smtp)
|
|
|
251
311
|
| ------------------- | ------------------------------------------ | ------ |
|
|
252
312
|
| Rename account | `/users/{old}/rename/{new}?action=rename` | POST |
|
|
253
313
|
| Update quota | `/quota/users/{mail}/size` | PUT |
|
|
314
|
+
| Get quota | `/quota/users/{mail}` | GET |
|
|
254
315
|
| Add delegation | `/users/{mail}/authorizedUsers/{delegate}` | PUT |
|
|
255
316
|
| Remove delegation | `/users/{mail}/authorizedUsers/{delegate}` | DELETE |
|
|
256
317
|
| Add alias | `/address/aliases/{mail}/sources/{alias}` | PUT |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-rest",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Light directory manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Identity-Manager"
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
"import": "./dist/plugins/auth/totp.js",
|
|
65
65
|
"types": "./dist/src/plugins/auth/totp.d.ts"
|
|
66
66
|
},
|
|
67
|
+
"./plugin-auth-trustedproxy": {
|
|
68
|
+
"import": "./dist/plugins/auth/trustedProxy.js",
|
|
69
|
+
"types": "./dist/src/plugins/auth/trustedProxy.d.ts"
|
|
70
|
+
},
|
|
67
71
|
"./plugin-configapi": {
|
|
68
72
|
"import": "./dist/plugins/configApi.js",
|
|
69
73
|
"types": "./dist/src/plugins/configApi.d.ts"
|
|
@@ -292,6 +296,10 @@
|
|
|
292
296
|
"import": "./static/browser/ldap-user-editor/types.js",
|
|
293
297
|
"types": "./dist/src/browser/ldap-user-editor/types.d.ts"
|
|
294
298
|
},
|
|
299
|
+
"./browser-shared-components-disposablecomponent": {
|
|
300
|
+
"import": "./static/browser/shared/components/DisposableComponent.js",
|
|
301
|
+
"types": "./dist/src/browser/shared/components/DisposableComponent.d.ts"
|
|
302
|
+
},
|
|
295
303
|
"./browser-shared-components-modal": {
|
|
296
304
|
"import": "./static/browser/shared/components/Modal.js",
|
|
297
305
|
"types": "./dist/src/browser/shared/components/Modal.d.ts"
|
|
@@ -450,6 +458,9 @@
|
|
|
450
458
|
"test:dev": "./.dev.mk test",
|
|
451
459
|
"test:one": "mocha --config .mocharc-one.json"
|
|
452
460
|
},
|
|
461
|
+
"overrides": {
|
|
462
|
+
"glob": ">=11.0.4"
|
|
463
|
+
},
|
|
453
464
|
"dependencies": {
|
|
454
465
|
"body-parser": "^2.2.0",
|
|
455
466
|
"csv-parse": "^6.1.0",
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { CacheManager } from '../cache/CacheManager.js';
|
|
2
|
+
import { DisposableComponent } from '../../shared/components/DisposableComponent.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* LDAP User Editor - Organization Tree Component
|
|
5
6
|
* Shows only LDAP organization tree (filters out users/groups)
|
|
6
7
|
*/
|
|
7
|
-
class UserTree {
|
|
8
|
+
class UserTree extends DisposableComponent {
|
|
8
9
|
constructor(container, baseUrl, onSelectOrg) {
|
|
10
|
+
super();
|
|
9
11
|
this.expandedNodes = new Set();
|
|
10
12
|
this.selectedDn = null;
|
|
11
13
|
this.rootDn = null;
|
|
@@ -13,8 +15,8 @@ class UserTree {
|
|
|
13
15
|
this.baseUrl = baseUrl;
|
|
14
16
|
this.onSelectOrg = onSelectOrg;
|
|
15
17
|
this.cache = new CacheManager();
|
|
16
|
-
// Clean expired entries every 5 minutes
|
|
17
|
-
|
|
18
|
+
// Clean expired entries every 5 minutes - now properly cleaned up
|
|
19
|
+
this.addManagedInterval(() => this.cache.cleanExpired(), 5 * 60 * 1000);
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Fetch with cache support
|
|
@@ -157,6 +159,7 @@ class UserTree {
|
|
|
157
159
|
}
|
|
158
160
|
destroy() {
|
|
159
161
|
this.container.innerHTML = '';
|
|
162
|
+
super.destroy();
|
|
160
163
|
}
|
|
161
164
|
}
|
|
162
165
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserTree.js","sources":["../../../../../src/browser/ldap-user-editor/components/UserTree.ts"],"sourcesContent":[null],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UserTree.js","sources":["../../../../../src/browser/ldap-user-editor/components/UserTree.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -324,12 +324,70 @@ class UserApiClient {
|
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Base class for browser components with proper lifecycle management
|
|
329
|
+
* Ensures all event listeners and intervals are cleaned up on destroy
|
|
330
|
+
* @module browser/shared/components/DisposableComponent
|
|
331
|
+
*/
|
|
332
|
+
class DisposableComponent {
|
|
333
|
+
constructor() {
|
|
334
|
+
this._eventListeners = [];
|
|
335
|
+
this._intervals = [];
|
|
336
|
+
this._isDestroyed = false;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Register an event listener for automatic cleanup
|
|
340
|
+
* Use this instead of element.addEventListener directly
|
|
341
|
+
*/
|
|
342
|
+
addManagedEventListener(target, type, listener, options) {
|
|
343
|
+
if (this._isDestroyed)
|
|
344
|
+
return;
|
|
345
|
+
target.addEventListener(type, listener, options);
|
|
346
|
+
this._eventListeners.push({ target, type, listener, options });
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Register an interval for automatic cleanup
|
|
350
|
+
* Use this instead of setInterval directly
|
|
351
|
+
*/
|
|
352
|
+
addManagedInterval(callback, ms) {
|
|
353
|
+
const id = setInterval(callback, ms);
|
|
354
|
+
this._intervals.push(id);
|
|
355
|
+
return id;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Check if the component has been destroyed
|
|
359
|
+
*/
|
|
360
|
+
get isDestroyed() {
|
|
361
|
+
return this._isDestroyed;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Clean up all managed resources
|
|
365
|
+
* Override in subclasses but ALWAYS call super.destroy()
|
|
366
|
+
*/
|
|
367
|
+
destroy() {
|
|
368
|
+
if (this._isDestroyed)
|
|
369
|
+
return;
|
|
370
|
+
this._isDestroyed = true;
|
|
371
|
+
// Remove all event listeners
|
|
372
|
+
for (const { target, type, listener, options } of this._eventListeners) {
|
|
373
|
+
target.removeEventListener(type, listener, options);
|
|
374
|
+
}
|
|
375
|
+
this._eventListeners = [];
|
|
376
|
+
// Clear all intervals
|
|
377
|
+
for (const id of this._intervals) {
|
|
378
|
+
clearInterval(id);
|
|
379
|
+
}
|
|
380
|
+
this._intervals = [];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
327
384
|
/**
|
|
328
385
|
* LDAP User Editor - Organization Tree Component
|
|
329
386
|
* Shows only LDAP organization tree (filters out users/groups)
|
|
330
387
|
*/
|
|
331
|
-
class UserTree {
|
|
388
|
+
class UserTree extends DisposableComponent {
|
|
332
389
|
constructor(container, baseUrl, onSelectOrg) {
|
|
390
|
+
super();
|
|
333
391
|
this.expandedNodes = new Set();
|
|
334
392
|
this.selectedDn = null;
|
|
335
393
|
this.rootDn = null;
|
|
@@ -337,8 +395,8 @@ class UserTree {
|
|
|
337
395
|
this.baseUrl = baseUrl;
|
|
338
396
|
this.onSelectOrg = onSelectOrg;
|
|
339
397
|
this.cache = new CacheManager();
|
|
340
|
-
// Clean expired entries every 5 minutes
|
|
341
|
-
|
|
398
|
+
// Clean expired entries every 5 minutes - now properly cleaned up
|
|
399
|
+
this.addManagedInterval(() => this.cache.cleanExpired(), 5 * 60 * 1000);
|
|
342
400
|
}
|
|
343
401
|
/**
|
|
344
402
|
* Fetch with cache support
|
|
@@ -481,6 +539,7 @@ class UserTree {
|
|
|
481
539
|
}
|
|
482
540
|
destroy() {
|
|
483
541
|
this.container.innerHTML = '';
|
|
542
|
+
super.destroy();
|
|
484
543
|
}
|
|
485
544
|
}
|
|
486
545
|
|