webspresso 0.0.98 → 0.0.100

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.
Files changed (88) hide show
  1. package/.agents/ADMIN.md +11 -1
  2. package/.agents/AGENTS.md +2 -2
  3. package/.agents/AUTH.md +415 -0
  4. package/.agents/CLI.md +47 -0
  5. package/.agents/ERRORS.md +59 -0
  6. package/.agents/ORM.md +178 -4
  7. package/.agents/PLUGINS.md +113 -8
  8. package/.agents/QUEUE.md +92 -0
  9. package/.agents/ROUTING.md +160 -7
  10. package/.agents/STREAMING.md +35 -0
  11. package/AGENTS.md +2 -0
  12. package/README.md +145 -93
  13. package/bin/commands/add-docker.js +467 -0
  14. package/bin/commands/deploy-check.js +339 -0
  15. package/bin/webspresso.js +4 -0
  16. package/core/auth/index.js +123 -1
  17. package/core/auth/magic-link.js +170 -0
  18. package/core/auth/manager.js +477 -3
  19. package/core/auth/middleware.js +118 -5
  20. package/core/auth/oauth.js +517 -0
  21. package/core/auth/qrcode.js +499 -0
  22. package/core/auth/session-manager.js +255 -0
  23. package/core/auth/sessions.js +351 -0
  24. package/core/auth/tokens.js +1 -0
  25. package/core/auth/totp.js +507 -0
  26. package/core/auth/user-agent.js +161 -0
  27. package/core/orm/eager-loader.js +144 -10
  28. package/core/orm/index.js +10 -1
  29. package/core/orm/model.js +55 -1
  30. package/core/orm/query-builder.js +90 -4
  31. package/core/orm/repository.js +327 -16
  32. package/core/orm/scopes.js +3 -2
  33. package/core/orm/search.js +213 -0
  34. package/core/orm/types.js +6 -1
  35. package/core/queue/cron.js +544 -0
  36. package/core/queue/index.js +8 -1
  37. package/core/queue/manager.js +100 -1
  38. package/core/queue/scheduler.js +415 -0
  39. package/core/sse/index.js +19 -0
  40. package/core/sse/stream.js +430 -0
  41. package/core/ssr/stream.js +62 -2
  42. package/index.d.ts +794 -6
  43. package/index.js +48 -0
  44. package/package.json +7 -5
  45. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +323 -31
  46. package/plugins/admin-panel/client/parts/08-record-list.js +5 -0
  47. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  48. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  49. package/plugins/admin-panel/field-renderers/index.js +2 -0
  50. package/plugins/admin-panel/modules/admin-users.js +1 -1
  51. package/plugins/api-key/crypto.js +101 -0
  52. package/plugins/api-key/index.js +288 -0
  53. package/plugins/api-key/middleware.js +461 -0
  54. package/plugins/api-key/migration-template.js +48 -0
  55. package/plugins/api-key/model.js +50 -0
  56. package/plugins/api-key/scopes.js +125 -0
  57. package/plugins/audit-log/admin-component.js +487 -70
  58. package/plugins/audit-log/api-handlers.js +114 -0
  59. package/plugins/audit-log/index.js +24 -0
  60. package/plugins/content/client/inline-edit.js +2 -46
  61. package/plugins/cors.js +13 -2
  62. package/plugins/email/auth-bridge.js +66 -0
  63. package/plugins/email/bundled-templates.js +15 -0
  64. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  65. package/plugins/file-manager/admin-component.js +69 -53
  66. package/plugins/file-manager/index.js +25 -8
  67. package/plugins/index.js +8 -0
  68. package/plugins/oauth/index.js +338 -0
  69. package/plugins/queue/index.js +4 -0
  70. package/plugins/webhooks/admin-component.js +608 -0
  71. package/plugins/webhooks/api-handlers.js +237 -0
  72. package/plugins/webhooks/crypto.js +144 -0
  73. package/plugins/webhooks/dispatcher.js +708 -0
  74. package/plugins/webhooks/index.js +263 -0
  75. package/plugins/webhooks/matcher.js +113 -0
  76. package/plugins/webhooks/migration-template.js +75 -0
  77. package/plugins/webhooks/models.js +98 -0
  78. package/plugins/webhooks/sender.js +176 -0
  79. package/src/api/api-loader.js +1 -0
  80. package/src/bundle/index.js +2 -1
  81. package/src/discovery/file-route-parser.js +44 -1
  82. package/src/discovery/scan-routes.js +2 -2
  83. package/src/file-router.js +310 -104
  84. package/src/helpers.js +16 -1
  85. package/src/njk-frontmatter.js +3 -0
  86. package/src/routing/cascading.js +490 -0
  87. package/src/routing/middleware-resolver.js +92 -0
  88. package/src/server.js +6 -0
