webspresso 0.0.99 → 0.0.101

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 (108) hide show
  1. package/.agents/ADMIN.md +47 -4
  2. package/.agents/AGENTS.md +4 -2
  3. package/.agents/AUTH.md +309 -0
  4. package/.agents/CLI.md +128 -0
  5. package/.agents/EMAIL.md +84 -2
  6. package/.agents/ERRORS.md +59 -0
  7. package/.agents/EVENTS.md +179 -0
  8. package/.agents/ORM.md +149 -0
  9. package/.agents/PLUGINS.md +44 -7
  10. package/.agents/QUEUE.md +18 -10
  11. package/.agents/ROUTING.md +97 -1
  12. package/.agents/STREAMING.md +35 -0
  13. package/AGENTS.md +2 -0
  14. package/README.md +145 -93
  15. package/bin/commands/add-docker.js +467 -0
  16. package/bin/commands/db-makemigrations.js +83 -0
  17. package/bin/commands/deploy-check.js +339 -0
  18. package/bin/commands/list.js +96 -0
  19. package/bin/commands/make-command.js +93 -0
  20. package/bin/commands/make-event.js +80 -0
  21. package/bin/commands/make-factory.js +72 -0
  22. package/bin/commands/make-mail.js +93 -0
  23. package/bin/commands/make-policy.js +106 -0
  24. package/bin/commands/run.js +195 -0
  25. package/bin/webspresso.js +21 -0
  26. package/core/auth/bouncer.js +196 -0
  27. package/core/auth/index.js +73 -1
  28. package/core/auth/magic-link.js +170 -0
  29. package/core/auth/manager.js +343 -3
  30. package/core/auth/middleware.js +45 -6
  31. package/core/auth/session-manager.js +255 -0
  32. package/core/auth/sessions.js +351 -0
  33. package/core/auth/tokens.js +1 -0
  34. package/core/auth/totp.js +4 -1
  35. package/core/auth/user-agent.js +161 -0
  36. package/core/commands/define-command.js +48 -0
  37. package/core/commands/index.js +18 -0
  38. package/core/commands/registry.js +156 -0
  39. package/core/commands/runner.js +76 -0
  40. package/core/compression/compressible.js +1 -1
  41. package/core/compression/middleware.js +35 -251
  42. package/core/content/service.js +8 -2
  43. package/core/events/discovery.js +142 -0
  44. package/core/events/dispatcher.js +552 -0
  45. package/core/events/index.js +23 -0
  46. package/core/events/signal.js +92 -0
  47. package/core/forms/flash.js +234 -0
  48. package/core/forms/form-builder.js +214 -0
  49. package/core/forms/index.js +16 -0
  50. package/core/mail/index.js +23 -0
  51. package/core/mail/mailable.js +250 -0
  52. package/core/mail/mailer.js +184 -0
  53. package/core/mail/transports.js +238 -0
  54. package/core/orm/factory.js +250 -0
  55. package/core/orm/index.js +8 -1
  56. package/core/orm/migrations/diff.js +185 -0
  57. package/core/orm/migrations/index.js +22 -4
  58. package/core/orm/model.js +55 -1
  59. package/core/orm/repository.js +283 -13
  60. package/core/orm/scopes.js +3 -2
  61. package/core/orm/types.js +6 -1
  62. package/core/queue/cron.js +252 -39
  63. package/core/queue/manager.js +20 -2
  64. package/core/queue/scheduler.js +115 -59
  65. package/core/ssr/stream.js +62 -2
  66. package/index.d.ts +540 -14
  67. package/index.js +44 -0
  68. package/package.json +11 -5
  69. package/plugins/admin-panel/api.js +509 -3
  70. package/plugins/admin-panel/index.js +17 -0
  71. package/plugins/admin-panel/modules/admin-users.js +1 -1
  72. package/plugins/audit-log/admin-component.js +54 -54
  73. package/plugins/audit-log/index.js +2 -2
  74. package/plugins/content/client/inline-edit.js +2 -46
  75. package/plugins/content/models/content-entry.js +4 -3
  76. package/plugins/content/models/content-type.js +4 -3
  77. package/plugins/cors.js +15 -4
  78. package/plugins/email/auth-bridge.js +66 -0
  79. package/plugins/email/bundled-templates.js +15 -0
  80. package/plugins/email/index.js +48 -0
  81. package/plugins/email/preview-handler.js +555 -0
  82. package/plugins/email/service.js +23 -12
  83. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  84. package/plugins/email/transport.js +30 -11
  85. package/plugins/file-manager/admin-component.js +49 -49
  86. package/plugins/file-manager/index.js +4 -4
  87. package/plugins/index.js +2 -0
  88. package/plugins/redirect/index.js +1 -1
  89. package/plugins/webhooks/admin-component.js +608 -0
  90. package/plugins/webhooks/api-handlers.js +237 -0
  91. package/plugins/webhooks/crypto.js +144 -0
  92. package/plugins/webhooks/dispatcher.js +708 -0
  93. package/plugins/webhooks/index.js +263 -0
  94. package/plugins/webhooks/matcher.js +113 -0
  95. package/plugins/webhooks/migration-template.js +75 -0
  96. package/plugins/webhooks/models.js +98 -0
  97. package/plugins/webhooks/sender.js +176 -0
  98. package/src/api/api-loader.js +1 -1
  99. package/src/app-context.js +48 -6
  100. package/src/bundle/index.js +2 -1
  101. package/src/dev-reload/index.js +9 -1
  102. package/src/discovery/scan-routes.js +10 -1
  103. package/src/file-router.js +173 -4
  104. package/src/helpers.js +150 -1
  105. package/src/njk-frontmatter.js +3 -0
  106. package/src/pages/page-loader.js +1 -1
  107. package/src/routing/cascading.js +120 -4
  108. package/src/server.js +180 -26
