web-agent-bridge 1.0.0 → 1.1.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.
Files changed (52) hide show
  1. package/README.ar.md +1 -1
  2. package/README.md +336 -36
  3. package/docs/DEPLOY.md +118 -0
  4. package/docs/SPEC.md +1540 -0
  5. package/examples/mcp-agent.js +85 -0
  6. package/examples/vision-agent.js +12 -0
  7. package/package.json +14 -3
  8. package/public/admin/dashboard.html +848 -0
  9. package/public/admin/login.html +84 -0
  10. package/public/cookies.html +208 -0
  11. package/public/css/premium.css +317 -0
  12. package/public/dashboard.html +138 -0
  13. package/public/docs.html +5 -2
  14. package/public/index.html +54 -28
  15. package/public/js/auth-nav.js +31 -0
  16. package/public/js/auth-redirect.js +12 -0
  17. package/public/js/cookie-consent.js +56 -0
  18. package/public/js/ws-client.js +74 -0
  19. package/public/login.html +4 -2
  20. package/public/premium-dashboard.html +2075 -0
  21. package/public/premium.html +791 -0
  22. package/public/privacy.html +295 -0
  23. package/public/register.html +11 -2
  24. package/public/terms.html +254 -0
  25. package/script/ai-agent-bridge.js +253 -22
  26. package/sdk/index.js +36 -0
  27. package/server/config/secrets.js +92 -0
  28. package/server/index.js +100 -26
  29. package/server/middleware/adminAuth.js +30 -0
  30. package/server/middleware/auth.js +4 -7
  31. package/server/middleware/rateLimits.js +24 -0
  32. package/server/migrations/001_add_analytics_indexes.sql +7 -0
  33. package/server/migrations/002_premium_features.sql +418 -0
  34. package/server/models/db.js +360 -4
  35. package/server/routes/admin.js +247 -0
  36. package/server/routes/api.js +26 -9
  37. package/server/routes/billing.js +45 -0
  38. package/server/routes/discovery.js +324 -0
  39. package/server/routes/license.js +200 -11
  40. package/server/routes/noscript.js +543 -0
  41. package/server/routes/premium.js +724 -0
  42. package/server/services/email.js +204 -0
  43. package/server/services/fairness.js +420 -0
  44. package/server/services/premium.js +1680 -0
  45. package/server/services/stripe.js +192 -0
  46. package/server/utils/cache.js +125 -0
  47. package/server/utils/migrate.js +81 -0
  48. package/server/utils/secureFields.js +50 -0
  49. package/server/ws.js +33 -13
  50. package/wab-mcp-adapter/README.md +136 -0
  51. package/wab-mcp-adapter/index.js +528 -0
  52. package/wab-mcp-adapter/package.json +17 -0
package/server/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  require('dotenv').config();
2
2
 
3
+ const { assertSecretsAtStartup } = require('./config/secrets');
4
+ assertSecretsAtStartup();
5
+
3
6
  const express = require('express');
4
7
  const http = require('http');
5
8
  const cors = require('cors');
@@ -7,33 +10,80 @@ const helmet = require('helmet');
7
10
  const rateLimit = require('express-rate-limit');
8
11
  const path = require('path');
9
12
  const { setupWebSocket } = require('./ws');
13
+ const { runMigrations } = require('./utils/migrate');
14
+ const { maybeBootstrapAdmin } = require('./models/db');
10
15
 
11
16
  const authRoutes = require('./routes/auth');
12
17
  const apiRoutes = require('./routes/api');
13
18
  const licenseRoutes = require('./routes/license');
19
+ const adminRoutes = require('./routes/admin');
20
+ const billingRoutes = require('./routes/billing');
21
+ const noscriptRoutes = require('./routes/noscript');
22
+ const discoveryRoutes = require('./routes/discovery');
23
+ const { handleWebhookRequest } = require('./services/stripe');
14
24
 
15
25
  const app = express();
16
26
  const PORT = process.env.PORT || 3000;
17
27
 