package/.agents/ADMIN.md CHANGED
@@ -225,7 +225,7 @@ registry.registerWidget('recent-orders', {
225
225
  - Top tables by row count with visual progress percentage bars
226
226
  - Direct navigation links to model CRUD screens
227
227
  - **`quick-actions`**: Fast shortcuts to create new records across enabled models.
228
- - **`recent-activity`**: Timeline of recent administrative mutations (creates, updates, deletes).
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
229
 
230
230
  ---
231
231
 
@@ -245,6 +245,16 @@ registry.registerAction('Product', {
245
245
  },
246
246
  });
247
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
+
248
258
  // Bulk multi-select table action
249
259
  registry.registerBulkAction('Product', {
250
260
  id: 'bulk-archive',
package/.agents/AGENTS.md CHANGED
@@ -44,11 +44,11 @@ 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 18 built-in official plugins (including `xlsxPlugin`, `csvPlugin`, `fileManagerPlugin`, `realtimePlugin`, and `basicAuthPlugin`).
47
+ 9. **[Plugin Ecosystem Guide](.agents/PLUGINS.md)** — Lifecycle hooks (`register`, `onRoutesReady`), CSP headers, and detailed reference for all 20 built-in official plugins (including `webhooksPlugin`, `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).
51
- 13. **[CLI Tooling & Commands](.agents/CLI.md)** — `webspresso dev`, `build`, `doctor`, `db:migrate`, `db:seed`, `polar:migrate`, `favicon:generate`, `add:tailwind`, `agents:init`.
51
+ 13. **[CLI Tooling & Commands](CLI.md)** — `webspresso dev`, `build`, `doctor`, `db:migrate`, `db:seed`, `polar:migrate`, `favicon:generate`, `add:tailwind`, `add:docker`, `deploy:check`, `agents:init`.
52
52
  14. **[Development & Testing Workflow](.agents/WORKFLOW.md)** — CLI commands, Vitest unit/integration testing, Playwright E2E tests, TypeScript type checking, and zero regression policy.
53
53
  15. **[Coding Standards & Conventions](.agents/CONVENTIONS.md)** — Zero external dependencies policy, strict function signature & schema verification, clean component separation, and log traceback inspection rules.
54
54
  16. **[Background Job Queue](.agents/QUEUE.md)** — In-Memory, Knex Database, and Distributed Redis queue adapters, job auto-discovery (`jobs/`), retries, and graceful draining.
package/.agents/AUTH.md CHANGED
@@ -45,6 +45,10 @@ Attached to Express request during auth middleware execution:
45
45
  | `req.auth.refreshAccessToken(refreshToken, options)` | Refresh access token & rotate refresh token |
46
46
  | `req.auth.check()` / `req.auth.guest()` | Check if user is authenticated or guest |
47
47
  | `req.auth.user()` / `req.auth.id()` | Get current user object or ID |
48
+ | `req.auth.sessions()` | List active sessions & device info for current user |
49
+ | `req.auth.revokeSession(sessionId)` | Revoke a specific session (remotely log out device) |
50
+ | `req.auth.revokeOtherSessions()` | Revoke all sessions except the current active session |
51
+ | `req.auth.revokeAllSessions(userId?)` | Revoke all active sessions (force logout from all devices) |
48
52
 
49
53
  ---
50
54
 
@@ -101,3 +105,414 @@ module.exports = {
101
105
  },
102
106
  };
103
107
  ```
108
+
109
+ ---
110
+
111
+ ## 5. Zero-Dependency OAuth 2.0 & Social Login (`plugins/oauth`, `core/auth/oauth`)
112
+
113
+ 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:
114
+
115
+ ### 5.1 Plugin Configuration
116
+ ```javascript
117
+ const { createApp, oauthPlugin } = require('webspresso');
118
+
119
+ const app = createApp({
120
+ plugins: [
121
+ oauthPlugin({
122
+ secret: process.env.SESSION_SECRET,
123
+ providers: {
124
+ google: {
125
+ clientId: process.env.GOOGLE_CLIENT_ID,
126
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET,
127
+ // redirectUri is optional; auto-inferred from request host if omitted
128
+ },
129
+ github: {
130
+ clientId: process.env.GITHUB_CLIENT_ID,
131
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
132
+ },
133
+ },
134
+ // Optional hook to find or create the user in your database
135
+ async findOrCreateUser({ provider, profile, tokens, db }) {
136
+ let user = await db.getRepository('User').findOne({ email: profile.email });
137
+ if (!user) {
138
+ user = await db.getRepository('User').create({
139
+ email: profile.email,
140
+ name: profile.name,
141
+ avatar_url: profile.avatarUrl,
142
+ });
143
+ }
144
+ return user;
145
+ },
146
+ defaultReturnTo: '/dashboard',
147
+ }),
148
+ ],
149
+ });
150
+ ```
151
+
152
+ ### 5.2 Automatic Routes
153
+ - `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.
154
+ - `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 }`.
155
+
156
+ ### 5.3 Template Helper (`fsy.oauthUrl`)
157
+ In Nunjucks templates:
158
+ ```html
159
+ <a href="{{ fsy.oauthUrl('google') }}" class="btn">Sign in with Google</a>
160
+ <a href="{{ fsy.oauthUrl('github', { returnTo: '/settings' }) }}" class="btn">Connect GitHub</a>
161
+ ```
162
+
163
+ ---
164
+
165
+ ## 6. Zero-Dependency Two-Factor Authentication (2FA / TOTP)
166
+
167
+ 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.
168
+
169
+ ### 6.1 Key Capabilities
170
+ - **RFC 6238 Time-Based OTP**: Standard 30s step, 6/8 digits, HMAC-SHA1/SHA256/SHA512.
171
+ - **Time Drift Tolerance**: Built-in clock drift window (`window: 1` verifies $t \pm 30s$) and detailed delta analysis.
172
+ - **Offline QR Code Generator**: Generates crisp vector SVG strings and data URIs directly in pure Node.js.
173
+ - **Single-Use Recovery Codes**: Cryptographically secure alphanumeric codes, SHA-256 hashed storage, and timing-safe single-use burning.
174
+ - **Session & Guard Integration**: `req.auth.verify2Fa()`, `req.auth.is2FaVerified()`, and `middleware: ['2fa']`.
175
+
176
+ ### 6.2 Initializing 2FA Setup for a User
177
+ ```javascript
178
+ const { totp } = require('webspresso');
179
+
180
+ // In an API route: POST /api/user/2fa/setup
181
+ module.exports = {
182
+ middleware: ['auth'],
183
+ async handler(req, res) {
184
+ const setup = totp.createTotpSetup({
185
+ user: req.user,
186
+ issuer: 'My SaaS App',
187
+ recoveryCodeCount: 8,
188
+ });
189
+
190
+ // 1. Temporarily save secret and hashed recovery codes to DB for confirmation
191
+ await req.db.getRepository('User').update(req.user.id, {
192
+ temp_totp_secret: setup.secret,
193
+ temp_recovery_codes: JSON.stringify(setup.recoveryCodesHashed),
194
+ });
195
+
196
+ // 2. Return QR SVG and plaintext recovery codes for user to record
197
+ return res.json({
198
+ secret: setup.secret,
199
+ qrSvg: setup.qrSvg,
200
+ qrDataUri: setup.qrDataUri,
201
+ recoveryCodes: setup.recoveryCodes,
202
+ });
203
+ },
204
+ };
205
+ ```
206
+
207
+ ### 6.3 Confirming 2FA Activation
208
+ ```javascript
209
+ // In an API route: POST /api/user/2fa/confirm
210
+ module.exports = {
211
+ middleware: ['auth'],
212
+ schema: ({ z }) => ({
213
+ body: z.object({ token: z.string().min(6).max(8) }),
214
+ }),
215
+ async handler(req, res) {
216
+ const user = req.user;
217
+ const { token } = req.input.body;
218
+
219
+ const isValid = totp.verifyTotp(token, user.temp_totp_secret);
220
+ if (!isValid) {
221
+ return res.status(400).json({ error: 'Invalid verification code' });
222
+ }
223
+
224
+ // Activate 2FA on user record
225
+ await req.db.getRepository('User').update(user.id, {
226
+ two_factor_enabled: true,
227
+ totp_secret: user.temp_totp_secret,
228
+ recovery_codes: user.temp_recovery_codes,
229
+ temp_totp_secret: null,
230
+ temp_recovery_codes: null,
231
+ });
232
+
233
+ req.auth.set2FaVerified(true);
234
+ return res.json({ success: true, message: '2FA enabled successfully' });
235
+ },
236
+ };
237
+ ```
238
+
239
+ ### 6.4 2FA Login Flow & Guard Middleware
240
+ ```javascript
241
+ // Step 1: Initial password login
242
+ const user = await req.auth.findUserByCredentials(email, password);
243
+ if (user.two_factor_enabled) {
244
+ // Mark session as 2FA pending without full login
245
+ req.auth.set2FaPending(user);
246
+ return res.json({ require2Fa: true });
247
+ }
248
+ await req.auth.login(user);
249
+
250
+ // Step 2: 2FA Verification Endpoint (accepts 6-digit TOTP token OR recovery code)
251
+ const verification = req.auth.verify2Fa(tokenOrRecoveryCode, user);
252
+ if (verification.success) {
253
+ if (verification.type === 'recovery_code') {
254
+ // Burn used recovery code
255
+ await req.db.getRepository('User').update(user.id, {
256
+ recovery_codes: JSON.stringify(verification.remainingHashedCodes),
257
+ });
258
+ }
259
+ await req.auth.login(user);
260
+ req.auth.set2FaVerified(true);
261
+ return res.json({ success: true });
262
+ }
263
+ ```
264
+
265
+ ### 6.5 Route Protection with `middleware: ['2fa']`
266
+ ```javascript
267
+ // In pages/api/account/billing.get.js or pages/security/index.njk
268
+ module.exports = {
269
+ middleware: ['auth', '2fa'], // Rejects unverified users with 403 or redirects to /2fa
270
+ async handler(req, res) {
271
+ return res.json({ secureData: true });
272
+ },
273
+ };
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 7. Magic Link & Passwordless Authentication
279
+
280
+ Webspresso includes zero-dependency, cryptographically secure **Magic Link / Passwordless Authentication**:
281
+ - **Single-Use Timed Tokens**: 32-byte cryptographically secure random tokens stored hashed (`SHA-256`) in the `auth_tokens` table (`type = 'magic_link'`) with a default 15-minute TTL.
282
+ - **Anti-Enumeration Protection**: `requestMagicLink` returns `{ sent: true, userFound: false }` for unknown emails by default so attackers cannot probe user existence.
283
+ - **Auto-Verification**: Successfully signing in via a magic link automatically marks `email_verified_at` if the user is not yet verified.
284
+ - **Dual Session & JWT Support**: Seamlessly establishes web sessions (`req.auth.login`) and/or issues JWT access/refresh token pairs.
285
+ - **Native Email Plugin Integration**: Built-in `auth-magic-link` MJML template, automated delivery via `plugins/email`'s `wireAuthEmails`, and pre-wired `/api/auth/magic-link` routes.
286
+
287
+ ### 7.1 Setup in `quickAuth`
288
+ ```javascript
289
+ const { quickAuth } = require('webspresso/core/auth');
290
+
291
+ const auth = quickAuth({
292
+ db,
293
+ userModel: 'User',
294
+ identifierField: 'email',
295
+ magicLink: true, // Automatically provisions Knex auth_tokens adapter
296
+ });
297
+ ```
298
+
299
+ ### 7.2 Requesting a Magic Link (`pages/api/auth/magic-link.post.js`)
300
+ ```javascript
301
+ const { defineApi } = require('webspresso');
302
+
303
+ module.exports = defineApi({
304
+ schema: ({ z }) => ({
305
+ body: z.object({
306
+ email: z.string().email(),
307
+ }),
308
+ }),
309
+ async handler(req, res) {
310
+ const { email } = req.input.body;
311
+
312
+ // Dispatches email notification via plugins/email (if active)
313
+ await req.auth.requestMagicLink(email, {
314
+ tokenTtlMs: 15 * 60 * 1000, // 15 mins (default)
315
+ path: '/auth/magic-link/verify',
316
+ });
317
+
318
+ return res.json({
319
+ ok: true,
320
+ message: 'If an account exists with this email, a sign-in link has been sent.',
321
+ });
322
+ },
323
+ });
324
+ ```
325
+
326
+ ### 7.3 Verifying & Logging In (`pages/api/auth/magic-link/verify.get.js`)
327
+ ```javascript
328
+ const { defineApi } = require('webspresso');
329
+
330
+ module.exports = defineApi({
331
+ schema: ({ z }) => ({
332
+ query: z.object({
333
+ token: z.string().min(1),
334
+ }),
335
+ }),
336
+ async handler(req, res) {
337
+ const { token } = req.input.query;
338
+
339
+ // Verifies token, consumes it, marks email verified, logs into session and/or generates JWTs
340
+ const result = await req.auth.loginWithMagicLink(token, {
341
+ remember: true,
342
+ jwt: false, // Set to true to receive accessToken and refreshToken
343
+ });
344
+
345
+ // For browser requests, redirect to dashboard:
346
+ if (req.accepts('html')) {
347
+ return res.redirect('/dashboard');
348
+ }
349
+
350
+ return res.json({ ok: true, user: result.user });
351
+ },
352
+ });
353
+ ```
354
+
355
+ ### 7.4 Using Built-in Route Middlewares
356
+ You can also mount Webspresso's pre-configured Express middlewares directly:
357
+ ```javascript
358
+ const {
359
+ createMagicLinkRequestHandler,
360
+ createMagicLinkVerifyMiddleware,
361
+ } = require('webspresso/core/auth');
362
+
363
+ // Request link: POST /auth/magic-link
364
+ app.post('/auth/magic-link', createMagicLinkRequestHandler(authManager));
365
+
366
+ // Verify & Login: GET /auth/magic-link/verify
367
+ app.get('/auth/magic-link/verify', createMagicLinkVerifyMiddleware(authManager, {
368
+ redirectTo: '/dashboard',
369
+ failureRedirect: '/login?error=invalid_magic_link',
370
+ jwt: false,
371
+ }));
372
+ ```
373
+
374
+ ### 7.5 AuthManager Magic Link Events
375
+ `AuthManager` extends `EventEmitter`, allowing you to hook into magic link lifecycle events:
376
+ ```javascript
377
+ authManager.on('magicLink:requested', ({ userId, email, expiresAt }) => {
378
+ console.log(`Magic link requested for ${email}, expires at ${expiresAt}`);
379
+ });
380
+
381
+ authManager.on('magicLink:verified', ({ userId }) => {
382
+ console.log(`User ${userId} signed in via magic link`);
383
+ });
384
+ ```
385
+
386
+ ---
387
+
388
+ ## 8. Active Devices & Remote Session Revocation
389
+
390
+ Webspresso provides a zero-dependency session management and device tracking system (`webspresso/core/auth`) allowing users to view all active logins, detect browser/OS/hardware types, and remotely revoke unwanted sessions.
391
+
392
+ ### 8.1 Database Migration
393
+ Create the `user_sessions` table using the built-in migration helper:
394
+ ```javascript
395
+ const { createUserSessionsTable } = require('webspresso/core/auth');
396
+
397
+ exports.up = async function(knex) {
398
+ await createUserSessionsTable(knex);
399
+ };
400
+
401
+ exports.down = async function(knex) {
402
+ const { dropUserSessionsTable } = require('webspresso/core/auth');
403
+ await dropUserSessionsTable(knex);
404
+ };
405
+ ```
406
+
407
+ Schema columns:
408
+ - `id`: Bigint primary key
409
+ - `session_id`: Unique Express session ID
410
+ - `user_id`: User ID reference
411
+ - `ip_address`: Proxy-safe client IP (`x-forwarded-for`, `x-real-ip`, or socket)
412
+ - `user_agent`: Raw User-Agent string
413
+ - `browser`: Detected browser and version (e.g. `Chrome 128`, `Safari 17`, `Firefox 125`, `Edge 122`)
414
+ - `os`: Detected operating system (e.g. `macOS 14`, `Windows 10/11`, `iOS 17.4`, `Android 14`, `Ubuntu Linux`)
415
+ - `device`: Device form factor (`desktop`, `mobile`, `tablet`, `bot`)
416
+ - `last_active_at`: Last seen timestamp (throttled to 60s by default to protect DB performance)
417
+ - `created_at`: Initial login timestamp
418
+
419
+ ### 8.2 Enabling Session Tracking
420
+ Enable session tracking in `createAuth` or `quickAuth`:
421
+ ```javascript
422
+ const { quickAuth } = require('webspresso/core/auth');
423
+
424
+ const auth = quickAuth({
425
+ db,
426
+ sessionTracking: true, // Automatically enables Knex session tracker and active session verification
427
+ });
428
+ ```
429
+
430
+ Or manually with custom options:
431
+ ```javascript
432
+ const { createAuth, createKnexSessionTracker } = require('webspresso/core/auth');
433
+
434
+ const auth = createAuth({
435
+ db,
436
+ sessionTracking: true,
437
+ sessionTracker: createKnexSessionTracker(db),
438
+ sessionTouchThrottleMs: 60000, // Touch session at most once per minute
439
+ // ...other config
440
+ });
441
+ ```
442
+
443
+ ### 8.3 File-Based API Routes (`pages/api/*`)
444
+
445
+ #### List Active Devices (`pages/api/auth/sessions.get.js`)
446
+ ```javascript
447
+ const { defineApi } = require('webspresso');
448
+
449
+ module.exports = defineApi({
450
+ middleware: ['auth'],
451
+ async handler(req, res) {
452
+ // Returns array of active sessions with is_current flag for current device
453
+ const sessions = await req.auth.sessions();
454
+ return res.json({ ok: true, sessions });
455
+ },
456
+ });
457
+ ```
458
+
459
+ #### Revoke Session / Remote Logout (`pages/api/auth/sessions/revoke.post.js`)
460
+ ```javascript
461
+ const { defineApi } = require('webspresso');
462
+
463
+ module.exports = defineApi({
464
+ middleware: ['auth'],
465
+ schema: ({ z }) => ({
466
+ body: z.object({
467
+ sessionId: z.string().optional(),
468
+ allOthers: z.boolean().optional(),
469
+ }),
470
+ }),
471
+ async handler(req, res) {
472
+ const { sessionId, allOthers } = req.input.body;
473
+
474
+ if (allOthers) {
475
+ // Revokes all sessions except current device
476
+ const count = await req.auth.revokeOtherSessions();
477
+ return res.json({ ok: true, revokedCount: count, message: 'All other sessions revoked' });
478
+ }
479
+
480
+ if (sessionId) {
481
+ // Revokes specified session (logs out current user if it is the current session)
482
+ await req.auth.revokeSession(sessionId);
483
+ return res.json({ ok: true, message: 'Session revoked' });
484
+ }
485
+
486
+ return res.status(400).json({ error: 'sessionId or allOthers is required' });
487
+ },
488
+ });
489
+ ```
490
+
491
+ ### 8.4 Automatic Invalidation in Middleware
492
+ When a session is revoked remotely, Webspresso's `authenticate` middleware automatically detects it on the next incoming request from that device:
493
+ - The revoked session is destroyed in `req.sessionStore`.
494
+ - `req.user` is set to `null`.
495
+ - Any subsequent `requireAuth` or `middleware: ['auth']` guard immediately redirects or rejects the request with `401 Unauthorized`.
496
+
497
+ ### 8.5 Session Lifecycle Events
498
+ `AuthManager` emits EventEmitter events for monitoring and security audits:
499
+ ```javascript
500
+ auth.on('session:created', ({ sessionId, userId, ip, browser, os, device }) => {
501
+ console.log(`New session created for user ${userId} from ${device} (${browser} on ${os}) at ${ip}`);
502
+ });
503
+
504
+ auth.on('session:revoked', ({ sessionId, userId, revokedBy }) => {
505
+ console.log(`Session ${sessionId} was revoked`);
506
+ });
507
+
508
+ auth.on('sessions:revoked_others', ({ userId, currentSessionId, count }) => {
509
+ console.log(`User ${userId} revoked ${count} other sessions`);
510
+ });
511
+
512
+ auth.on('sessions:revoked_all', ({ userId, count }) => {
513
+ console.log(`All ${count} sessions revoked for user ${userId}`);
514
+ });
515
+ ```
516
+
517
+
518
+
package/.agents/CLI.md CHANGED
@@ -302,8 +302,55 @@ npx webspresso typegen --out types/webspresso.d.ts --no-env-file
302
302
  - **Typed Services (`req.service('name', input)`)**: Auto-completes registered service names and enforces validated input payloads.
303
303
  - **Typed Route Table (`AppRoute`, `AppRoutePath`)**: Union types for all discovered SSR and API routes.
304
304
 
305
+ ---
306
+
307
+ ## 16. Containerization & Docker Setup (`add:docker` / `docker`)
308
+
309
+ Generate production-ready, security-hardened, multi-stage `Dockerfile`, `.dockerignore`, and `docker-compose.yml`:
310
+
311
+ ```bash
312
+ # Generate Dockerfile, .dockerignore, and docker-compose.yml
313
+ npx webspresso add:docker
314
+
315
+ # Customize Node.js version, database service, or container port
316
+ npx webspresso add:docker --node 22-alpine --db postgres --redis --port 3000
305
317
 
318
+ # Overwrite existing files
319
+ npx webspresso add:docker --force
320
+ ```
306
321
 
322
+ ### Generated Artifacts:
323
+ - **Multi-Stage `Dockerfile`**:
324
+ - `deps` stage: caches production dependencies with native build tools (`python3`, `make`, `g++`).
325
+ - `builder` stage: runs asset build scripts (`npm run build`).
326
+ - `runner` stage: runs as unprivileged user `webspresso` (UID 1001), health check with `/health` and `/healthz`, and `SIGTERM` signal handling.
327
+ - **`.dockerignore`**: Excludes `node_modules`, `.env`, local `.sqlite` databases, test files, and git metadata while preserving upload directories.
328
+ - **`docker-compose.yml`**: Configures app container with persistent upload volumes, database services (PostgreSQL, MySQL, or SQLite), and optional Redis.
329
+ - **`package.json` Scripts**: Adds `docker:build`, `docker:run`, `docker:up`, and `docker:down`.
307
330
 
331
+ ---
308
332
 
333
+ ## 17. Production Deployment Preflight Check (`deploy:check` / `check:deploy`)
334
+
335
+ Run comprehensive pre-flight verification before deploying to Fly.io, Railway, Render, VPS, or Kubernetes:
336
+
337
+ ```bash
338
+ # Run production preflight verification
339
+ npx webspresso deploy:check
340
+
341
+ # Run against a specific environment (.env.staging or .env.production)
342
+ npx webspresso deploy:check --env staging
343
+
344
+ # Strict mode: fail with exit code 1 on warnings as well as errors (ideal for CI/CD)
345
+ npx webspresso deploy:check --strict
346
+
347
+ # Machine-readable JSON output for automated deployment pipelines
348
+ npx webspresso deploy:check --json
349
+ ```
309
350
 
351
+ ### Audited Verification Areas:
352
+ 1. **Secrets & Cryptography**: Ensures `SESSION_SECRET` or `APP_KEY` is set, >= 32 characters, and not using weak placeholder defaults.
353
+ 2. **Git & Leak Prevention**: Verifies `.env` and local database files are excluded in `.gitignore`.
354
+ 3. **Container Hardening**: Ensures `Dockerfile` runs as a non-root `USER`, configures a `HEALTHCHECK`, and uses multi-stage caching.
355
+ 4. **Database & Migrations**: Audits for pending unapplied migrations and warns against ephemeral SQLite storage.
356
+ 5. **Server Optimizations**: Validates HTTP compression (`compression: true`), graceful shutdown, and health check endpoints.
package/.agents/ERRORS.md CHANGED
@@ -199,3 +199,62 @@ In server logs, 500 errors are automatically highlighted with visual banners pin
199
199
  Phase: handler
200
200
  ```
201
201
 
202
+ ---
203
+
204
+ ## 6. Directory-Level Error Boundaries (`_error.njk` & `_error.js`)
205
+
206
+ In addition to API JSON errors and the top-level 500 error page, Webspresso provides **Directory-Level Error Boundaries** for SSR pages (similar to Next.js `error.js` or Nuxt `error.vue`):
207
+
208
+ ```
209
+ pages/
210
+ ├── _layout.njk # Root site layout
211
+ ├── _error.njk # Global site error fallback
212
+ └── dashboard/
213
+ ├── _layout.njk # Dashboard layout (sidebar, header)
214
+ ├── _error.njk # Dashboard-scoped error boundary
215
+ ├── _error.js # Optional companion loader
216
+ ├── billing/
217
+ │ └── invoices.njk # If load() fails here...
218
+ └── settings.njk
219
+ ```
220
+
221
+ ### 6.1 Isolated Error Containment
222
+ When an unhandled error is thrown in a page route's `load()`, template rendering, or lifecycle hooks:
223
+ 1. Webspresso searches up the directory hierarchy (innermost leaf to root `pages/`) for the nearest `_error.njk`.
224
+ 2. The error boundary is rendered **inside** the ancestor cascading layouts (`_layout.njk`), ensuring that headers, sidebars, and navigation remain visible while displaying the isolated error state.
225
+ 3. The HTTP response status is preserved from `error.status || error.statusCode || 500` (e.g. throwing `NotFoundError` sets HTTP 404).
226
+
227
+ ### 6.2 Companion Loader (`_error.js`)
228
+ An optional companion `_error.js` can prefetch support data, report errors to monitoring services, or enrich the context:
229
+
230
+ ```javascript
231
+ // pages/dashboard/_error.js
232
+ module.exports = {
233
+ load: async ({ error, req, res, ctx, db }) => {
234
+ return {
235
+ supportEmail: 'support@example.com',
236
+ incidentId: req.id,
237
+ };
238
+ },
239
+ };
240
+ ```
241
+
242
+ ### 6.3 Template Context Variables
243
+ Inside `_error.njk`, the following variables are injected:
244
+ - `error`: `{ message, status, statusCode, code, name, details, fields, stack }` (stack is only exposed when `isDev === true`).
245
+ - `route`: `{ routePath, file }`.
246
+ - `req`: `{ path, query, params, method, url }`.
247
+ - `fsy`: Template helper suite.
248
+ - Plus any data returned by companion `_error.js` loader.
249
+
250
+ ### 6.4 Layout Opt-Out
251
+ To render a full-page custom error view without the parent layout, set `layout: false` in `_error.js` or frontmatter:
252
+ ```yaml
253
+ ---
254
+ layout: false
255
+ ---
256
+ <div class="standalone-error-screen">
257
+ <h1>{{ error.status }} - {{ error.message }}</h1>
258
+ </div>
259
+ ```
260
+