web-agent-bridge 1.0.0 → 1.1.1

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 (53) 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 +94 -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 +102 -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 +329 -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/routes/wab-api.js +476 -0
  43. package/server/services/email.js +204 -0
  44. package/server/services/fairness.js +420 -0
  45. package/server/services/premium.js +1680 -0
  46. package/server/services/stripe.js +192 -0
  47. package/server/utils/cache.js +125 -0
  48. package/server/utils/migrate.js +81 -0
  49. package/server/utils/secureFields.js +50 -0
  50. package/server/ws.js +33 -13
  51. package/wab-mcp-adapter/README.md +136 -0
  52. package/wab-mcp-adapter/index.js +555 -0
  53. 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,81 @@ 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 wabApiRoutes = require('./routes/wab-api');
24
+ const { handleWebhookRequest } = require('./services/stripe');
14
25
 
15
26
  const app = express();
16
27
  const PORT = process.env.PORT || 3000;
17
28
 
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());
29
+ app.set('trust proxy', 1);
30
+
31
+ const corsOrigins = (process.env.ALLOWED_ORIGINS
32
+ || 'http://localhost:3000,http://127.0.0.1:3000,http://localhost:5173')
33
+ .split(',')
34
+ .map((s) => s.trim())
35
+ .filter(Boolean);
36
+
37
+ app.use(
38
+ cors({
39
+ origin(origin, callback) {
40
+ if (!origin) return callback(null, true);
41
+ if (corsOrigins.includes(origin)) return callback(null, true);
42
+ if (process.env.NODE_ENV !== 'production' && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) {
43
+ return callback(null, true);
44
+ }
45
+ return callback(null, false);
46
+ },
47
+ credentials: true
48
+ })
49
+ );
50
+
51
+ const scriptSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
52
+ ? ["'self'"]
53
+ : ["'self'", "'unsafe-inline'"];
54
+ const styleSrc = process.env.CSP_ALLOW_UNSAFE_INLINE === 'false'
55
+ ? ["'self'"]
56
+ : ["'self'", "'unsafe-inline'"];
57
+
58
+ app.use(
59
+ helmet({
60
+ contentSecurityPolicy: {
61
+ directives: {
62
+ defaultSrc: ["'self'"],
63
+ scriptSrc,
64
+ styleSrc,
65
+ imgSrc: ["'self'", 'data:', 'https:'],
66
+ connectSrc: ["'self'", 'ws:', 'wss:'],
67
+ fontSrc: ["'self'", 'https:', 'data:'],
68
+ frameSrc: ["'none'"],
69
+ frameAncestors: ["'none'"],
70
+ objectSrc: ["'none'"],
71
+ baseUri: ["'self'"]
72
+ }
73
+ },
74
+ crossOriginEmbedderPolicy: false
75
+ })
76
+ );
77
+
78
+ app.post('/api/billing/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
79
+ try {
80
+ await handleWebhookRequest(req);
81
+ res.json({ received: true });
82
+ } catch (err) {
83
+ console.error('Webhook error:', err.message);
84
+ res.status(400).json({ error: err.message });
85
+ }
86
+ });
87
+
37
88
  app.use(express.json());
38
89
 
39
90
  const apiLimiter = rateLimit({
@@ -48,19 +99,25 @@ const licenseLimiter = rateLimit({
48
99
  windowMs: 60 * 1000,
49
100
  max: 120,
50
101
  standardHeaders: true,
51
- legacyHeaders: false
102
+ legacyHeaders: false,
103
+ keyGenerator: (req) => {
104
+ const key = req.body?.licenseKey || req.body?.siteId || req.ip;
105
+ return `${req.ip}:${key}`;
106
+ }
52
107
  });
53
108
 
54
- // ─── Static Files ───────────────────────────────────────────────────────
55
109
  app.use(express.static(path.join(__dirname, '..', 'public')));
56
110
  app.use('/script', express.static(path.join(__dirname, '..', 'script')));
57
111
 
58
- // ─── API Routes ─────────────────────────────────────────────────────────
59
112
  app.use('/api/auth', apiLimiter, authRoutes);
60
113
  app.use('/api', apiLimiter, apiRoutes);
61
114
  app.use('/api/license', licenseLimiter, licenseRoutes);
115
+ app.use('/api/admin', apiLimiter, adminRoutes);
116
+ app.use('/api/billing', apiLimiter, billingRoutes);
117
+ app.use('/api/noscript', noscriptRoutes);
118
+ app.use('/api/wab', wabApiRoutes);
119
+ app.use('/', discoveryRoutes);
62
120
 
63
- // ─── HTML Routes ────────────────────────────────────────────────────────
64
121
  app.get('/dashboard', (req, res) => {
65
122
  res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
66
123
  });
@@ -73,13 +130,29 @@ app.get('/login', (req, res) => {
73
130
  app.get('/register', (req, res) => {
74
131
  res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
75
132
  });
133
+ app.get('/admin/login', (req, res) => {
134
+ res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'login.html'));
135
+ });
136
+ app.get('/admin', (req, res) => {
137
+ res.sendFile(path.join(__dirname, '..', 'public', 'admin', 'dashboard.html'));
138
+ });
139
+ app.get('/premium', (req, res) => {
140
+ res.sendFile(path.join(__dirname, '..', 'public', 'premium.html'));
141
+ });
142
+ app.get('/privacy', (req, res) => {
143
+ res.sendFile(path.join(__dirname, '..', 'public', 'privacy.html'));
144
+ });
145
+ app.get('/terms', (req, res) => {
146
+ res.sendFile(path.join(__dirname, '..', 'public', 'terms.html'));
147
+ });
148
+ app.get('/cookies', (req, res) => {
149
+ res.sendFile(path.join(__dirname, '..', 'public', 'cookies.html'));
150
+ });
76
151
 
77
- // ─── CDN Versioned Script ───────────────────────────────────────────────
78
152
  const pkg = require('../package.json');
79
153
  app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
80
154
  app.use('/latest', express.static(path.join(__dirname, '..', 'script')));
81
155
 
82
- // ─── SPA Fallback ───────────────────────────────────────────────────────
83
156
  app.get('*', (req, res) => {
84
157
  if (req.accepts('html')) {
85
158
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
@@ -88,8 +161,11 @@ app.get('*', (req, res) => {
88
161
  }
89
162
  });
90
163
 
91
- // ─── Start ──────────────────────────────────────────────────────────────
92
164
  if (process.env.NODE_ENV !== 'test') {
165
+ console.log('Running database migrations...');
166
+ runMigrations();
167
+ maybeBootstrapAdmin();
168
+
93
169
  const server = http.createServer(app);
94
170
  setupWebSocket(server);
95
171
 
@@ -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);