18
- // ─── Security & Middleware ──────────────────────────────────────────────
19
- app.use(helmet({
20
- contentSecurityPolicy: {
21
- directives: {
22
- defaultSrc: ["'self'"],
23
- scriptSrc: ["'self'", "'unsafe-inline'"],
24
- styleSrc: ["'self'", "'unsafe-inline'"],
25
- imgSrc: ["'self'", "data:", "https:"],
26
- connectSrc: ["'self'", "ws:", "wss:"],
27
- fontSrc: ["'self'", "https:", "data:"],
28
- frameSrc: ["'none'"],
29
- frameAncestors: ["'none'"],
30
- objectSrc: ["'none'"],
31
- baseUri: ["'self'"]
32
- }
33
- },
34
- crossOriginEmbedderPolicy: false
35
- }));
36
- app.use(cors());
28
+ app.set('trust proxy', 1);
29
+
30
+ const corsOrigins = (process.env.ALLOWED_ORIGINS
31
+ || 'http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173')
32
+ .split(',')
33
+ .map((s) => s.trim())
34
+ .filter(Boolean);
35
+
36
+ app.use(
37
+ cors({
38
+ origin(origin, callback) {
39
+ if (!origin) return callback(null, true);
40
+ if (corsOrigins.includes(origin)) return callback(null, true);
41
+ if (process.env.NODE_ENV !== 'production' && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
42
+ return callback(null, true);
43
+ }
44
+ return callback(null, false);
45
+ },
46
+ credentials: true
47
+ })
48
+ );
49
+
50
+ const scriptSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
51
+ ? ["'self'"]
52
+ : ["'self'", "'unsafe-inline'"];
53
+ const styleSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
54
+ ? ["'self'"]
55
+ : ["'self'", "'unsafe-inline'"];
56
+
57
+ app.use(
58
+ helmet({
59
+ contentSecurityPolicy: {
60
+ directives: {
61
+ defaultSrc: ["'self'"],
62
+ scriptSrc,
63
+ styleSrc,
64
+ imgSrc: ["'self'", 'data:', 'https:'],
65
+ connectSrc: ["'self'", 'ws:', 'wss:'],
66
+ fontSrc: ["'self'", 'https:', 'data:'],
67
+ frameSrc: ["'none'"],
68
+ frameAncestors: ["'none'"],
69
+ objectSrc: ["'none'"],
70
+ baseUri: ["'self'"]
71
+ }
72
+ },
73
+ crossOriginEmbedderPolicy: false
74
+ })
75
+ );
76
+
77
+ app.post('/api/billing/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
78
+ try {
79
+ await handleWebhookRequest(req);
80
+ res.json({ received: true });
81
+ } catch (err) {
82
+ console.error('Webhook error:', err.message);
83
+ res.status(400).json({ error: err.message });
84
+ }
85
+ });
86
+
37
87
  app.use(express.json());
38
88
 
39
89
  const apiLimiter = rateLimit({
@@ -48,19 +98,24 @@ const licenseLimiter = rateLimit({
48
98
  windowMs: 60 * 1000,
49
99
  max: 120,
50
100
  standardHeaders: true,
51
- legacyHeaders: false
101
+ legacyHeaders: false,
102
+ keyGenerator: (req) => {
103
+ const key = req.body?.licenseKey || req.body?.siteId || req.ip;
104
+ return `${req.ip}:${key}`;
105
+ }
52
106
  });
53
107
 
54
- // ─── Static Files ───────────────────────────────────────────────────────
55
108
  app.use(express.static(path.join(__dirname, '..', 'public')));
56
109
  app.use('/script', express.static(path.join(__dirname, '..', 'script')));
57
110
 
58
- // ─── API Routes ─────────────────────────────────────────────────────────
59
111
  app.use('/api/auth', apiLimiter, authRoutes);
60
112
  app.use('/api', apiLimiter, apiRoutes);
61
113
  app.use('/api/license', licenseLimiter, licenseRoutes);
114
+ app.use('/api/admin', apiLimiter, adminRoutes);
115
+ app.use('/api/billing', apiLimiter, billingRoutes);
116
+ app.use('/api/noscript', noscriptRoutes);
117
+ app.use('/', discoveryRoutes);
62
118
 
63
- // ─── HTML Routes ────────────────────────────────────────────────────────
64
119
  app.get('/dashboard', (req, res) => {
65
120
  res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
66
121
  });
@@ -73,13 +128,29 @@ app.get('/login', (req, res) => {
73
128
  app.get('/register', (req, res) => {
74
129
  res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
75
130
  });
131
+ app.get('/admin/login', (req, res) => {
132
+ res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'login.html'));
133
+ });
134
+ app.get('/admin', (req, res) => {
135
+ res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'dashboard.html'));
136
+ });
137
+ app.get('/premium', (req, res) => {
138
+ res.sendFile(path.join(__dirname, '..', 'public', 'premium.html'));
139
+ });
140
+ app.get('/privacy', (req, res) => {
141
+ res.sendFile(path.join(__dirname, '..', 'public', 'privacy.html'));
142
+ });
143
+ app.get('/terms', (req, res) => {
144
+ res.sendFile(path.join(__dirname, '..', 'public', 'terms.html'));
145
+ });
146
+ app.get('/cookies', (req, res) => {
147
+ res.sendFile(path.join(__dirname, '..', 'public', 'cookies.html'));
148
+ });
76
149
 
