@reldens/cms 0.47.0 → 0.49.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.
@@ -0,0 +1,426 @@
1
+ # Password Management Guide
2
+
3
+ **Package**: @reldens/cms
4
+ **Feature**: User Password Management and Encryption
5
+ **Version**: 0.48.0+
6
+
7
+ ## Overview
8
+
9
+ The CMS provides comprehensive password management features to ensure secure handling of user passwords. This guide covers password encryption, updating passwords, and best practices for password security.
10
+
11
+ ## Password Encryption
12
+
13
+ ### How It Works
14
+
15
+ User passwords are encrypted using PBKDF2 (Password-Based Key Derivation Function 2) with:
16
+ - **Iterations**: 100,000
17
+ - **Key Length**: 64 bytes
18
+ - **Digest**: SHA-512
19
+ - **Salt Length**: 32 bytes (randomly generated per password)
20
+
21
+ The encrypted password is stored in the format: `salt:hash` where both salt and hash are hex-encoded.
22
+
23
+ ### Automatic Encryption
24
+
25
+ The CMS includes a `PasswordEncryptionHandler` that automatically encrypts passwords when saving user records through the admin panel.
26
+
27
+ **How It Works**:
28
+ 1. Listens to the `reldens.adminBeforeEntitySave` event
29
+ 2. Detects when the `users` entity is being saved
30
+ 3. Checks if the `password` field is being modified
31
+ 4. Verifies the password is not already encrypted
32
+ 5. Encrypts the password using `Encryptor.encryptPassword()`
33
+ 6. Updates the request body with the encrypted password
34
+
35
+ **Enabled by Default**: The password encryption handler is enabled by default in the Manager configuration.
36
+
37
+ **To Disable** (not recommended):
38
+ ```javascript
39
+ const manager = new Manager({
40
+ enablePasswordEncryption: false, // Disables automatic password encryption
41
+ // ... other config
42
+ });
43
+ ```
44
+
45
+ ### Manual Encryption
46
+
47
+ To manually encrypt a password in your code:
48
+
49
+ ```javascript
50
+ const { Encryptor } = require('@reldens/server-utils');
51
+
52
+ let plainPassword = 'mySecurePassword123';
53
+ let encryptedPassword = Encryptor.encryptPassword(plainPassword);
54
+ // Returns: '64char-salt:128char-hash'
55
+ ```
56
+
57
+ ### Password Validation
58
+
59
+ During authentication, passwords are validated using:
60
+
61
+ ```javascript
62
+ const { Encryptor } = require('@reldens/server-utils');
63
+
64
+ let plainPassword = 'userProvidedPassword';
65
+ let storedPassword = '64char-salt:128char-hash';
66
+ let isValid = Encryptor.validatePassword(plainPassword, storedPassword);
67
+ // Returns: true or false
68
+ ```
69
+
70
+ ## Updating User Passwords
71
+
72
+ ### Method 1: CLI Command (Recommended)
73
+
74
+ The CMS provides a dedicated CLI command for updating user passwords:
75
+
76
+ #### Basic Usage
77
+
78
+ ```bash
79
+ # Update by email (will prompt for password)
80
+ npx reldens-cms-update-password --email=admin@example.com
81
+
82
+ # Update by username (will prompt for password)
83
+ npx reldens-cms-update-password --username=admin
84
+
85
+ # Update with password in command (less secure, not recommended)
86
+ npx reldens-cms-update-password --email=admin@example.com --password=newPassword123
87
+ ```
88
+
89
+ #### CLI Options
90
+
91
+ - `--email=[email]` - User email address
92
+ - `--username=[username]` - User username
93
+ - `--password=[password]` - New password (prompted if not provided)
94
+ - `--help` or `-h` - Show help message
95
+
96
+ #### Requirements
97
+
98
+ - Either `--email` or `--username` must be provided
99
+ - CMS must be installed (install.lock file must exist)
100
+ - Entities must be generated for the configured driver
101
+
102
+ #### How It Works
103
+
104
+ The CLI tool is **driver-agnostic** and works with any storage driver:
105
+ 1. Reads `RELDENS_STORAGE_DRIVER` from .env (defaults to 'prisma')
106
+ 2. Uses `EntitiesLoader` to load entities for the detected driver
107
+ 3. If driver is 'prisma', automatically loads Prisma client from `./prisma/client`
108
+ 4. Initializes Manager and dataServer with the correct driver
109
+ 5. Updates password using the driver's entity repository
110
+
111
+ #### Security Notes
112
+
113
+ - **Interactive Mode** (no --password flag): Most secure, passwords are not stored in command history
114
+ - **Command-Line Mode** (with --password flag): Less secure, password appears in shell history
115
+
116
+ ### Method 2: Admin Panel
117
+
118
+ User passwords can be updated through the admin panel when editing a user record.
119
+
120
+ **Password Field Behavior**:
121
+ - **Field Type**: `type="password"` (hidden input, not plain text)
122
+ - **On Edit**: Password field is empty (does not show encrypted hash)
123
+ - **On Create**: Password field is empty and required
124
+ - **On Update**: Password field is optional - only updates if filled
125
+ - **Encryption**: Automatic via `PasswordEncryptionHandler` before save
126
+
127
+ **How It Works**:
128
+ 1. When editing a user, the password field is empty (line 605-607 in router-contents.js)
129
+ 2. Input type is set to 'password' for security (line 706-708 in router-contents.js)
130
+ 3. If password field is left empty, it's skipped during update (line 515-519 in router-contents.js)
131
+ 4. If password is entered, it's encrypted by `PasswordEncryptionHandler` before save
132
+ 5. Password is stored as encrypted `salt:hash` in database
133
+
134
+ **Configuration**:
135
+ - Password field names configurable via `passwordFieldNames` (default: `['password']`)
136
+ - Can be customized when creating RouterContents instance
137
+
138
+ **Important Notes**:
139
+ - Password encryption is enabled by default via `PasswordEncryptionHandler`
140
+ - If you disabled automatic encryption, passwords will be saved as plain text (not recommended)
141
+ - Always verify encryption is enabled in production environments
142
+
143
+ ### Method 3: Direct Database Update
144
+
145
+ For emergency situations or when CLI is not available:
146
+
147
+ 1. Generate encrypted password using Node.js:
148
+
149
+ ```javascript
150
+ const { Encryptor } = require('@reldens/server-utils');
151
+ let encryptedPassword = Encryptor.encryptPassword('yourNewPassword');
152
+ console.log(encryptedPassword);
153
+ ```
154
+
155
+ 2. Update database directly:
156
+
157
+ ```sql
158
+ UPDATE users
159
+ SET password = 'generated-salt:generated-hash'
160
+ WHERE email = 'admin@example.com';
161
+ ```
162
+
163
+ **⚠️ Warning**: Only use direct database updates as a last resort. Prefer CLI or admin panel methods.
164
+
165
+ ### Method 4: Programmatic Update
166
+
167
+ ```javascript
168
+ const { Manager } = require('@reldens/cms');
169
+ const { Encryptor } = require('@reldens/server-utils');
170
+
171
+ async function updateUserPassword(email, newPassword)
172
+ {
173
+ let manager = new Manager({projectRoot: process.cwd()});
174
+ await manager.initializeDataServer();
175
+ let usersEntity = manager.dataServer.getEntity('users');
176
+ let user = await usersEntity.loadOneBy('email', email);
177
+ if(!user){
178
+ console.error('User not found');
179
+ return false;
180
+ }
181
+ let encryptedPassword = Encryptor.encryptPassword(newPassword);
182
+ await usersEntity.updateById(user.id, {password: encryptedPassword});
183
+ console.log('Password updated successfully');
184
+ return true;
185
+ }
186
+ ```
187
+
188
+ ## Password Security Best Practices
189
+
190
+ ### For Developers
191
+
192
+ 1. **Never Store Plain Text Passwords**
193
+ - Always use `Encryptor.encryptPassword()` before saving
194
+ - Verify automatic encryption is enabled in production
195
+
196
+ 2. **Verify Password Encryption**
197
+ - Check that stored passwords contain `:` separator
198
+ - Verify salt length is 64 characters (hex)
199
+ - Verify hash length is 128 characters (hex)
200
+
201
+ 3. **Use CLI for Password Updates**
202
+ - Prefer interactive mode (no --password flag)
203
+ - Avoid logging passwords in application logs
204
+
205
+ 4. **Validate Password Format**
206
+ - Use `PasswordEncryptionHandler.isAlreadyEncrypted()` to check format
207
+ - Don't re-encrypt already encrypted passwords
208
+
209
+ 5. **Handle Password Changes**
210
+ - Clear user sessions after password changes
211
+ - Send email notifications for password changes
212
+ - Implement password history to prevent reuse
213
+
214
+ ### For Users
215
+
216
+ 1. **Strong Password Requirements**
217
+ - Minimum 12 characters
218
+ - Mix of uppercase, lowercase, numbers, and symbols
219
+ - Avoid common words and patterns
220
+ - Use password managers
221
+
222
+ 2. **Password Rotation**
223
+ - Change passwords regularly (every 90 days recommended)
224
+ - Change immediately if compromise is suspected
225
+ - Never share passwords
226
+
227
+ 3. **Account Security**
228
+ - Use unique passwords for each system
229
+ - Enable two-factor authentication when available
230
+ - Monitor login activity
231
+
232
+ ## Password Encryption Handler
233
+
234
+ ### Architecture
235
+
236
+ The `PasswordEncryptionHandler` class provides automatic password encryption for user entities.
237
+
238
+ **File**: `lib/password-encryption-handler.js`
239
+
240
+ **Key Methods**:
241
+ - `registerEventListeners()` - Registers event listeners for automatic encryption
242
+ - `handleBeforeEntitySave()` - Processes password encryption before entity save
243
+ - `isAlreadyEncrypted()` - Checks if password is already in encrypted format
244
+
245
+ ### Configuration
246
+
247
+ ```javascript
248
+ const { Manager } = require('@reldens/cms');
249
+
250
+ let manager = new Manager({
251
+ projectRoot: process.cwd(),
252
+ enablePasswordEncryption: true, // Default: true
253
+ // ... other config
254
+ });
255
+ ```
256
+
257
+ ### Customization
258
+
259
+ To customize the password encryption handler:
260
+
261
+ ```javascript
262
+ const { PasswordEncryptionHandler } = require('@reldens/cms');
263
+
264
+ let customHandler = new PasswordEncryptionHandler({
265
+ events: eventsManager,
266
+ entityKey: 'users', // Entity to monitor
267
+ passwordField: 'password', // Field name
268
+ enabled: true // Enable/disable
269
+ });
270
+
271
+ customHandler.registerEventListeners();
272
+ ```
273
+
274
+ ### Event Integration
275
+
276
+ The password handler hooks into the admin save process:
277
+
278
+ **Event**: `reldens.adminBeforeEntitySave`
279
+
280
+ **Event Data**:
281
+ - `driverResource` - Entity resource being saved
282
+ - `req` - Express request object
283
+ - `res` - Express response object
284
+ - `entityPath` - Entity path
285
+
286
+ **Processing Flow**:
287
+ 1. Event is emitted before entity save
288
+ 2. Handler checks if entity is 'users'
289
+ 3. Handler checks if password field is present
290
+ 4. Handler verifies password is not already encrypted
291
+ 5. Handler encrypts password and updates `req.body.password`
292
+ 6. Entity save continues with encrypted password
293
+
294
+ ## Troubleshooting
295
+
296
+ ### Common Issues
297
+
298
+ **Issue**: "Encryptor is not a constructor"
299
+ **Solution**: Use `Encryptor.encryptPassword()` directly, not `new Encryptor()`
300
+
301
+ **Issue**: "Invalid password during login"
302
+ **Solution**: Verify password is encrypted in database (should contain `:` and be 192 chars total)
303
+
304
+ **Issue**: "Password not encrypted when saving"
305
+ **Solution**: Verify `enablePasswordEncryption: true` in Manager config
306
+
307
+ **Issue**: "Password gets re-encrypted"
308
+ **Solution**: The handler checks for encryption format, but ensure you're not manually encrypting before save
309
+
310
+ ### Debugging
311
+
312
+ Enable debug logging to troubleshoot password issues:
313
+
314
+ ```bash
315
+ RELDENS_LOG_LEVEL=9 node .
316
+ ```
317
+
318
+ Look for these debug messages:
319
+ - "Password encrypted successfully for user entity save"
320
+ - "Password field appears to be already encrypted"
321
+ - "PasswordEncryptionHandler registered for event"
322
+
323
+ ### Verification
324
+
325
+ To verify a password is correctly encrypted:
326
+
327
+ ```javascript
328
+ const { Encryptor } = require('@reldens/server-utils');
329
+
330
+ let storedPassword = 'from-database';
331
+ let parts = storedPassword.split(':');
332
+
333
+ if(2 === parts.length && 64 === parts[0].length && 128 === parts[1].length){
334
+ console.log('Password is correctly encrypted');
335
+ } else {
336
+ console.log('Password format is invalid');
337
+ }
338
+ ```
339
+
340
+ ## Migration from Plain Text Passwords
341
+
342
+ If you have existing plain text passwords in your database:
343
+
344
+ 1. **Create Migration Script**:
345
+
346
+ ```javascript
347
+ const { Manager } = require('@reldens/cms');
348
+ const { Encryptor } = require('@reldens/server-utils');
349
+
350
+ async function migratePasswords()
351
+ {
352
+ let manager = new Manager({projectRoot: process.cwd()});
353
+ await manager.initializeDataServer();
354
+ let usersEntity = manager.dataServer.getEntity('users');
355
+ let allUsers = await usersEntity.loadAll();
356
+ for(let user of allUsers){
357
+ let parts = user.password.split(':');
358
+ if(2 === parts.length && 64 === parts[0].length){
359
+ console.log('User '+user.email+' already encrypted, skipping');
360
+ continue;
361
+ }
362
+ let encryptedPassword = Encryptor.encryptPassword(user.password);
363
+ await usersEntity.updateById(user.id, {password: encryptedPassword});
364
+ console.log('Migrated password for user: '+user.email);
365
+ }
366
+ console.log('Migration completed');
367
+ }
368
+
369
+ migratePasswords().catch(console.error);
370
+ ```
371
+
372
+ 2. **Run Migration**:
373
+ ```bash
374
+ node migrate-passwords.js
375
+ ```
376
+
377
+ 3. **Verify Migration**:
378
+ ```sql
379
+ SELECT id, email, LENGTH(password) as pwd_length,
380
+ CASE WHEN password LIKE '%:%' THEN 'Encrypted' ELSE 'Plain' END as status
381
+ FROM users;
382
+ ```
383
+
384
+ ## API Reference
385
+
386
+ ### Encryptor Methods
387
+
388
+ **`Encryptor.encryptPassword(password)`**
389
+ - **Parameters**: `password` (string) - Plain text password
390
+ - **Returns**: String in format `salt:hash` or `false` on error
391
+ - **Usage**: Encrypt a password before storing
392
+
393
+ **`Encryptor.validatePassword(password, storedPassword)`**
394
+ - **Parameters**:
395
+ - `password` (string) - Plain text password to verify
396
+ - `storedPassword` (string) - Encrypted password from database
397
+ - **Returns**: Boolean - `true` if password matches, `false` otherwise
398
+ - **Usage**: Validate password during authentication
399
+
400
+ ### PasswordEncryptionHandler Methods
401
+
402
+ **`new PasswordEncryptionHandler(props)`**
403
+ - **Parameters**: Object with `events`, `entityKey`, `passwordField`, `enabled`
404
+ - **Returns**: PasswordEncryptionHandler instance
405
+
406
+ **`registerEventListeners()`**
407
+ - **Parameters**: None
408
+ - **Returns**: Boolean - `true` if registered, `false` if disabled
409
+
410
+ **`isAlreadyEncrypted(value)`**
411
+ - **Parameters**: `value` (string) - Password value to check
412
+ - **Returns**: Boolean - `true` if encrypted format, `false` otherwise
413
+
414
+ ## Support
415
+
416
+ For issues or questions about password management:
417
+
418
+ - **GitHub Issues**: https://github.com/damian-pastorini/reldens-cms/issues
419
+ - **Discord**: https://discord.gg/HuJMxUY
420
+ - **Email**: info@dwdeveloper.com
421
+
422
+ ## Related Documentation
423
+
424
+ - Main CLAUDE.md - Package overview and architecture
425
+ - @reldens/server-utils CLAUDE.md - Encryptor class documentation
426
+ - Security Features section in main documentation
@@ -0,0 +1,66 @@
1
+ # Search Functionality Guide
2
+
3
+ ## Basic Search
4
+
5
+ ```bash
6
+ /search?search=technology
7
+ /search?search=javascript&limit=20
8
+ /search?search=news&renderPartial=newsListView&renderLayout=minimal
9
+ ```
10
+
11
+ ## Advanced Search with Template Data
12
+
13
+ Pass custom template variables through URL parameters:
14
+
15
+ ```bash
16
+ /search?search=articles&templateData[columnsClass]=col-md-4&templateData[showExcerpt]=true
17
+ /search?search=technology&templateData[columnsClass]=col-lg-6&templateData[cardClass]=shadow-sm&templateData[showAuthor]=false
18
+ ```
19
+
20
+ ## Search Template Variables
21
+
22
+ Templates receive dynamic data through URL parameters.
23
+
24
+ **URL Example:**
25
+ ```
26
+ /search?search=tech&templateData[columnsClass]=col-md-6&templateData[showDate]=true
27
+ ```
28
+
29
+ **Template (entriesListView.html):**
30
+ ```html
31
+ <div class="{{columnsClass}}">
32
+ <div class="card">
33
+ <h3>{{row.title}}</h3>
34
+ <p>{{row.content}}</p>
35
+ {{#showDate}}
36
+ <span class="date">{{row.created_at}}</span>
37
+ {{/showDate}}
38
+ </div>
39
+ </div>
40
+ ```
41
+
42
+ **Default Values:**
43
+
44
+ - `columnsClass` defaults to `col-lg-6` if not provided or empty
45
+ - Custom variables can be added via `templateData[variableName]=value`
46
+
47
+ ## Search Configuration
48
+
49
+ Custom search sets in Manager configuration:
50
+
51
+ ```javascript
52
+ const searchSets = {
53
+ articlesSearch: {
54
+ entities: [{
55
+ name: 'articles',
56
+ fields: ['title', 'content', 'summary'],
57
+ relations: 'authors'
58
+ }],
59
+ pagination: {active: true, limit: 15, sortBy: 'created_at', sortDirection: 'desc'}
60
+ }
61
+ };
62
+
63
+ const cms = new Manager({
64
+ searchSets: searchSets
65
+ });
66
+ ```