@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,349 @@
1
+ # Enhanced Templating System Guide
2
+
3
+ ## System Variables
4
+
5
+ Every template has access to system variables providing context about the current request:
6
+
7
+ ```html
8
+ {{currentRequest.baseUrl}}
9
+ {{currentRequest.protocol}}
10
+ {{currentRequest.host}}
11
+ {{currentRequest.path}}
12
+ {{currentRequest.method}}
13
+ {{currentRequest.userAgent}}
14
+ {{currentRequest.isSecure}}
15
+
16
+ {{currentRoute.id}}
17
+ {{currentRoute.path}}
18
+ {{currentRoute.title}}
19
+ {{currentRoute.template}}
20
+ {{currentRoute.layout}}
21
+
22
+ {{currentDomain.current}}
23
+ {{currentDomain.default}}
24
+ {{currentDomain.resolved}}
25
+
26
+ {{systemInfo.environment}}
27
+ {{systemInfo.nodeVersion}}
28
+ {{systemInfo.timestamp}}
29
+ ```
30
+
31
+ ## Template Functions
32
+
33
+ ### URL Transformers
34
+
35
+ The CMS provides three URL transformers with different behaviors for CDN support:
36
+
37
+ #### [url()] - Public URL (No CDN)
38
+
39
+ Generates URLs using the **public base URL** without CDN support.
40
+
41
+ ```html
42
+ [url(/articles)]
43
+ [url(/contact#form)]
44
+ [url(/css/styles.css)]
45
+ ```
46
+
47
+ **Output:**
48
+ - Without CDN: `https://example.com/css/styles.css`
49
+ - With CDN configured: `https://example.com/css/styles.css` (same, CDN ignored)
50
+
51
+ **Use for:** Internal application routes, API endpoints, form actions.
52
+
53
+ #### [cdn()] - CDN or Public URL
54
+
55
+ Generates URLs using the **CDN URL if configured**, otherwise falls back to public URL.
56
+
57
+ ```html
58
+ [cdn(/css/styles.css)]
59
+ [cdn(/js/scripts.js)]
60
+ [cdn(/assets/web/logo.png)]
61
+ ```
62
+
63
+ **Output:**
64
+ - Without CDN: `https://example.com/css/styles.css`
65
+ - With CDN configured: `https://cdn.example.com/css/styles.css`
66
+
67
+ **Use for:** Static assets outside `/assets` folder (CSS, JS, fonts, etc.).
68
+
69
+ #### [asset()] - CDN or Public URL + /assets Prefix
70
+
71
+ Generates URLs using the **CDN URL if configured** and **automatically prepends `/assets`** to the path.
72
+
73
+ ```html
74
+ [asset(/web/logo.png)]
75
+ [asset(/images/hero.jpg)]
76
+ ```
77
+
78
+ **Output:**
79
+ - Without CDN: `https://example.com/assets/web/logo.png`
80
+ - With CDN configured: `https://cdn.example.com/assets/web/logo.png`
81
+
82
+ **Use for:** Static assets inside `/assets` folder (images, downloads, media files).
83
+
84
+ ---
85
+
86
+ ### URL Transformer Best Practices
87
+
88
+ **Based on folder structure:**
89
+
90
+ ```
91
+ public/
92
+ assets/ # Use [asset(/file)] OR [cdn(/assets/file)]
93
+ web/
94
+ images/
95
+ downloads/
96
+ css/ # Use [cdn(/css/file)]
97
+ js/ # Use [cdn(/js/file)]
98
+ fonts/ # Use [cdn(/fonts/file)]
99
+ ```
100
+
101
+ **Examples:**
102
+
103
+ ```html
104
+ <!-- Files in public/assets/ -->
105
+ <meta property="og:image" content="[asset(/web/logo.png)]"/>
106
+ <img src="[asset(/images/hero.jpg)]" alt="Hero"/>
107
+
108
+ <!-- Files in public/css/, public/js/, etc. -->
109
+ <link rel="stylesheet" href="[cdn(/css/styles.css)]"/>
110
+ <script src="[cdn(/js/scripts.js)]"></script>
111
+
112
+ <!-- Internal routes -->
113
+ <form action="[url(/api/submit)]" method="post">
114
+ <a href="[url(/articles)]">Articles</a>
115
+ ```
116
+
117
+ **Key Rules:**
118
+ - `[asset(/file)]` adds `/assets` prefix automatically → Use for files in `/assets` folder
119
+ - `[cdn(/assets/file)]` requires full path → Alternative for files in `/assets` folder
120
+ - `[cdn(/css/file)]` requires full path → Use for files outside `/assets` (css, js, fonts)
121
+ - `[url(/path)]` never uses CDN → Use for application routes only
122
+
123
+ **Recommendation:** Use `[asset()]` for `/assets` files (cleaner syntax), `[cdn()]` for everything else.
124
+
125
+ ### Date Formatting
126
+
127
+ ```html
128
+ [date()]
129
+ [date(now, Y-m-d)]
130
+ [date(2024-01-01, d/m/Y)]
131
+ ```
132
+
133
+ ### Internationalization
134
+
135
+ ```html
136
+ [translate(welcome.message)]
137
+ [t(hello.world, Hello World!)]
138
+ [t(greeting, Hi {name}!, {name: John})]
139
+ ```
140
+
141
+ ## Enhanced Context Passing
142
+
143
+ Child content blocks and partials receive context from parent pages:
144
+
145
+ ```html
146
+ <entity name="cmsBlocks" field="name" value="article-sidebar"/>
147
+
148
+ {{currentEntity.title}}
149
+ {{currentEntity.id}}
150
+ {{currentEntity.template}}
151
+ ```
152
+
153
+ ## Entity Rendering
154
+
155
+ ### Single Entity
156
+
157
+ ```html
158
+ <entity name="cmsBlocks" field="name" value="header-main"/>
159
+ <entity name="cmsBlocks" field="name" value="sidebar-left"/>
160
+ <entity name="articles" id="123"/>
161
+ <entity name="cmsPages" id="1"/>
162
+ ```
163
+
164
+ ### Single Field Collections
165
+
166
+ Extract and concatenate a single field from multiple records:
167
+
168
+ ```html
169
+ <collection name="cmsBlocks" filters="{status: 'active', category: 'navigation'}" field="content"/>
170
+ <collection name="articles" filters="{featured: true}" field="title"/>
171
+ <collection name="articles" filters="{featured: true}" field="title" data="{limit: 5, sortBy: 'created_at', sortDirection: 'desc'}"/>
172
+ ```
173
+
174
+ ### Loop Collections
175
+
176
+ ```html
177
+ <collection name="cmsBlocks" filters="{status: 'active'}">
178
+ <div class="block">
179
+ <h3>{{row.title}}</h3>
180
+ <div class="content">{{row.content}}</div>
181
+ </div>
182
+ </collection>
183
+
184
+ <collection name="articles" filters="{category: 'technology'}">
185
+ <div class="article">
186
+ <h4>{{row.title}}</h4>
187
+ <p>{{row.summary}}</p>
188
+ <img src="{{row.featured_image}}" alt="{{row.title}}">
189
+ </div>
190
+ </collection>
191
+ ```
192
+
193
+ ### Paginated Collections
194
+
195
+ ```html
196
+ <collection name="articles"
197
+ filters="{featured: true}"
198
+ data="{limit: 10, sortBy: 'created_at', sortDirection: 'desc'}"
199
+ pagination="articles-1"
200
+ container="pagedCollection"
201
+ prevPages="2"
202
+ nextPages="2">
203
+ <div class="article-card">
204
+ <h4>{{row.title}}</h4>
205
+ <p>{{row.summary}}</p>
206
+ <span class="date">{{row.created_at}}</span>
207
+ </div>
208
+ </collection>
209
+ ```
210
+
211
+ **Pagination Attributes:**
212
+
213
+ - `pagination="collection-id"` - Enables pagination with unique identifier
214
+ - `container="templateName"` - Custom pagination template (defaults to "pagedCollection")
215
+ - `prevPages="2"` - Number of previous page links to show (default: 2)
216
+ - `nextPages="2"` - Number of next page links to show (default: 2)
217
+
218
+ **Pagination URL Parameters:**
219
+
220
+ ```
221
+ /articles?articles-1-key={"page":2,"limit":10,"sortBy":"created_at","sortDirection":"desc"}
222
+ ```
223
+
224
+ **Available Pagination Template Variables:**
225
+
226
+ - `{{&collectionContentForCurrentPage}}` - Rendered collection items for current page
227
+ - `{{currentPage}}` - Current page number
228
+ - `{{totalPages}}` - Total number of pages
229
+ - `{{totalRecords}}` - Total number of records
230
+ - `{{prevPageUrl}}` / `{{nextPageUrl}}` - Previous/next page URLs
231
+ - `{{&prevPageLabel}}` / `{{&nextPageLabel}}` - Previous/next link labels
232
+ - `{{#prevPages}}` / `{{#nextPages}}` - Arrays of page objects with pageUrl and pageLabel
233
+ - `{{hasNextPage}}` / `{{hasPrevPage}}` - Boolean flags for navigation availability
234
+
235
+ ## Custom Partials
236
+
237
+ ### HTML-style Syntax
238
+
239
+ ```html
240
+ <partial name="hero"
241
+ sectionStyle=" bg-black"
242
+ bigTextHtml="A free open-source platform!"
243
+ mediumTextHtml="Build with Node.js, MySQL"
244
+ imageUrl="/assets/web/logo.png"
245
+ imageAlt="Logo" />
246
+
247
+ <partial name="productCard"
248
+ title="Premium Package"
249
+ price="$99"
250
+ highlighted="true">
251
+ </partial>
252
+ ```
253
+
254
+ ### Mustache Syntax
255
+
256
+ ```html
257
+ {{>hero -{
258
+ bigTextHtml: "Welcome!",
259
+ mediumTextHtml: "Build with Node.js",
260
+ imageUrl: "https://example.com/hero.jpg",
261
+ ctaText: "Get Started",
262
+ ctaLink: "/documentation"
263
+ }-}}
264
+
265
+ <collection name="cmsPages" filters="{featured: true}" data="{limit: 3}">
266
+ {{>cardView -{row}-}}
267
+ </collection>
268
+
269
+ {{>productCard -{
270
+ title: "Premium Package",
271
+ price: "$99",
272
+ features: ["Advanced Analytics", "Priority Support"],
273
+ highlighted: true
274
+ }-}}
275
+ ```
276
+
277
+ ## Collection Query Options
278
+
279
+ Collections support advanced query parameters:
280
+
281
+ - **limit** - Maximum number of records to return
282
+ - **offset** - Number of records to skip (for pagination)
283
+ - **sortBy** - Field name to sort by
284
+ - **sortDirection** - Sort direction ('asc' or 'desc')
285
+
286
+ ### Examples
287
+
288
+ ```html
289
+ <collection name="articles" filters="{}" field="title" data="{limit: 5, sortBy: 'title'}"/>
290
+
291
+ <collection name="articles" filters="{published: true}" data="{limit: 10, offset: 20, sortBy: 'created_at', sortDirection: 'desc'}">
292
+ <article>{{row.title}}</article>
293
+ </collection>
294
+
295
+ <collection name="articles" filters="{featured: true}" data="{limit: 3, sortBy: 'created_at', sortDirection: 'desc'}">
296
+ <div class="featured-article">{{row.title}}</div>
297
+ </collection>
298
+ ```
299
+
300
+ ## Layout System
301
+
302
+ ### page.html - Full HTML wrapper
303
+
304
+ ```html
305
+ <!DOCTYPE html>
306
+ <html lang="{{locale}}">
307
+ <head>
308
+ <title>{{title}}</title>
309
+ <meta name="description" content="{{description}}"/>
310
+ <link href="[url(/css/styles.css)]" rel="stylesheet"/>
311
+ </head>
312
+ <body class="{{siteHandle}}">
313
+ {{&content}}
314
+ <script src="[url(/js/scripts.js)]"></script>
315
+ </body>
316
+ </html>
317
+ ```
318
+
319
+ ### layouts/default.html - Body content only
320
+
321
+ ```html
322
+ <entity name="cmsBlocks" field="name" value="header-main"/>
323
+
324
+ <main id="main" class="main-container">
325
+ <div class="container">
326
+ <div class="row">
327
+ <div class="col-md-3">
328
+ <entity name="cmsBlocks" field="name" value="sidebar-left"/>
329
+ </div>
330
+ <div class="col-md-9">
331
+ {{&content}}
332
+ </div>
333
+ </div>
334
+ </div>
335
+ </main>
336
+
337
+ <entity name="cmsBlocks" field="name" value="footer-main"/>
338
+ ```
339
+
340
+ ## Content Blocks
341
+
342
+ Create reusable content blocks in the cms_blocks table via admin panel:
343
+
344
+ ```sql
345
+ INSERT INTO cms_blocks (name, title, content) VALUES
346
+ ('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
347
+ ('article-sidebar', 'Article Categories',
348
+ '<div class="categories"><h3>Categories</h3><ul><li><a href="[url(/articles/technology)]">Technology</a></li></ul></div>');
349
+ ```
package/CLAUDE.md CHANGED
@@ -14,6 +14,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
14
14
  - Template reloading for development
