webspresso 0.0.97 → 0.0.99
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/.agents/ADMIN.md +163 -5
- package/.agents/AGENTS.md +1 -1
- package/.agents/ARCHITECTURE.md +10 -0
- package/.agents/AUTH.md +169 -0
- package/.agents/CLI.md +195 -1
- package/.agents/ORM.md +88 -4
- package/.agents/PLUGINS.md +221 -9
- package/.agents/QUEUE.md +127 -0
- package/.agents/ROUTING.md +127 -7
- package/AGENTS.md +1 -0
- package/bin/commands/add-esbuild.js +80 -0
- package/bin/commands/add-tailwind.js +76 -31
- package/bin/commands/analytics-prune.js +57 -0
- package/bin/commands/api.js +207 -15
- package/bin/commands/build.js +99 -0
- package/bin/commands/dev.js +64 -35
- package/bin/commands/doctor.js +26 -0
- package/bin/commands/email-prune.js +47 -0
- package/bin/commands/make-job.js +86 -0
- package/bin/commands/make-model.js +250 -0
- package/bin/commands/new.js +45 -28
- package/bin/commands/openapi.js +102 -0
- package/bin/commands/page.js +6 -3
- package/bin/commands/queue-prune.js +59 -0
- package/bin/commands/routes.js +52 -0
- package/bin/commands/service.js +6 -3
- package/bin/commands/typegen.js +106 -0
- package/bin/commands/upload-prune.js +79 -0
- package/bin/webspresso.js +22 -0
- package/core/applySchema.js +13 -6
- package/core/auth/hash.js +21 -12
- package/core/auth/index.js +60 -0
- package/core/auth/manager.js +134 -0
- package/core/auth/middleware.js +88 -0
- package/core/auth/oauth.js +517 -0
- package/core/auth/qrcode.js +499 -0
- package/core/auth/totp.js +504 -0
- package/core/compression/middleware.js +23 -17
- package/core/openapi/build-from-api-routes.js +160 -29
- package/core/orm/cache/fingerprint.js +15 -1
- package/core/orm/cache/memory-provider.js +27 -2
- package/core/orm/eager-loader.js +144 -10
- package/core/orm/index.js +20 -7
- package/core/orm/model.js +53 -7
- package/core/orm/query-builder.js +90 -4
- package/core/orm/repository.js +44 -3
- package/core/orm/search.js +213 -0
- package/core/queue/adapters/database.js +94 -0
- package/core/queue/adapters/memory.js +62 -0
- package/core/queue/adapters/redis.js +80 -0
- package/core/queue/cron.js +331 -0
- package/core/queue/index.js +8 -1
- package/core/queue/manager.js +205 -1
- package/core/queue/scheduler.js +359 -0
- package/core/sse/index.js +19 -0
- package/core/sse/stream.js +430 -0
- package/core/ssr/stream.js +11 -0
- package/index.d.ts +1188 -10
- package/index.js +88 -96
- package/package.json +1 -1
- package/plugins/admin-panel/api.js +279 -17
- package/plugins/admin-panel/app.js +114 -22
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
- package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +333 -36
- package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
- package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
- package/plugins/admin-panel/client/parts/08-record-list.js +248 -23
- package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
- package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
- package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
- package/plugins/admin-panel/core/admin-module.js +13 -7
- package/plugins/admin-panel/core/api-extensions.js +30 -2
- package/plugins/admin-panel/core/registry.js +96 -16
- package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
- package/plugins/admin-panel/field-renderers/index.js +2 -0
- package/plugins/admin-panel/index.js +96 -0
- package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
- package/plugins/admin-panel/modules/admin-users.js +16 -1
- package/plugins/admin-panel/modules/bulk-actions.js +13 -5
- package/plugins/admin-panel/modules/custom-pages.js +492 -3
- package/plugins/admin-panel/modules/dashboard.js +283 -0
- package/plugins/admin-panel/modules/menu.js +56 -18
- package/plugins/admin-panel/modules/user-management.js +24 -6
- package/plugins/api-key/crypto.js +101 -0
- package/plugins/api-key/index.js +288 -0
- package/plugins/api-key/middleware.js +461 -0
- package/plugins/api-key/migration-template.js +48 -0
- package/plugins/api-key/model.js +50 -0
- package/plugins/api-key/scopes.js +125 -0
- package/plugins/audit-log/admin-component.js +487 -59
- package/plugins/audit-log/api-handlers.js +148 -0
- package/plugins/audit-log/index.js +153 -3
- package/plugins/audit-log/purge.js +60 -10
- package/plugins/dashboard/app.js +152 -27
- package/plugins/dashboard/index.js +143 -39
- package/plugins/dashboard/styles.js +78 -0
- package/plugins/data-exchange/export-xlsx.js +9 -2
- package/plugins/data-exchange/import.js +9 -2
- package/plugins/data-exchange/index.js +6 -3
- package/plugins/email/index.js +132 -2
- package/plugins/email/purge.js +60 -10
- package/plugins/file-manager/admin-component.js +20 -4
- package/plugins/file-manager/index.js +21 -4
- package/plugins/index.js +10 -0
- package/plugins/oauth/index.js +338 -0
- package/plugins/orm-cache-admin/index.js +4 -2
- package/plugins/queue/index.js +15 -0
- package/plugins/schema-explorer/admin-component.js +714 -0
- package/plugins/schema-explorer/relations-graph.js +205 -0
- package/plugins/schema-explorer.js +248 -69
- package/plugins/site-analytics/admin-component.js +23 -0
- package/plugins/site-analytics/api-handlers.js +42 -0
- package/plugins/site-analytics/index.js +147 -2
- package/plugins/site-analytics/purge.js +168 -0
- package/plugins/swagger.js +261 -13
- package/plugins/upload/index.js +26 -0
- package/plugins/upload/orphan-pruner.js +292 -0
- package/src/api/api-loader.js +4 -1
- package/src/bundle/index.js +517 -0
- package/src/bundle/manifest.js +165 -0
- package/src/dev/banner.js +296 -0
- package/src/dev/colors.js +70 -0
- package/src/dev/index.js +62 -0
- package/src/dev/profiler.js +354 -0
- package/src/dev/typegen.js +497 -0
- package/src/dev-reload/index.js +611 -0
- package/src/discovery/file-route-parser.js +44 -1
- package/src/discovery/scan-routes.js +2 -2
- package/src/file-router.js +347 -162
- package/src/helpers.js +967 -98
- package/src/njk-frontmatter.js +12 -0
- package/src/pages/page-loader.js +61 -11
- package/src/routing/cascading.js +378 -0
- package/src/routing/middleware-resolver.js +92 -0
- package/src/server.js +635 -63
package/.agents/ADMIN.md
CHANGED
|
@@ -133,7 +133,59 @@ registry.registerPageDir(path.join(__dirname, 'admin-pages'));
|
|
|
133
133
|
|
|
134
134
|
---
|
|
135
135
|
|
|
136
|
-
## 5.
|
|
136
|
+
## 5. Page & Plugin Title Management
|
|
137
|
+
|
|
138
|
+
Admin Panel supports configurable base titles, title templates, and dynamic SPA page titles:
|
|
139
|
+
|
|
140
|
+
### Plugin Configuration
|
|
141
|
+
```js
|
|
142
|
+
adminPanelPlugin({
|
|
143
|
+
db,
|
|
144
|
+
title: 'Acme Admin', // Base title in <title> and header (default: 'Admin Panel')
|
|
145
|
+
titleTemplate: '%s | %b', // Format template: %s = page title, %b = base title (default: '%s | %b')
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Module & Page Title Configuration
|
|
150
|
+
When registering modules and custom pages, titles are automatically propagated to navigation and browser document title:
|
|
151
|
+
```js
|
|
152
|
+
adminApi.registerModule({
|
|
153
|
+
id: 'reports',
|
|
154
|
+
title: 'Reports & Analytics', // Module fallback title
|
|
155
|
+
pages: [
|
|
156
|
+
{
|
|
157
|
+
id: 'monthly-report',
|
|
158
|
+
title: 'Monthly Report', // Document title -> "Monthly Report | Acme Admin"
|
|
159
|
+
path: '/reports/monthly',
|
|
160
|
+
component: ...,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'live-dashboard',
|
|
164
|
+
documentTitle: 'Live Monitor (Fullscreen)', // Raw document.title override
|
|
165
|
+
path: '/reports/live',
|
|
166
|
+
layout: false,
|
|
167
|
+
component: ...,
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Dynamic Client-Side Title Helper (`setAdminPageTitle`)
|
|
174
|
+
From any Mithril component or client script:
|
|
175
|
+
```js
|
|
176
|
+
// Dynamic title formatted with template (e.g. "Sales Overview | Acme Admin")
|
|
177
|
+
window.setAdminPageTitle('Sales Overview');
|
|
178
|
+
|
|
179
|
+
// Custom template override
|
|
180
|
+
window.setAdminPageTitle('Sales Overview', { template: '%s — %b' });
|
|
181
|
+
|
|
182
|
+
// Raw document title without template
|
|
183
|
+
window.setAdminPageTitle('Sales Overview', { raw: true });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 6. Custom Field Renderers (`registerFieldRenderer`)
|
|
137
189
|
|
|
138
190
|
Register custom display and edit renderers for specific column types:
|
|
139
191
|
|
|
@@ -152,7 +204,7 @@ registry.registerFieldRenderer('color-picker', {
|
|
|
152
204
|
|
|
153
205
|
---
|
|
154
206
|
|
|
155
|
-
##
|
|
207
|
+
## 7. Dashboard Widgets (`registerWidget`)
|
|
156
208
|
|
|
157
209
|
Register dashboard widgets for the Admin overview screen:
|
|
158
210
|
|
|
@@ -164,9 +216,20 @@ registry.registerWidget('recent-orders', {
|
|
|
164
216
|
});
|
|
165
217
|
```
|
|
166
218
|
|
|
219
|
+
### Built-in Dashboard Widgets
|
|
220
|
+
- **`model-stats`**: Model overview cards displaying record counts, column counts, and last update timestamps.
|
|
221
|
+
- **`database-storage`**: Database Storage & Health monitor showing:
|
|
222
|
+
- Database engine (SQLite, PostgreSQL, MySQL) & active file sizes
|
|
223
|
+
- Total tables and total record counts across the schema
|
|
224
|
+
- Storage health status badge with high-volume alerts for unpruned logs
|
|
225
|
+
- Top tables by row count with visual progress percentage bars
|
|
226
|
+
- Direct navigation links to model CRUD screens
|
|
227
|
+
- **`quick-actions`**: Fast shortcuts to create new records across enabled models.
|
|
228
|
+
- **`recent-activity`**: Timeline of recent administrative mutations (creates, updates, deletes) powered by `audit_logs` / `admin_activity_logs` with direct links to record changes.
|
|
229
|
+
|
|
167
230
|
---
|
|
168
231
|
|
|
169
|
-
##
|
|
232
|
+
## 8. Single Record & Bulk Table Actions
|
|
170
233
|
|
|
171
234
|
Add custom action buttons to model table views:
|
|
172
235
|
|
|
@@ -182,6 +245,16 @@ registry.registerAction('Product', {
|
|
|
182
245
|
},
|
|
183
246
|
});
|
|
184
247
|
|
|
248
|
+
// Record action with navigation redirect (e.g. audit-timeline)
|
|
249
|
+
registry.registerAction('Product', {
|
|
250
|
+
id: 'view-history',
|
|
251
|
+
label: 'History',
|
|
252
|
+
icon: 'clock',
|
|
253
|
+
handler: async (record, model) => {
|
|
254
|
+
return { redirect: `/_admin/audit-log?model=${model.name}&recordId=${record.id}` };
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
185
258
|
// Bulk multi-select table action
|
|
186
259
|
registry.registerBulkAction('Product', {
|
|
187
260
|
id: 'bulk-archive',
|
|
@@ -196,7 +269,7 @@ registry.registerBulkAction('Product', {
|
|
|
196
269
|
|
|
197
270
|
---
|
|
198
271
|
|
|
199
|
-
##
|
|
272
|
+
## 9. Client Components & Custom Client Script Injections
|
|
200
273
|
|
|
201
274
|
Inject custom client-side JavaScript / Mithril components into the Admin SPA runtime:
|
|
202
275
|
|
|
@@ -213,7 +286,7 @@ registry.registerClientComponent('custom-banner', `
|
|
|
213
286
|
|
|
214
287
|
---
|
|
215
288
|
|
|
216
|
-
##
|
|
289
|
+
## 10. Offline & Zero-CDN Architecture
|
|
217
290
|
|
|
218
291
|
The Admin Panel is 100% self-contained and operates completely offline without external CDN dependencies (`unpkg.com`, `cdn.jsdelivr.net`, or `cdn.tailwindcss.com`):
|
|
219
292
|
- **Vendored Static Assets (`plugins/admin-panel/vendor/`)**:
|
|
@@ -223,3 +296,88 @@ The Admin Panel is 100% self-contained and operates completely offline without e
|
|
|
223
296
|
- **Local Asset Serving**: Served locally at `GET /_admin/vendor/:file` with proper MIME types, path traversal protections, and `Cache-Control: public, max-age=86400` caching.
|
|
224
297
|
- **Hardened CSP**: Works in air-gapped / intranet environments with strict `'self'` Content Security Policy without leaking third-party network requests.
|
|
225
298
|
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 11. Toast Notifications, Modal Confirmations & Session Expiry UX
|
|
302
|
+
|
|
303
|
+
The Admin SPA includes a built-in reactive notification and modal system that replaces intrusive browser `alert()` and `confirm()` dialogs.
|
|
304
|
+
|
|
305
|
+
### Toast Notifications (`window.notify`)
|
|
306
|
+
Display auto-dismissing, animated toasts in the top-right corner with pause-on-hover:
|
|
307
|
+
|
|
308
|
+
```js
|
|
309
|
+
// Available on window.notify or as global helpers: notifySuccess, notifyError, notifyWarning, notifyInfo
|
|
310
|
+
notify.success('Record created successfully');
|
|
311
|
+
notify.error('Failed to save record: Validation error');
|
|
312
|
+
notify.warning('Please review unsaved changes');
|
|
313
|
+
notify.info('Export started in the background');
|
|
314
|
+
|
|
315
|
+
// Custom duration (TTL in milliseconds, default 4000ms):
|
|
316
|
+
notify.success('Saved!', 2000);
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Confirmation Modal (`window.confirmModal` / `confirmAction`)
|
|
320
|
+
Display an accessible, styled modal dialog returning a Promise resolving to `true` or `false`:
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
// Destructive confirmation (danger: true highlights the confirm button in red)
|
|
324
|
+
const confirmed = await confirmAction({
|
|
325
|
+
title: 'Delete User',
|
|
326
|
+
message: 'Are you sure you want to permanently delete this user?',
|
|
327
|
+
confirmText: 'Delete',
|
|
328
|
+
cancelText: 'Cancel',
|
|
329
|
+
danger: true,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (confirmed) {
|
|
333
|
+
await api.delete(`/users/${id}`);
|
|
334
|
+
notify.success('User deleted');
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Session Expiration & Intended Return-To Redirection
|
|
339
|
+
When an authenticated staff user's session expires or an API call returns `401 Unauthorized`:
|
|
340
|
+
1. `redirectToLogin('expired')` captures the active route and redirects to `/_admin/#!/login?returnTo=<route>&reason=expired`.
|
|
341
|
+
2. The login screen displays an informational notice banner: *"Your session has expired. Please sign in again."*
|
|
342
|
+
3. After authenticating successfully, the user is automatically redirected to their intended route.
|
|
343
|
+
4. **Open-Redirect Hardening**: All return destinations are strictly validated by `isSafeIntendedRoute(path)` to disallow protocol-relative URLs (`//evil.com`), foreign protocols (`http:`, `javascript:`), or backslashes.
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 12. Admin Login Brute-Force Protection & Rate Limiting
|
|
348
|
+
|
|
349
|
+
The Admin Panel includes built-in in-memory rate limiting to defend staff login endpoints against credential stuffing and brute-force password guessing.
|
|
350
|
+
|
|
351
|
+
### Configuration (`adminPanelPlugin`)
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
import { createApp, adminPanelPlugin } from 'webspresso';
|
|
355
|
+
|
|
356
|
+
const app = createApp({
|
|
357
|
+
plugins: [
|
|
358
|
+
adminPanelPlugin({
|
|
359
|
+
db,
|
|
360
|
+
// Configure or disable login rate limiting:
|
|
361
|
+
loginRateLimit: {
|
|
362
|
+
enabled: true, // Default: true
|
|
363
|
+
maxAttempts: 5, // Max failed attempts before blocking (default: 5)
|
|
364
|
+
windowMs: 15 * 60 * 1000, // Tracking window (default: 15 minutes)
|
|
365
|
+
blockDurationMs: 15 * 60 * 1000 // Temporary block duration (default: 15 minutes)
|
|
366
|
+
},
|
|
367
|
+
}),
|
|
368
|
+
],
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
To disable rate limiting (e.g. in test or internal automated environments):
|
|
373
|
+
```js
|
|
374
|
+
adminPanelPlugin({ db, loginRateLimit: false });
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Protection Mechanics
|
|
378
|
+
- **IP Tracking**: Tracks failed attempts keyed by client IP (`req.ip` or upstream trusted proxy headers).
|
|
379
|
+
- **Automatic Block**: After `maxAttempts` failed logins within `windowMs`, returns HTTP 429 Too Many Requests with a `Retry-After: <seconds>` response header and JSON error payload.
|
|
380
|
+
- **Success Reset**: A single successful login clears the failed attempt counter for that IP.
|
|
381
|
+
- **Programmatic Reset**: Cleared automatically upon server restart or via `plugin.api.resetLoginAttempts(ip)`.
|
|
382
|
+
|
|
383
|
+
|
package/.agents/AGENTS.md
CHANGED
|
@@ -44,7 +44,7 @@ This `.agents/` customization root is organized into modular topic guides:
|
|
|
44
44
|
6. **[HTTP Response Compression](.agents/COMPRESSION.md)** — Native streaming zlib compression (`server.compression`), Brotli/Gzip/Deflate negotiation, threshold handling, and `res.compress(false)` route opt-out.
|
|
45
45
|
7. **[Dual Authentication System](.agents/AUTH.md)** — Stateful Session + Cookie & Stateless HS256 JWT + Refresh Token Rotation, `req.auth` helpers, and `middleware: ['jwt']` guard integrations.
|
|
46
46
|
8. **[ORM & Database Layer](.agents/ORM.md)** — Model definitions with `defineModel`, `zdb` schemas, Repositories API, Query Caching, Relations, Soft Delete, Scopes & Migrations.
|
|
47
|
-
9. **[Plugin Ecosystem Guide](.agents/PLUGINS.md)** — Lifecycle hooks (`register`, `onRoutesReady`), CSP headers, and detailed reference for all
|
|
47
|
+
9. **[Plugin Ecosystem Guide](.agents/PLUGINS.md)** — Lifecycle hooks (`register`, `onRoutesReady`), CSP headers, and detailed reference for all 19 built-in official plugins (including `apiKeyPlugin`, `xlsxPlugin`, `csvPlugin`, `fileManagerPlugin`, `realtimePlugin`, and `basicAuthPlugin`).
|
|
48
48
|
10. **[Realtime Layer & Adapters](.agents/REALTIME.md)** — Framework-agnostic realtime layer (`core/realtime`), generic WebSocket, SSE, and Socket.IO adapters, subscription identity, backoff reconnect, and auth lifecycle.
|
|
49
49
|
11. **[Email Plugin Guide](.agents/EMAIL.md)** — MJML template compilation, Nodemailer transport, DB delivery logs, Auth Email bridge, and Admin UI.
|
|
50
50
|
12. **[Admin Panel Customization](.agents/ADMIN.md)** — Extending Mithril.js Admin Panel SPA (Custom pages `component.js`, custom field renderers, widgets, single & bulk actions).
|
package/.agents/ARCHITECTURE.md
CHANGED
|
@@ -116,4 +116,14 @@ Plugins expose `name`, `version`, `dependencies`, and lifecycle hooks (`register
|
|
|
116
116
|
| `xlsxPlugin` | Excel / Export | Excel spreadsheet generation, parsing, ORM streaming export (`res.xlsx`), and services |
|
|
117
117
|
| `csvPlugin` | CSV / Export | RFC 4180 CSV generator, parser, Excel UTF-8 BOM, streaming writer (`res.csv`), and services |
|
|
118
118
|
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 5. Developer Experience & Live/Hot Reload (`src/dev-reload/`)
|
|
122
|
+
|
|
123
|
+
- **Zero-Dependency SSE Reload Hub**: Enabled via `createApp({ devReload: true })` (or `devReload: { watchTemplates: true, watchCss: true, debounce: 50 }`).
|
|
124
|
+
- **Instant CSS Hot Swap**: Modifying `.css` files dynamically refreshes all `<link rel="stylesheet">` elements without page refresh or DOM state loss.
|
|
125
|
+
- **Template & Layout Live Reload**: Modifying Nunjucks templates (`.njk`, `.html`) in `views/` or pages in `pages/` automatically invalidates frontmatter/loader caches and broadcasts a debounced `'full'` reload event to active browser tabs.
|
|
126
|
+
- **Error Overlay Frame**: Client runtime script automatically renders terminal-free in-browser error frames for compile/build errors with line numbers and dismiss keyboard shortcuts (`Esc`).
|
|
127
|
+
- **DevTools Toolbar & Dashboard**: Embedded via `fsy.devToolbar()` and available at `/_webspresso` for interactive inspection of routes, models, services, cache, and profiler metrics.
|
|
128
|
+
|
|
119
129
|
|
package/.agents/AUTH.md
CHANGED
|
@@ -101,3 +101,172 @@ module.exports = {
|
|
|
101
101
|
},
|
|
102
102
|
};
|
|
103
103
|
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 5. Zero-Dependency OAuth 2.0 & Social Login (`plugins/oauth`, `core/auth/oauth`)
|
|
108
|
+
|
|
109
|
+
Webspresso includes a built-in, zero-dependency OAuth 2.0 client and plugin with RFC 7636 PKCE, HMAC-SHA256 signed anti-CSRF state tokens, profile normalization, and seamless Dual Auth integration:
|
|
110
|
+
|
|
111
|
+
### 5.1 Plugin Configuration
|
|
112
|
+
```javascript
|
|
113
|
+
const { createApp, oauthPlugin } = require('webspresso');
|
|
114
|
+
|
|
115
|
+
const app = createApp({
|
|
116
|
+
plugins: [
|
|
117
|
+
oauthPlugin({
|
|
118
|
+
secret: process.env.SESSION_SECRET,
|
|
119
|
+
providers: {
|
|
120
|
+
google: {
|
|
121
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
122
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
123
|
+
// redirectUri is optional; auto-inferred from request host if omitted
|
|
124
|
+
},
|
|
125
|
+
github: {
|
|
126
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
127
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
// Optional hook to find or create the user in your database
|
|
131
|
+
async findOrCreateUser({ provider, profile, tokens, db }) {
|
|
132
|
+
let user = await db.getRepository('User').findOne({ email: profile.email });
|
|
133
|
+
if (!user) {
|
|
134
|
+
user = await db.getRepository('User').create({
|
|
135
|
+
email: profile.email,
|
|
136
|
+
name: profile.name,
|
|
137
|
+
avatar_url: profile.avatarUrl,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return user;
|
|
141
|
+
},
|
|
142
|
+
defaultReturnTo: '/dashboard',
|
|
143
|
+
}),
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 5.2 Automatic Routes
|
|
149
|
+
- `GET /auth/:provider` (e.g. `/auth/google?returnTo=/dashboard`): Generates PKCE code challenge and HMAC-signed state token with embedded return URL, redirecting the browser to the provider's authorization page.
|
|
150
|
+
- `GET /auth/:provider/callback`: Validates anti-CSRF state, exchanges authorization code for tokens, fetches normalized profile, calls `findOrCreateUser`, automatically establishes session via `req.auth.login(user)`, and redirects to `returnTo`. For API/mobile clients (`format=json` or `Accept: application/json`), returns `{ success: true, user, token }`.
|
|
151
|
+
|
|
152
|
+
### 5.3 Template Helper (`fsy.oauthUrl`)
|
|
153
|
+
In Nunjucks templates:
|
|
154
|
+
```html
|
|
155
|
+
<a href="{{ fsy.oauthUrl('google') }}" class="btn">Sign in with Google</a>
|
|
156
|
+
<a href="{{ fsy.oauthUrl('github', { returnTo: '/settings' }) }}" class="btn">Connect GitHub</a>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 6. Zero-Dependency Two-Factor Authentication (2FA / TOTP)
|
|
162
|
+
|
|
163
|
+
Webspresso provides an enterprise-grade, zero-dependency 2FA/TOTP suite adhering strictly to **RFC 6238 (TOTP)**, **RFC 4226 (HOTP)**, and **RFC 4648 (Base32)**. It requires NO external npm libraries and features offline SVG QR code generation to prevent leaking secret keys to third-party APIs.
|
|
164
|
+
|
|
165
|
+
### 6.1 Key Capabilities
|
|
166
|
+
- **RFC 6238 Time-Based OTP**: Standard 30s step, 6/8 digits, HMAC-SHA1/SHA256/SHA512.
|
|
167
|
+
- **Time Drift Tolerance**: Built-in clock drift window (`window: 1` verifies $t \pm 30s$) and detailed delta analysis.
|
|
168
|
+
- **Offline QR Code Generator**: Generates crisp vector SVG strings and data URIs directly in pure Node.js.
|
|
169
|
+
- **Single-Use Recovery Codes**: Cryptographically secure alphanumeric codes, SHA-256 hashed storage, and timing-safe single-use burning.
|
|
170
|
+
- **Session & Guard Integration**: `req.auth.verify2Fa()`, `req.auth.is2FaVerified()`, and `middleware: ['2fa']`.
|
|
171
|
+
|
|
172
|
+
### 6.2 Initializing 2FA Setup for a User
|
|
173
|
+
```javascript
|
|
174
|
+
const { totp } = require('webspresso');
|
|
175
|
+
|
|
176
|
+
// In an API route: POST /api/user/2fa/setup
|
|
177
|
+
module.exports = {
|
|
178
|
+
middleware: ['auth'],
|
|
179
|
+
async handler(req, res) {
|
|
180
|
+
const setup = totp.createTotpSetup({
|
|
181
|
+
user: req.user,
|
|
182
|
+
issuer: 'My SaaS App',
|
|
183
|
+
recoveryCodeCount: 8,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 1. Temporarily save secret and hashed recovery codes to DB for confirmation
|
|
187
|
+
await req.db.getRepository('User').update(req.user.id, {
|
|
188
|
+
temp_totp_secret: setup.secret,
|
|
189
|
+
temp_recovery_codes: JSON.stringify(setup.recoveryCodesHashed),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 2. Return QR SVG and plaintext recovery codes for user to record
|
|
193
|
+
return res.json({
|
|
194
|
+
secret: setup.secret,
|
|
195
|
+
qrSvg: setup.qrSvg,
|
|
196
|
+
qrDataUri: setup.qrDataUri,
|
|
197
|
+
recoveryCodes: setup.recoveryCodes,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### 6.3 Confirming 2FA Activation
|
|
204
|
+
```javascript
|
|
205
|
+
// In an API route: POST /api/user/2fa/confirm
|
|
206
|
+
module.exports = {
|
|
207
|
+
middleware: ['auth'],
|
|
208
|
+
schema: ({ z }) => ({
|
|
209
|
+
body: z.object({ token: z.string().min(6).max(8) }),
|
|
210
|
+
}),
|
|
211
|
+
async handler(req, res) {
|
|
212
|
+
const user = req.user;
|
|
213
|
+
const { token } = req.input.body;
|
|
214
|
+
|
|
215
|
+
const isValid = totp.verifyTotp(token, user.temp_totp_secret);
|
|
216
|
+
if (!isValid) {
|
|
217
|
+
return res.status(400).json({ error: 'Invalid verification code' });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Activate 2FA on user record
|
|
221
|
+
await req.db.getRepository('User').update(user.id, {
|
|
222
|
+
two_factor_enabled: true,
|
|
223
|
+
totp_secret: user.temp_totp_secret,
|
|
224
|
+
recovery_codes: user.temp_recovery_codes,
|
|
225
|
+
temp_totp_secret: null,
|
|
226
|
+
temp_recovery_codes: null,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
req.auth.set2FaVerified(true);
|
|
230
|
+
return res.json({ success: true, message: '2FA enabled successfully' });
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 6.4 2FA Login Flow & Guard Middleware
|
|
236
|
+
```javascript
|
|
237
|
+
// Step 1: Initial password login
|
|
238
|
+
const user = await req.auth.findUserByCredentials(email, password);
|
|
239
|
+
if (user.two_factor_enabled) {
|
|
240
|
+
// Mark session as 2FA pending without full login
|
|
241
|
+
req.auth.set2FaPending(user);
|
|
242
|
+
return res.json({ require2Fa: true });
|
|
243
|
+
}
|
|
244
|
+
await req.auth.login(user);
|
|
245
|
+
|
|
246
|
+
// Step 2: 2FA Verification Endpoint (accepts 6-digit TOTP token OR recovery code)
|
|
247
|
+
const verification = req.auth.verify2Fa(tokenOrRecoveryCode, user);
|
|
248
|
+
if (verification.success) {
|
|
249
|
+
if (verification.type === 'recovery_code') {
|
|
250
|
+
// Burn used recovery code
|
|
251
|
+
await req.db.getRepository('User').update(user.id, {
|
|
252
|
+
recovery_codes: JSON.stringify(verification.remainingHashedCodes),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
await req.auth.login(user);
|
|
256
|
+
req.auth.set2FaVerified(true);
|
|
257
|
+
return res.json({ success: true });
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 6.5 Route Protection with `middleware: ['2fa']`
|
|
262
|
+
```javascript
|
|
263
|
+
// In pages/api/account/billing.get.js or pages/security/index.njk
|
|
264
|
+
module.exports = {
|
|
265
|
+
middleware: ['auth', '2fa'], // Rejects unverified users with 403 or redirects to /2fa
|
|
266
|
+
async handler(req, res) {
|
|
267
|
+
return res.json({ secureData: true });
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
|
package/.agents/CLI.md
CHANGED
|
@@ -13,8 +13,12 @@ npx webspresso dev
|
|
|
13
13
|
# --port, -p <number> (default: 3001)
|
|
14
14
|
# --host <string> (default: localhost)
|
|
15
15
|
|
|
16
|
-
# Build production bundle /
|
|
16
|
+
# Build production bundle / compile assets (Tailwind + esbuild with manifest.json)
|
|
17
17
|
npx webspresso build
|
|
18
|
+
# Flags:
|
|
19
|
+
# --no-css (skip CSS build)
|
|
20
|
+
# --no-js (skip JS bundle build)
|
|
21
|
+
# --no-hash (disable content hashing in filenames)
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
---
|
|
@@ -113,3 +117,193 @@ npx webspresso agents:init
|
|
|
113
117
|
npx webspresso agents:init --force
|
|
114
118
|
```
|
|
115
119
|
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 8. Tailwind CSS Setup & Migration (`add:tailwind`)
|
|
123
|
+
|
|
124
|
+
Add or upgrade Tailwind CSS in a project:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Add Tailwind CSS v4 (Oxide engine, LightningCSS, zero-config)
|
|
128
|
+
npx webspresso add tailwind --v4
|
|
129
|
+
|
|
130
|
+
# Add Tailwind CSS v3 (legacy PostCSS engine)
|
|
131
|
+
npx webspresso add tailwind --v3
|
|
132
|
+
|
|
133
|
+
# Upgrade existing Tailwind v3 project to v4
|
|
134
|
+
npx webspresso add tailwind --upgrade
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 9. Client Bundler Setup (`add:esbuild`)
|
|
140
|
+
|
|
141
|
+
Add client-side JS/TS bundling with esbuild incremental watching:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Scaffold esbuild client bundler setup
|
|
145
|
+
npx webspresso add esbuild
|
|
146
|
+
# or
|
|
147
|
+
npx webspresso add:esbuild
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 10. Data Retention & Storage Cleanup Commands
|
|
153
|
+
|
|
154
|
+
Keep databases and storage clean by pruning historical records and unreferenced files:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Delete audit log entries older than N days (default: 90)
|
|
158
|
+
npx webspresso audit:prune --days 90
|
|
159
|
+
|
|
160
|
+
# Delete email delivery logs older than N days (default: 90)
|
|
161
|
+
npx webspresso email:prune --days 90
|
|
162
|
+
|
|
163
|
+
# Delete completed/failed background jobs older than N days (default: 30)
|
|
164
|
+
npx webspresso queue:prune --days 30
|
|
165
|
+
|
|
166
|
+
# Delete expired site analytics visits & JS errors older than N days
|
|
167
|
+
npx webspresso analytics:prune --days 90
|
|
168
|
+
|
|
169
|
+
# Scan and delete orphaned uploads not referenced by any database model
|
|
170
|
+
npx webspresso upload:prune
|
|
171
|
+
|
|
172
|
+
# Dry run mode (inspect orphaned files without deleting)
|
|
173
|
+
npx webspresso upload:prune --dry-run
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 11. Routes Introspection (`routes`)
|
|
179
|
+
|
|
180
|
+
List and inspect all compiled file-based and API routes in terminal:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# List all application routes in a colorized table
|
|
184
|
+
npx webspresso routes
|
|
185
|
+
|
|
186
|
+
# Filter to only API endpoints
|
|
187
|
+
npx webspresso routes --api
|
|
188
|
+
|
|
189
|
+
# Filter to only SSR page routes
|
|
190
|
+
npx webspresso routes --pages
|
|
191
|
+
|
|
192
|
+
# Output routes as machine-readable JSON
|
|
193
|
+
npx webspresso routes --json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 12. Dev Server DX & Query Profiler
|
|
199
|
+
|
|
200
|
+
When starting in development mode (`isDev: true` / `process.env.NODE_ENV !== 'production'`), Webspresso automatically:
|
|
201
|
+
|
|
202
|
+
- **Displays Startup Banner**: ASCII box with Node/Framework version, Local & Admin URLs, Database dialect, and counts of routes, models, services, and plugins.
|
|
203
|
+
- **Request Profiler & Logger**: Color-coded HTTP requests with status, response duration, and database metrics:
|
|
204
|
+
```text
|
|
205
|
+
[GET 200] /api/notes — 14.2ms | 2 DB queries (2.8ms)
|
|
206
|
+
```
|
|
207
|
+
- **N+1 Query Detection**: Flags queries executing repeatedly against the same table within a single request:
|
|
208
|
+
```text
|
|
209
|
+
⚠️ [webspresso:n+1] Potential N+1 query pattern detected on table 'users' (5 queries, 6.4ms). Consider eager loading with repo.query().include('users') or batching.
|
|
210
|
+
```
|
|
211
|
+
- **Configuration (`dev` options in `createApp`)**:
|
|
212
|
+
```javascript
|
|
213
|
+
createApp({
|
|
214
|
+
dev: {
|
|
215
|
+
banner: true, // show startup banner (default: true in dev)
|
|
216
|
+
routeTable: true, // show route table in banner (default: true)
|
|
217
|
+
profiler: true, // enable request & DB profiler (default: true)
|
|
218
|
+
nPlusOne: true, // detect N+1 queries (default: true)
|
|
219
|
+
nPlusOneThreshold: 4,// query count threshold for warning (default: 4)
|
|
220
|
+
slowQueryMs: 50, // flag queries slower than N ms (default: 50)
|
|
221
|
+
verbose: false, // log individual SQL statements
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 13. Scaffolding & Code Generation (`make:*` Commands)
|
|
229
|
+
|
|
230
|
+
Quickly generate boilerplate adhering strictly to Webspresso contracts:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# 1. Scaffold an ORM Model in models/<Name>.js with parsed zdb fields and Knex migration
|
|
234
|
+
npx webspresso make:model Post --fields title:string,content:text,published:boolean,views:integer --migration
|
|
235
|
+
# Flags:
|
|
236
|
+
# --fields <list> (title:string,content:text,views:integer,price:number,published:boolean,authorId:id)
|
|
237
|
+
# --table <name> (custom table name; defaults to pluralized lowercase)
|
|
238
|
+
# --migration, -m (auto-generate timestamped Knex migration in migrations/)
|
|
239
|
+
# --soft-deletes (include deletedAt column and default scope)
|
|
240
|
+
|
|
241
|
+
# 2. Scaffold API Endpoints in pages/api/
|
|
242
|
+
# Single endpoint with Zod validation and handler:
|
|
243
|
+
npx webspresso make:api notes/export --method POST --auth
|
|
244
|
+
|
|
245
|
+
# Full REST CRUD Suite (list, create, detail, update, delete) in pages/api/posts:
|
|
246
|
+
npx webspresso make:api posts --crud --model Post
|
|
247
|
+
|
|
248
|
+
# 3. Scaffold an SSR Page in pages/
|
|
249
|
+
npx webspresso make:page blog/[slug]
|
|
250
|
+
# Generates pages/blog/[slug].njk template + companion loader pages/blog/[slug].js
|
|
251
|
+
|
|
252
|
+
# 4. Scaffold a Service in services/<domain>/<action>.js
|
|
253
|
+
npx webspresso make:service order.process --transaction --auth admin
|
|
254
|
+
|
|
255
|
+
# 5. Scaffold a Background Queue Worker in jobs/<name>.js
|
|
256
|
+
npx webspresso make:job send-welcome-email --queue default --retries 3
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## 14. OpenAPI & API Documentation Export (`openapi` / `api:docs`)
|
|
262
|
+
|
|
263
|
+
Generate and export standard OpenAPI 3.0 JSON specification from file-based API routes and Zod schemas:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# Print OpenAPI 3.0 specification to stdout
|
|
267
|
+
npx webspresso openapi
|
|
268
|
+
|
|
269
|
+
# Export directly to a JSON file
|
|
270
|
+
npx webspresso openapi --out openapi.json
|
|
271
|
+
|
|
272
|
+
# Export with custom title, version, and server URL
|
|
273
|
+
npx webspresso api:docs --out public/openapi.json --title "My API" --api-version "2.0.0" --server "https://api.myapp.com"
|
|
274
|
+
|
|
275
|
+
# Include ORM model schemas in components.schemas
|
|
276
|
+
npx webspresso openapi --out openapi.json --include-orm
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 15. TypeScript Definitions & IDE Auto-Complete Generator (`typegen` / `types`)
|
|
282
|
+
|
|
283
|
+
Scan ORM models, business services, and routes to automatically generate typed TypeScript declaration files (`.webspresso/types.d.ts` and `webspresso-env.d.ts`) with module augmentation:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# Generate TypeScript definitions
|
|
287
|
+
npx webspresso typegen
|
|
288
|
+
|
|
289
|
+
# Aliases
|
|
290
|
+
npx webspresso types
|
|
291
|
+
npx webspresso generate:types
|
|
292
|
+
|
|
293
|
+
# Watch mode (automatically regenerates on model/service/page edits)
|
|
294
|
+
npx webspresso typegen --watch
|
|
295
|
+
|
|
296
|
+
# Custom output destination
|
|
297
|
+
npx webspresso typegen --out types/webspresso.d.ts --no-env-file
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Generated Capabilities & IDE DX:
|
|
301
|
+
- **Typed Models (`db.getRepository('Model')`)**: Auto-completes model names and infers entity column types from Zod schemas.
|
|
302
|
+
- **Typed Services (`req.service('name', input)`)**: Auto-completes registered service names and enforces validated input payloads.
|
|
303
|
+
- **Typed Route Table (`AppRoute`, `AppRoutePath`)**: Union types for all discovered SSR and API routes.
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|