ldap-rest 0.4.4 → 0.4.6
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 +23 -0
- package/bin/sync-james.mjs +262 -86
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -53,6 +53,28 @@ LDAP-Rest uses syslog-style log levels:
|
|
|
53
53
|
|
|
54
54
|
Use `--log-level notice` for production to see web access logs without general info messages.
|
|
55
55
|
|
|
56
|
+
## Deployment
|
|
57
|
+
|
|
58
|
+
### Docker
|
|
59
|
+
|
|
60
|
+
Multi-arch (amd64/arm64) images are published on each release:
|
|
61
|
+
|
|
62
|
+
```shell
|
|
63
|
+
docker run -p 8081:8081 ghcr.io/linagora/ldap-rest:latest
|
|
64
|
+
# also available on Docker Hub as yadd/ldap-rest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Helm
|
|
68
|
+
|
|
69
|
+
A Helm chart is published as an OCI artifact on each release:
|
|
70
|
+
|
|
71
|
+
```shell
|
|
72
|
+
helm install ldap-rest oci://ghcr.io/linagora/charts/ldap-rest
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See the **[Helm chart README](./helm/ldap-rest/README.md)** for configuration
|
|
76
|
+
(image, `env`/`secrets`, ingress, probes, …).
|
|
77
|
+
|
|
56
78
|
## Configuration
|
|
57
79
|
|
|
58
80
|
See **[Configuration Guide](./docs/usage/configuration.md)** for all CLI options and environment variables.
|
|
@@ -67,6 +89,7 @@ Installation, configuration and plugin usage.
|
|
|
67
89
|
|
|
68
90
|
- **[Getting Started](./docs/usage/README.md)** - Quick start guide
|
|
69
91
|
- **[Configuration](./docs/usage/configuration.md)** - CLI options and environment variables
|
|
92
|
+
- **[Helm chart](./helm/ldap-rest/README.md)** - Kubernetes deployment
|
|
70
93
|
- **[Plugins](./docs/usage/plugins/README.md)** - Plugin documentation
|
|
71
94
|
- **[Authentication](./docs/usage/plugins/auth/README.md)** - Token, TOTP, HMAC, LemonLDAP::NG, OpenID Connect
|
|
72
95
|
|
package/bin/sync-james.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Sync utility to verify and fix consistency between LDAP and James
|
|
4
|
-
* Ensures James quotas match LDAP mailQuota values
|
|
4
|
+
* Ensures James quotas match LDAP mailQuota values and that James aliases
|
|
5
|
+
* match the LDAP alias attribute (catch-up in case the event-based sync failed)
|
|
5
6
|
* @author Generated with Claude Code
|
|
6
7
|
*/
|
|
7
8
|
|
|
@@ -12,18 +13,27 @@ import { DM } from '../dist/bin/index.js';
|
|
|
12
13
|
const args = process.argv.slice(2);
|
|
13
14
|
const quiet = args.includes('--quiet') || args.includes('-q');
|
|
14
15
|
const dryRun = args.includes('--dry-run') || args.includes('-n');
|
|
16
|
+
const noAliasDelete = args.includes('--no-alias-delete');
|
|
15
17
|
|
|
16
18
|
if (args.includes('--help') || args.includes('-h')) {
|
|
17
19
|
console.log(`
|
|
18
20
|
Usage: sync-james [options]
|
|
19
21
|
|
|
20
|
-
Synchronizes James
|
|
21
|
-
|
|
22
|
+
Synchronizes James with LDAP. LDAP is considered the source of truth.
|
|
23
|
+
|
|
24
|
+
For every user it reconciles:
|
|
25
|
+
- the mailbox quota (James quota <- LDAP mailQuota)
|
|
26
|
+
- the mail aliases (James aliases <- LDAP alias attribute)
|
|
27
|
+
|
|
28
|
+
The alias reconciliation is a catch-up mechanism: it repairs aliases that
|
|
29
|
+
were not propagated to James (e.g. when the event-based sync failed).
|
|
22
30
|
|
|
23
31
|
Options:
|
|
24
|
-
--quiet, -q
|
|
25
|
-
--dry-run, -n
|
|
26
|
-
--
|
|
32
|
+
--quiet, -q Only show summary and errors
|
|
33
|
+
--dry-run, -n Show what would be changed without making changes
|
|
34
|
+
--no-alias-delete Do not remove James aliases that are absent from LDAP
|
|
35
|
+
(only add missing ones)
|
|
36
|
+
--help, -h Show this help message
|
|
27
37
|
|
|
28
38
|
Environment variables:
|
|
29
39
|
DM_JAMES_WEBADMIN_URL James WebAdmin URL (required)
|
|
@@ -31,16 +41,45 @@ Environment variables:
|
|
|
31
41
|
DM_LDAP_BASE LDAP search base
|
|
32
42
|
DM_MAIL_ATTRIBUTE Mail attribute name (default: mail)
|
|
33
43
|
DM_QUOTA_ATTRIBUTE Quota attribute name (default: mailQuota)
|
|
44
|
+
DM_ALIAS_ATTRIBUTE Alias attribute name (default: mailAlternateAddress)
|
|
34
45
|
`);
|
|
35
46
|
process.exit(0);
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Normalize an email alias - handle AD format (smtp:alias@domain.com).
|
|
51
|
+
* Mirrors the behaviour of TwakePlugin.normalizeAlias in the main codebase.
|
|
52
|
+
* @param {string} alias
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function normalizeAlias(alias) {
|
|
56
|
+
if (alias.toLowerCase().startsWith('smtp:')) {
|
|
57
|
+
return alias.substring(5);
|
|
58
|
+
}
|
|
59
|
+
return alias;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract normalized aliases from an LDAP attribute value.
|
|
64
|
+
* @param {string|string[]|Buffer|Buffer[]|undefined} value
|
|
65
|
+
* @returns {string[]}
|
|
66
|
+
*/
|
|
67
|
+
function getAliases(value) {
|
|
68
|
+
if (!value) return [];
|
|
69
|
+
const aliases = Array.isArray(value) ? value : [value];
|
|
70
|
+
return aliases
|
|
71
|
+
.map(a => (Buffer.isBuffer(a) ? a.toString('utf-8') : String(a)))
|
|
72
|
+
.map(a => normalizeAlias(a))
|
|
73
|
+
.filter(a => a.length > 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function syncJames() {
|
|
39
77
|
const dm = new DM();
|
|
40
78
|
await dm.ready;
|
|
41
79
|
|
|
42
80
|
const mailAttr = dm.config.mail_attribute || 'mail';
|
|
43
81
|
const quotaAttr = dm.config.quota_attribute || 'mailQuota';
|
|
82
|
+
const aliasAttr = dm.config.alias_attribute || 'mailAlternateAddress';
|
|
44
83
|
const jamesUrl = dm.config.james_webadmin_url;
|
|
45
84
|
const jamesToken = dm.config.james_webadmin_token;
|
|
46
85
|
|
|
@@ -49,8 +88,13 @@ async function syncJamesQuotas() {
|
|
|
49
88
|
process.exit(1);
|
|
50
89
|
}
|
|
51
90
|
|
|
91
|
+
const headers = {};
|
|
92
|
+
if (jamesToken) {
|
|
93
|
+
headers.Authorization = `Bearer ${jamesToken}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
52
96
|
if (!quiet) {
|
|
53
|
-
dm.logger.info('Starting LDAP to James
|
|
97
|
+
dm.logger.info('Starting LDAP to James synchronization...');
|
|
54
98
|
dm.logger.info(`LDAP base: ${dm.config.ldap_base}`);
|
|
55
99
|
dm.logger.info(`James URL: ${jamesUrl}`);
|
|
56
100
|
if (dryRun) {
|
|
@@ -59,18 +103,196 @@ async function syncJamesQuotas() {
|
|
|
59
103
|
dm.logger.info('');
|
|
60
104
|
}
|
|
61
105
|
|
|
106
|
+
const stats = {
|
|
107
|
+
checked: 0,
|
|
108
|
+
quotaSynced: 0,
|
|
109
|
+
aliasesAdded: 0,
|
|
110
|
+
aliasesDeleted: 0,
|
|
111
|
+
errors: 0,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reconcile the mailbox quota for a single user.
|
|
116
|
+
*/
|
|
117
|
+
async function syncQuota(dn, mail, ldapQuota) {
|
|
118
|
+
if (isNaN(ldapQuota)) return;
|
|
119
|
+
|
|
120
|
+
const url = `${jamesUrl}/quota/users/${mail}/size`;
|
|
121
|
+
const getRes = await fetch(url, { method: 'GET', headers });
|
|
122
|
+
|
|
123
|
+
if (!getRes.ok) {
|
|
124
|
+
if (getRes.status === 404) {
|
|
125
|
+
dm.logger.warn(`User ${mail} not found in James, skipping quota (DN: ${dn})`);
|
|
126
|
+
} else {
|
|
127
|
+
dm.logger.error(
|
|
128
|
+
`Error getting quota for ${mail}: ${getRes.status} ${getRes.statusText}`
|
|
129
|
+
);
|
|
130
|
+
stats.errors++;
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const jamesQuota = Number(await getRes.text());
|
|
136
|
+
|
|
137
|
+
if (jamesQuota === ldapQuota) {
|
|
138
|
+
if (!quiet) {
|
|
139
|
+
dm.logger.info(`${mail}: quota OK (${ldapQuota})`);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
dm.logger.warn(
|
|
145
|
+
`${mail}: quota mismatch - LDAP: ${ldapQuota}, James: ${jamesQuota}`
|
|
146
|
+
);
|
|
147
|
+
if (dryRun) {
|
|
148
|
+
dm.logger.info(` Would update James quota to ${ldapQuota}`);
|
|
149
|
+
stats.quotaSynced++;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!quiet) {
|
|
154
|
+
dm.logger.info(` Updating James quota to ${ldapQuota}...`);
|
|
155
|
+
}
|
|
156
|
+
const putRes = await fetch(url, {
|
|
157
|
+
method: 'PUT',
|
|
158
|
+
headers,
|
|
159
|
+
body: ldapQuota.toString(),
|
|
160
|
+
});
|
|
161
|
+
if (putRes.ok) {
|
|
162
|
+
if (!quiet) {
|
|
163
|
+
dm.logger.info(` Updated successfully`);
|
|
164
|
+
}
|
|
165
|
+
stats.quotaSynced++;
|
|
166
|
+
} else {
|
|
167
|
+
dm.logger.error(` Failed to update quota: ${putRes.status} ${putRes.statusText}`);
|
|
168
|
+
stats.errors++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Fetch the aliases currently declared in James for a destination mail.
|
|
174
|
+
* @returns {Promise<string[]|null>} list of source addresses, or null on error
|
|
175
|
+
*/
|
|
176
|
+
async function fetchJamesAliases(mail) {
|
|
177
|
+
const res = await fetch(`${jamesUrl}/address/aliases/${mail}`, {
|
|
178
|
+
method: 'GET',
|
|
179
|
+
headers,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// James returns 404 when the destination has no alias at all
|
|
183
|
+
if (res.status === 404) {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
if (!res.ok) {
|
|
187
|
+
dm.logger.error(
|
|
188
|
+
`Error getting aliases for ${mail}: ${res.status} ${res.statusText}`
|
|
189
|
+
);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const body = await res.json();
|
|
194
|
+
if (!Array.isArray(body)) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
// James returns [{ source: 'alias@domain' }, ...]
|
|
198
|
+
return body
|
|
199
|
+
.map(e => (e && typeof e.source === 'string' ? e.source : null))
|
|
200
|
+
.filter(Boolean);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Reconcile the aliases for a single user (catch-up).
|
|
205
|
+
* LDAP is the source of truth: missing aliases are added, extra aliases in
|
|
206
|
+
* James are removed (unless --no-alias-delete is set).
|
|
207
|
+
*/
|
|
208
|
+
async function syncAliases(dn, mail, ldapAliasesRaw) {
|
|
209
|
+
const ldapAliases = getAliases(ldapAliasesRaw);
|
|
210
|
+
|
|
211
|
+
const jamesAliases = await fetchJamesAliases(mail);
|
|
212
|
+
if (jamesAliases === null) {
|
|
213
|
+
stats.errors++;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Case-insensitive comparison to avoid spurious add/delete churn
|
|
218
|
+
const ldapSet = new Set(ldapAliases.map(a => a.toLowerCase()));
|
|
219
|
+
const jamesSet = new Set(jamesAliases.map(a => a.toLowerCase()));
|
|
220
|
+
|
|
221
|
+
const toAdd = ldapAliases.filter(a => !jamesSet.has(a.toLowerCase()));
|
|
222
|
+
const toDelete = noAliasDelete
|
|
223
|
+
? []
|
|
224
|
+
: jamesAliases.filter(a => !ldapSet.has(a.toLowerCase()));
|
|
225
|
+
|
|
226
|
+
if (toAdd.length === 0 && toDelete.length === 0) {
|
|
227
|
+
if (!quiet && (ldapAliases.length > 0 || jamesAliases.length > 0)) {
|
|
228
|
+
dm.logger.info(`${mail}: aliases OK (${ldapAliases.length})`);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const alias of toAdd) {
|
|
234
|
+
dm.logger.warn(`${mail}: missing alias in James - ${alias}`);
|
|
235
|
+
if (dryRun) {
|
|
236
|
+
dm.logger.info(` Would add alias ${alias}`);
|
|
237
|
+
stats.aliasesAdded++;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const res = await fetch(
|
|
241
|
+
`${jamesUrl}/address/aliases/${mail}/sources/${alias}`,
|
|
242
|
+
{ method: 'PUT', headers }
|
|
243
|
+
);
|
|
244
|
+
// 409 = alias already exists, treat as success (mirrors plugin behaviour)
|
|
245
|
+
if (res.ok || res.status === 409) {
|
|
246
|
+
if (!quiet) {
|
|
247
|
+
dm.logger.info(` Added alias ${alias}`);
|
|
248
|
+
}
|
|
249
|
+
stats.aliasesAdded++;
|
|
250
|
+
} else {
|
|
251
|
+
dm.logger.error(
|
|
252
|
+
` Failed to add alias ${alias}: ${res.status} ${res.statusText}`
|
|
253
|
+
);
|
|
254
|
+
stats.errors++;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const alias of toDelete) {
|
|
259
|
+
dm.logger.warn(`${mail}: stale alias in James - ${alias}`);
|
|
260
|
+
if (dryRun) {
|
|
261
|
+
dm.logger.info(` Would delete alias ${alias}`);
|
|
262
|
+
stats.aliasesDeleted++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const res = await fetch(
|
|
266
|
+
`${jamesUrl}/address/aliases/${mail}/sources/${alias}`,
|
|
267
|
+
{ method: 'DELETE', headers }
|
|
268
|
+
);
|
|
269
|
+
if (res.ok) {
|
|
270
|
+
if (!quiet) {
|
|
271
|
+
dm.logger.info(` Deleted alias ${alias}`);
|
|
272
|
+
}
|
|
273
|
+
stats.aliasesDeleted++;
|
|
274
|
+
} else {
|
|
275
|
+
dm.logger.error(
|
|
276
|
+
` Failed to delete alias ${alias}: ${res.status} ${res.statusText}`
|
|
277
|
+
);
|
|
278
|
+
stats.errors++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
62
283
|
try {
|
|
63
|
-
// Search for
|
|
284
|
+
// Search for every user that has a mail (paginated for large directories).
|
|
285
|
+
// We enumerate all mailboxes, not only those carrying a quota/alias, so
|
|
286
|
+
// that a user whose alias attribute was cleared still gets its stale James
|
|
287
|
+
// aliases removed (LDAP is the source of truth). Destinations whose mail is
|
|
288
|
+
// absent from LDAP are intentionally left alone: James refuses delivery for
|
|
289
|
+
// an alias pointing to a non-existent mailbox, so they are harmless.
|
|
64
290
|
const resultGenerator = await dm.ldap.search({
|
|
65
291
|
paged: true,
|
|
66
|
-
filter: `(
|
|
67
|
-
attributes: [mailAttr, quotaAttr, 'dn'],
|
|
292
|
+
filter: `(${mailAttr}=*)`,
|
|
293
|
+
attributes: [mailAttr, quotaAttr, aliasAttr, 'dn'],
|
|
68
294
|
});
|
|
69
295
|
|
|
70
|
-
let checked = 0;
|
|
71
|
-
let synced = 0;
|
|
72
|
-
let errors = 0;
|
|
73
|
-
|
|
74
296
|
// Process results page by page
|
|
75
297
|
for await (const result of resultGenerator) {
|
|
76
298
|
if (!result.searchEntries || result.searchEntries.length === 0) {
|
|
@@ -88,92 +310,46 @@ async function syncJamesQuotas() {
|
|
|
88
310
|
const mail = Array.isArray(entry[mailAttr])
|
|
89
311
|
? entry[mailAttr][0]
|
|
90
312
|
: entry[mailAttr];
|
|
91
|
-
const ldapQuota = Array.isArray(entry[quotaAttr])
|
|
92
|
-
? Number(entry[quotaAttr][0])
|
|
93
|
-
: Number(entry[quotaAttr]);
|
|
94
313
|
|
|
95
|
-
if (!mail
|
|
96
|
-
dm.logger.warn(`Skipping ${dn}: invalid mail
|
|
314
|
+
if (!mail) {
|
|
315
|
+
dm.logger.warn(`Skipping ${dn}: invalid mail`);
|
|
97
316
|
continue;
|
|
98
317
|
}
|
|
99
318
|
|
|
100
|
-
checked++;
|
|
319
|
+
stats.checked++;
|
|
101
320
|
|
|
102
321
|
try {
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const getRes = await fetch(getUrl, { method: 'GET', headers });
|
|
111
|
-
|
|
112
|
-
if (!getRes.ok) {
|
|
113
|
-
if (getRes.status === 404) {
|
|
114
|
-
dm.logger.warn(
|
|
115
|
-
`User ${mail} not found in James, skipping (DN: ${dn})`
|
|
116
|
-
);
|
|
117
|
-
} else {
|
|
118
|
-
dm.logger.error(
|
|
119
|
-
`Error getting quota for ${mail}: ${getRes.status} ${getRes.statusText}`
|
|
120
|
-
);
|
|
121
|
-
errors++;
|
|
122
|
-
}
|
|
123
|
-
continue;
|
|
322
|
+
// Reconcile quota only when the user has a quota attribute
|
|
323
|
+
if (entry[quotaAttr] !== undefined) {
|
|
324
|
+
const ldapQuota = Array.isArray(entry[quotaAttr])
|
|
325
|
+
? Number(entry[quotaAttr][0])
|
|
326
|
+
: Number(entry[quotaAttr]);
|
|
327
|
+
await syncQuota(dn, mail, ldapQuota);
|
|
124
328
|
}
|
|
125
329
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!quiet) {
|
|
130
|
-
dm.logger.info(`${mail}: quota OK (${ldapQuota})`);
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
dm.logger.warn(
|
|
134
|
-
`${mail}: quota mismatch - LDAP: ${ldapQuota}, James: ${jamesQuota}`
|
|
135
|
-
);
|
|
136
|
-
if (dryRun) {
|
|
137
|
-
dm.logger.info(` Would update James quota to ${ldapQuota}`);
|
|
138
|
-
synced++;
|
|
139
|
-
} else {
|
|
140
|
-
if (!quiet) {
|
|
141
|
-
dm.logger.info(` Updating James quota to ${ldapQuota}...`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Update James quota
|
|
145
|
-
const putUrl = `${jamesUrl}/quota/users/${mail}/size`;
|
|
146
|
-
const putRes = await fetch(putUrl, {
|
|
147
|
-
method: 'PUT',
|
|
148
|
-
headers,
|
|
149
|
-
body: ldapQuota.toString(),
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (putRes.ok) {
|
|
153
|
-
if (!quiet) {
|
|
154
|
-
dm.logger.info(` Updated successfully`);
|
|
155
|
-
}
|
|
156
|
-
synced++;
|
|
157
|
-
} else {
|
|
158
|
-
dm.logger.error(
|
|
159
|
-
` Failed to update: ${putRes.status} ${putRes.statusText}`
|
|
160
|
-
);
|
|
161
|
-
errors++;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
330
|
+
// Reconcile aliases (catch-up). Always run so that stale aliases
|
|
331
|
+
// are removed even when the LDAP attribute has been cleared.
|
|
332
|
+
await syncAliases(dn, mail, entry[aliasAttr]);
|
|
165
333
|
} catch (err) {
|
|
166
334
|
dm.logger.error(`Error processing ${mail}: ${err.message}`);
|
|
167
|
-
errors++;
|
|
335
|
+
stats.errors++;
|
|
168
336
|
}
|
|
169
337
|
}
|
|
170
338
|
}
|
|
171
339
|
|
|
172
340
|
dm.logger.info('\n' + '='.repeat(60));
|
|
173
341
|
dm.logger.info('Synchronization summary:');
|
|
174
|
-
dm.logger.info(` Users checked: ${checked}`);
|
|
175
|
-
dm.logger.info(
|
|
176
|
-
|
|
342
|
+
dm.logger.info(` Users checked: ${stats.checked}`);
|
|
343
|
+
dm.logger.info(
|
|
344
|
+
` Quotas ${dryRun ? 'needing sync' : 'synced'}: ${stats.quotaSynced}`
|
|
345
|
+
);
|
|
346
|
+
dm.logger.info(
|
|
347
|
+
` Aliases ${dryRun ? 'to add' : 'added'}: ${stats.aliasesAdded}`
|
|
348
|
+
);
|
|
349
|
+
dm.logger.info(
|
|
350
|
+
` Aliases ${dryRun ? 'to delete' : 'deleted'}: ${stats.aliasesDeleted}`
|
|
351
|
+
);
|
|
352
|
+
dm.logger.info(` Errors: ${stats.errors}`);
|
|
177
353
|
dm.logger.info('='.repeat(60));
|
|
178
354
|
} catch (err) {
|
|
179
355
|
dm.logger.error('Error during synchronization:', err);
|
|
@@ -184,7 +360,7 @@ async function syncJamesQuotas() {
|
|
|
184
360
|
}
|
|
185
361
|
|
|
186
362
|
// Run the sync
|
|
187
|
-
|
|
363
|
+
syncJames().catch(err => {
|
|
188
364
|
console.error('Fatal error:', err);
|
|
189
365
|
process.exit(1);
|
|
190
366
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ldap-rest",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.6",
|
|
4
4
|
"description": "Light directory manager",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Identity-Manager"
|
|
@@ -549,7 +549,7 @@
|
|
|
549
549
|
"require": "./static/schemas/twake/users.json"
|
|
550
550
|
}
|
|
551
551
|
},
|
|
552
|
-
"main": "dist/bin/index.
|
|
552
|
+
"main": "dist/bin/index.js",
|
|
553
553
|
"types": "./dist/src/bin/index.d.ts",
|
|
554
554
|
"bin": {
|
|
555
555
|
"cleanup-external-users": "bin/cleanup-external-users.mjs",
|