15
15
  - Caching system for performance
16
16
  - Event-driven architecture for extensibility
17
+ - Password encryption and management - Automatic password encryption with PBKDF2
17
18
 
18
19
  ## Key Commands
19
20
 
@@ -24,10 +25,61 @@ npx reldens-cms
24
25
  # Generate entities from database
25
26
  npx reldens-cms-generate-entities
26
27
 
28
+ # Update user password via CLI
29
+ npx reldens-cms-update-password --email=admin@example.com
30
+
27
31
  # Or via npm scripts
28
32
  npm run generate-entities
29
33
  ```
30
34
 
35
+ ## Password Management
36
+
37
+ ### CLI Password Update Command
38
+
39
+ The CMS provides a dedicated CLI command for securely updating user passwords:
40
+
41
+ ```bash
42
+ # Interactive mode (recommended - will prompt for password)
43
+ npx reldens-cms-update-password --email=admin@example.com
44
+
45
+ # By username
46
+ npx reldens-cms-update-password --username=admin
47
+
48
+ # With password in command (less secure)
49
+ npx reldens-cms-update-password --email=admin@example.com --password=newPassword123
50
+ ```
51
+
52
+ **Options**:
53
+ - `--email=[email]` - User email address
54
+ - `--username=[username]` - User username
55
+ - `--password=[password]` - New password (prompted if not provided)
56
+ - `--help` or `-h` - Show this help message
57
+
58
+ **Notes**:
59
+ - Works with any storage driver (prisma, objection-js, mikro-orm)
60
+ - Uses `RELDENS_STORAGE_DRIVER` environment variable to detect the driver
61
+ - Automatically loads entities and Prisma client (if using Prisma)
62
+
63
+ ### Automatic Password Encryption
64
+
65
+ The CMS automatically encrypts passwords when saving user records through the admin panel using the `PasswordEncryptionHandler`:
66
+
67
+ - Listens to `reldens.adminBeforeEntitySave` event
68
+ - Detects password field changes in users entity
69
+ - Encrypts passwords using PBKDF2 (100k iterations, SHA-512)
70
+ - Stores passwords in `salt:hash` format
71
+ - **Enabled by default** - can be disabled with `enablePasswordEncryption: false` in Manager config
72
+
73
+ **Password Encryption Algorithm**:
74
+ - **Method**: PBKDF2 (Password-Based Key Derivation Function 2)
75
+ - **Iterations**: 100,000
76
+ - **Key Length**: 64 bytes
77
+ - **Digest**: SHA-512
78
+ - **Salt Length**: 32 bytes (randomly generated)
79
+ - **Storage Format**: `salt:hash` (192 characters total)
80
+
81
+ See `.claude/password-management-guide.md` for comprehensive password management documentation.
82
+
31
83
  ## Architecture Overview
32
84
 
33
85
  The CMS follows a modular architecture with specialized classes following SOLID principles:
@@ -39,6 +91,7 @@ The CMS follows a modular architecture with specialized classes following SOLID
39
91
  - Handles configuration and environment variables
40
92
  - Manages multi-domain setup and security
41
93
  - Coordinates service lifecycle
94
+ - Initializes password encryption handler
42
95
 
43
96
  **lib/frontend.js** - Frontend orchestrator
44
97
  - Coordinates all frontend operations
@@ -52,6 +105,18 @@ The CMS follows a modular architecture with specialized classes following SOLID
52
105
  - Processes file uploads
53
106
  - Builds admin UI from entity configurations
54
107
 
108
+ **lib/password-encryption-handler.js** - Password encryption handler
109
+ - Automatic password encryption for users entity
110
+ - Event-driven architecture
111
+ - PBKDF2 encryption with 100k iterations
112
+ - Detects and skips already-encrypted passwords
113
+ - Configurable entity and field names
114
+
115
+ **lib/admin-manager/router-contents.js** - Admin routing and form handling
116
+ - Password field handling (type="password", empty on edit)
117
+ - Password updates optional (skip if empty when editing)
118
+ - Configurable password field names via `passwordFieldNames`
119
+
55
120
  ### Installation & Setup
56
121
 
57
122
  **lib/installer.js** - Installation orchestrator
@@ -252,7 +317,7 @@ The CMS uses these core tables:
252
317
  - **cms_pages_meta** - Page-specific metadata
253
318
 
254
319
  ### User Management (Optional)
255
- - **users** - User authentication
320
+ - **users** - User authentication (with encrypted passwords)
256
321
  - **roles** - Role definitions
257
322
 
258
323
  ## Template System
@@ -342,13 +407,21 @@ templates/
342
407
 
343
408
  **Template Functions:**
344
409
  ```html