77
- // ─── CDN Versioned Script ───────────────────────────────────────────────
78
150
  const pkg = require('../package.json');
79
151
  app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
80
152
  app.use('/latest', express.static(path.join(__dirname, '..', 'script')));
81
153
 
82
- // ─── SPA Fallback ───────────────────────────────────────────────────────
83
154
  app.get('*', (req, res) => {
84
155
  if (req.accepts('html')) {
85
156
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
@@ -88,8 +159,11 @@ app.get('*', (req, res) => {
88
159
  }
89
160
  });
90
161
 
91
- // ─── Start ──────────────────────────────────────────────────────────────
92
162
  if (process.env.NODE_ENV !== 'test') {
163
+ console.log('Running database migrations...');
164
+ runMigrations();
165
+ maybeBootstrapAdmin();
166
+
93
167
  const server = http.createServer(app);
94
168
  setupWebSocket(server);
95
169
 
@@ -0,0 +1,30 @@
1
+ const { signAdminToken, verifyAdminToken } = require('../config/secrets');
2
+
3
+ function generateAdminToken(admin) {
4
+ return signAdminToken(
5
+ { id: admin.id, email: admin.email, name: admin.name, role: admin.role, isAdmin: true },
6
+ { expiresIn: '12h' }
7
+ );
8
+ }
9
+
10
+ function authenticateAdmin(req, res, next) {
11
+ const authHeader = req.headers['authorization'];
12
+ const token = authHeader && authHeader.split(' ')[1];
13
+
14
+ if (!token) {
15
+ return res.status(401).json({ error: 'Admin access token required' });
16
+ }
17
+
18
+ try {
19
+ const decoded = verifyAdminToken(token);
20
+ if (!decoded.isAdmin) {
21
+ return res.status(403).json({ error: 'Admin privileges required' });
22
+ }
23
+ req.admin = decoded;
24
+ next();
25
+ } catch (err) {
26
+ return res.status(403).json({ error: 'Invalid or expired admin token' });
27
+ }
28
+ }
29
+
30
+ module.exports = { generateAdminToken, authenticateAdmin };
@@ -1,11 +1,8 @@
1
- const jwt = require('jsonwebtoken');
2
-
3
- const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';
1
+ const { signUserToken, verifyUserToken } = require('../config/secrets');
4
2
 
5
3
  function generateToken(user) {
6
- return jwt.sign(
4
+ return signUserToken(
7
5
  { id: user.id, email: user.email, name: user.name },
8
- JWT_SECRET,
9
6
  { expiresIn: '7d' }
10
7
  );
11
8
  }
@@ -19,7 +16,7 @@ function authenticateToken(req, res, next) {
19
16
  }
20
17
 
21
18
  try {
22
- const decoded = jwt.verify(token, JWT_SECRET);
19
+ const decoded = verifyUserToken(token);
23
20
  req.user = decoded;
24
21
  next();
25
22
  } catch (err) {
@@ -33,7 +30,7 @@ function optionalAuth(req, res, next) {
33
30
 
34
31
  if (token) {
35
32
  try {
36
- req.user = jwt.verify(token, JWT_SECRET);
33
+ req.user = verifyUserToken(token);
37
34
  } catch (e) {
38
35
  // ignore invalid tokens for optional auth
39
36
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Stricter rate limits for license token / track endpoints (used inside license router).
3
+ */
4
+
5
+ const rateLimit = require('express-rate-limit');
6
+
7
+ const licenseTokenLimiter = rateLimit({
8
+ windowMs: 15 * 60 * 1000,
9
+ max: 30,
10
+ standardHeaders: true,
11
+ legacyHeaders: false,
12
+ message: { error: 'Too many token requests, please try again later' }
13
+ });
14
+
15
+ const licenseTrackLimiter = rateLimit({
16
+ windowMs: 60 * 1000,
17
+ max: 300,
18
+ standardHeaders: true,
19
+ legacyHeaders: false,
20
+ keyGenerator: (req) => `${req.ip}:${req.body?.sessionToken || req.body?.siteId || 'anon'}`,
21
+ message: { error: 'Too many track requests, please try again later' }
22
+ });
23
+
24
+ module.exports = { licenseTokenLimiter, licenseTrackLimiter };
@@ -0,0 +1,7 @@
1
+ -- Migration 001: Add composite indexes for analytics performance
2
+ -- Created: 2024-12-01
3
+
4
+ CREATE INDEX IF NOT EXISTS idx_analytics_site_action ON analytics(site_id, action_name);
5
+ CREATE INDEX IF NOT EXISTS idx_analytics_site_created ON analytics(site_id, created_at);
6
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_user ON subscriptions(user_id);
7
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);