@@ -46,6 +46,25 @@ const {
46
46
  generateQrSvg,
47
47
  generateQrDataUri,
48
48
  } = require('./qrcode');
49
+ const {
50
+ buildMagicLinkUrl,
51
+ formatMagicLinkHtml,
52
+ createMagicLinkVerifyMiddleware,
53
+ createMagicLinkRequestHandler,
54
+ } = require('./magic-link');
55
+ const {
56
+ createUserSessionsTable,
57
+ dropUserSessionsTable,
58
+ createKnexSessionTracker,
59
+ createMemorySessionTracker,
60
+ getClientIp,
61
+ parseUserAgent,
62
+ } = require('./sessions');
63
+ const {
64
+ SessionManager,
65
+ createListSessionsHandler,
66
+ createRevokeSessionHandler,
67
+ } = require('./session-manager');
49
68
 
50
69
  /**
51
70
  * Create authentication instance
@@ -73,6 +92,8 @@ function createAuth(config) {
73
92
  * @param {Object} [options.session] - Session config
74
93
  * @param {boolean} [options.rememberMe=true] - Enable remember me
75
94
  * @param {boolean} [options.authTokens=false] - Enable auth_tokens adapter (password reset / email verify)
95
+ * @param {boolean} [options.magicLink=false] - Enable magic link / passwordless auth tokens adapter
96
+ * @param {boolean} [options.sessionTracking=false] - Enable active session tracking & revocation
76
97
  * @param {Object|boolean} [options.jwt=false] - Enable JWT support or JWT config
77
98
  * @param {string} [options.verifiedField='email_verified_at'] - Verified timestamp column
78
99
  * @returns {AuthManager}
@@ -86,6 +107,8 @@ function quickAuth(options) {
86
107
  session = {},
87
108
  rememberMe = true,
88
109
  authTokens: enableAuthTokens = false,
110
+ magicLink = false,
111
+ sessionTracking = false,
89
112
  jwt = false,
90
113
  verifiedField = 'email_verified_at',
91
114
  } = options;
@@ -151,10 +174,15 @@ function quickAuth(options) {
151
174
  };
152
175
  }
153
176
 
154
- if (enableAuthTokens) {
177
+ if (enableAuthTokens || magicLink) {
155
178
  config.authTokens = createKnexAuthTokensAdapter(db);
156
179
  }
157
180
 
181
+ if (sessionTracking) {
182
+ config.sessionTracking = true;
183
+ config.sessionTracker = createKnexSessionTracker(db);
184
+ }
185
+
158
186
  return createAuth(config);
159
187
  }
160
188
 
@@ -197,6 +225,16 @@ module.exports = {
197
225
  // Classes
198
226
  AuthManager,
199
227
  PolicyManager,
228
+ Bouncer: require('./bouncer').Bouncer,
229
+ createBouncer: require('./bouncer').createBouncer,
230
+ createBouncerMiddleware: require('./bouncer').createBouncerMiddleware,
231
+
232
+ // Policies & Authorization (AdonisJS Bouncer & Django Permissions-inspired)
233
+ definePolicy: require('./policy').definePolicy,
234
+ defineGate: require('./policy').defineGate,
235
+ can: require('./policy').can,
236
+ cannot: require('./policy').cannot,
237
+ authorize: require('./policy').authorize,
200
238
 
201
239
  // Errors
202
240
  AuthenticationError,
@@ -255,6 +293,40 @@ module.exports = {
255
293
  generateQrSvg,
256
294
  generateQrDataUri,
257
295
 
296
+ // Magic Link / Passwordless Authentication
297
+ buildMagicLinkUrl,
298
+ formatMagicLinkHtml,
299
+ createMagicLinkVerifyMiddleware,
300
+ createMagicLinkRequestHandler,
301
+ magicLink: {
302
+ buildMagicLinkUrl,
303
+ formatMagicLinkHtml,
304
+ createMagicLinkVerifyMiddleware,
305
+ createMagicLinkRequestHandler,
306
+ },
307
+
308
+ // Active Devices & Session Management
309
+ SessionManager,
310
+ createUserSessionsTable,
311
+ dropUserSessionsTable,
312
+ createKnexSessionTracker,
313
+ createMemorySessionTracker,
314
+ getClientIp,
315
+ parseUserAgent,
316
+ createListSessionsHandler,
317
+ createRevokeSessionHandler,
318
+ sessions: {
319
+ SessionManager,
320
+ createUserSessionsTable,
321
+ dropUserSessionsTable,
322
+ createKnexSessionTracker,
323
+ createMemorySessionTracker,
324
+ getClientIp,
325
+ parseUserAgent,
326
+ createListSessionsHandler,
327
+ createRevokeSessionHandler,
328
+ },
329
+
258
330
  // Config
259
331
  DEFAULT_CONFIG,
260
332
  };
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Webspresso Magic Link & Passwordless Authentication Module
3
+ * Cryptographically signed one-time tokens, session login, and verification helpers
4
+ * @module core/auth/magic-link
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * Construct full magic link verification URL
11
+ *
12
+ * @param {Object} options
13
+ * @param {string} [options.baseUrl] - Base site URL (e.g. 'https://example.com')
14
+ * @param {string} [options.path='/auth/magic-link/verify'] - Relative path
15
+ * @param {string} options.token - Raw unhashed token
16
+ * @returns {string}
17
+ */
18
+ function buildMagicLinkUrl(options = {}) {
19
+ let baseUrl = String(options.baseUrl || process.env.BASE_URL || 'http://localhost:3000');
20
+ while (baseUrl.endsWith('/')) {
21
+ baseUrl = baseUrl.slice(0, -1);
22
+ }
23
+ const path = options.path || '/auth/magic-link/verify';
24
+ const token = options.token;
25
+
26
+ if (!token) {
27
+ throw new Error('Token is required to build magic link URL');
28
+ }
29
+
30
+ const cleanPath = path.startsWith('/') ? path : `/${path}`;
31
+ const sep = cleanPath.includes('?') ? '&' : '?';
32
+ return `${baseUrl}${cleanPath}${sep}token=${encodeURIComponent(token)}`;
33
+ }
34
+
35
+ /**
36
+ * Default HTML template for magic link emails
37
+ *
38
+ * @param {Object} data
39
+ * @param {string} [data.name='there']
40
+ * @param {string} data.magicLinkUrl
41
+ * @param {number} [data.expiresMinutes=15]
42
+ * @returns {string} Clean HTML email body
43
+ */
44
+ function formatMagicLinkHtml(data) {
45
+ const name = data.name || 'there';
46
+ const url = data.magicLinkUrl;
47
+ const minutes = data.expiresMinutes || 15;
48
+
49
+ return `<!DOCTYPE html>
50
+ <html>
51
+ <head>
52
+ <meta charset="utf-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>Sign in to your account</title>
55
+ </head>
56
+ <body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f4f4f5; margin: 0; padding: 24px; color: #18181b;">
57
+ <div style="max-width: 520px; margin: 0 auto; background: #ffffff; border-radius: 16px; padding: 32px; border: 1px solid #e4e4e7;">
58
+ <h2 style="margin-top: 0; font-size: 22px; font-weight: 700; color: #18181b;">Sign in to your account</h2>
59
+ <p style="font-size: 15px; line-height: 1.6; color: #52525b;">Hello ${name},</p>
60
+ <p style="font-size: 15px; line-height: 1.6; color: #52525b;">Click the button below to sign in to your account without a password. This link is valid for <strong>${minutes} minutes</strong>.</p>
61
+ <div style="margin: 28px 0; text-align: center;">
62
+ <a href="${url}" style="display: inline-block; background-color: #4f46e5; color: #ffffff; text-decoration: none; padding: 12px 28px; font-weight: 600; font-size: 15px; border-radius: 12px;">Sign In →</a>
63
+ </div>
64
+ <p style="font-size: 13px; line-height: 1.5; color: #a1a1aa; border-top: 1px solid #f4f4f5; padding-top: 16px;">If you did not request this link, you can safely ignore this email.</p>
65
+ </div>
66
+ </body>
67
+ </html>`;
68
+ }
69
+
70
+ /**
71
+ * Express / Webspresso route middleware for magic link verification and session login
72
+ *
73
+ * @param {import('./manager').AuthManager} authManager
74
+ * @param {Object} [options]
75
+ * @param {string} [options.tokenParam='token'] - Query / body parameter name
76
+ * @param {string} [options.redirectTo='/'] - Redirect URL on successful HTML login
77
+ * @param {string} [options.failureRedirect='/login?error=invalid_magic_link'] - Redirect URL on failure
78
+ * @param {boolean} [options.markEmailVerified=true] - Auto-verify email on login
79
+ * @param {boolean} [options.jwt=false] - Generate JWT tokens in response
80
+ * @returns {Function} Express middleware
81
+ */
82
+ function createMagicLinkVerifyMiddleware(authManager, options = {}) {
83
+ const {
84
+ tokenParam = 'token',
85
+ redirectTo = '/',
86
+ failureRedirect = '/login?error=invalid_magic_link',
87
+ markEmailVerified = true,
88
+ jwt = false,
89
+ } = options;
90
+
91
+ return async function magicLinkVerifyMiddleware(req, res, next) {
92
+ const rawToken = req.query?.[tokenParam] || req.body?.[tokenParam];
93
+ const isHtml = req.accepts && req.accepts('html') && req.method === 'GET';
94
+
95
+ if (!rawToken) {
96
+ if (isHtml) {
97
+ return res.redirect(failureRedirect);
98
+ }
99
+ return res.status(400).json({ error: 'Missing magic link token' });
100
+ }
101
+
102
+ try {
103
+ const { user } = await authManager.verifyMagicLink(rawToken, { markEmailVerified });
104
+
105
+ if (req.auth && typeof req.auth.login === 'function') {
106
+ await req.auth.login(user);
107
+ }
108
+
109
+ let jwtTokens = null;
110
+ if (jwt) {
111
+ jwtTokens = {
112
+ accessToken: authManager.generateUserToken(user),
113
+ refreshToken: authManager.generateRefreshToken(user),
114
+ };
115
+ }
116
+
117
+ if (isHtml) {
118
+ return res.redirect(redirectTo);
119
+ }
120
+
121
+ return res.status(200).json({
122
+ ok: true,
123
+ user,
124
+ ...(jwtTokens || {}),
125
+ });
126
+ } catch (err) {
127
+ if (isHtml) {
128
+ return res.redirect(failureRedirect);
129
+ }
130
+ return res.status(400).json({
131
+ error: err.message || 'Invalid or expired magic link',
132
+ code: err.code || 'INVALID_MAGIC_LINK',
133
+ });
134
+ }
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Express / Webspresso route handler for requesting a magic link
140
+ *
141
+ * @param {import('./manager').AuthManager} authManager
142
+ * @param {Object} [options]
143
+ * @returns {Function} Express route handler
144
+ */
145
+ function createMagicLinkRequestHandler(authManager, options = {}) {
146
+ return async function magicLinkRequestHandler(req, res) {
147
+ const identifier = req.body?.email || req.body?.identifier || req.query?.email;
148
+ if (!identifier) {
149
+ return res.status(400).json({ error: 'Email or identifier is required' });
150
+ }
151
+
152
+ try {
153
+ const result = await authManager.requestMagicLink(identifier, options);
154
+ return res.status(200).json({
155
+ ok: true,
156
+ message: 'If the account exists, a magic link has been sent.',
157
+ expiresAt: result.expiresAt,
158
+ });
159
+ } catch (err) {
160
+ return res.status(500).json({ error: err.message || 'Failed to send magic link' });
161
+ }
162
+ };
163
+ }
164
+
165
+ module.exports = {
166
+ buildMagicLinkUrl,
167
+ formatMagicLinkHtml,
168
+ createMagicLinkVerifyMiddleware,
169
+ createMagicLinkRequestHandler,
170
+ };