345
- [url(/articles)] <!-- URL generation -->
346
- [asset(/img/logo.png)] <!-- Asset URLs -->
410
+ [url(/articles)] <!-- URL generation (no CDN) -->
411
+ [cdn(/css/styles.css)] <!-- CDN URL (or public URL if no CDN) -->
412
+ [asset(/web/logo.png)] <!-- Asset URL with /assets prefix (uses CDN if available) -->
347
413
  [date(now, Y-m-d)] <!-- Date formatting -->
348
414
  [translate(welcome.message)] <!-- i18n -->
349
415
  [t(key, Default, {var: value})] <!-- i18n with interpolation -->
350
416
  ```
351
417
 
418
+ **URL Transformer Selection:**
419
+ - `[url()]` - Application routes (never uses CDN)
420
+ - `[cdn()]` - Static assets outside `/assets` (CSS, JS, fonts)
421
+ - `[asset()]` - Static assets inside `/assets` folder (images, media)
422
+
423
+ See `.claude/templating-system-guide.md` for detailed URL transformer documentation.
424
+
352
425
  ## Configuration
353
426
 
354
427
  ### Environment Variables (.env)
@@ -399,6 +472,9 @@ const cms = new Manager({
399
472
  },
400
473
  adminRoleId: 99,
401
474
 
475
+ // Password encryption (enabled by default)
476
+ enablePasswordEncryption: true, // Set to false to disable
477
+
402
478
  // Performance
403
479
  cache: true,
404
480
  reloadTime: -1, // Development: reload on every request
@@ -444,6 +520,7 @@ The CMS provides extensive event hooks for customization:
444
520
  - `reldens.setupAdminRouter` - Setup admin routes
445
521
  - `reldens.setupAdminRoutes` - After route setup
446
522
  - `reldens.setupAdminManagers` - After manager setup
523
+ - `reldens.adminBeforeEntitySave` - Before entity save (used by password encryption handler)
447
524
 
448
525
  ### Template Reloading Events
449
526
  - `reldens.templateReloader.templatesChanged` - Templates changed
@@ -495,6 +572,8 @@ Define custom entity configurations in `entitiesConfig`:
495
572
  ## Security Features
496
573
 
497
574
  - **Authentication** - Role-based admin access
575
+ - **Password Encryption** - PBKDF2 with 100k iterations, SHA-512
576
+ - **Automatic Password Encryption** - Event-driven encryption on save (enabled by default)
498
577
  - **CSRF Protection** - Via session tokens
499
578
  - **File Upload Validation** - MIME type and extension checking
500
579
  - **Entity Access Control** - Public/private entity rules
@@ -518,6 +597,8 @@ Define custom entity configurations in `entitiesConfig`:
518
597
  8. **Event hooks are async** - use await when emitting events
519
598
  9. **Template reloading is development-only** - disable in production
520
599
  10. **Multi-domain requires proper configuration** - set up domain mapping correctly
600
+ 11. **Password encryption is automatic** - enabled by default for users entity
601
+ 12. **Never store plain text passwords** - always use Encryptor.encryptPassword()
521
602
 
522
603
  ## Common File Paths
523
604
 
@@ -528,6 +609,8 @@ Define custom entity configurations in `entitiesConfig`:
528
609
  - **Public assets:** `public/`
529
610
  - **Environment:** `.env`
530
611
  - **Install lock:** `install.lock`
612
+ - **Password CLI:** `bin/reldens-cms-update-password.js`
613
+ - **Password handler:** `lib/password-encryption-handler.js`
531
614
 
532
615
  ## Troubleshooting
533
616
 
@@ -551,6 +634,12 @@ Define custom entity configurations in `entitiesConfig`:
551
634
  - Check entity access configuration
552
635
  - Verify relationship mappings
553
636
 
637
+ ### Password Issues
638
+ - Verify password is encrypted (contains `:` and is 192 chars)
639
+ - Check `enablePasswordEncryption` is `true` in Manager config
640
+ - Use CLI command for password updates: `npx reldens-cms-update-password`
641
+ - For debugging, set `RELDENS_LOG_LEVEL=9` to see password encryption logs
642
+
554
643
  ## Dependencies
555
644
 
556
645
  - **@reldens/storage** - Database abstraction layer