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.
- package/README.ar.md +1 -1
- package/README.md +336 -36
- package/docs/DEPLOY.md +118 -0
- package/docs/SPEC.md +1540 -0
- package/examples/mcp-agent.js +94 -0
- package/examples/vision-agent.js +12 -0
- package/package.json +14 -3
- package/public/admin/dashboard.html +848 -0
- package/public/admin/login.html +84 -0
- package/public/cookies.html +208 -0
- package/public/css/premium.css +317 -0
- package/public/dashboard.html +138 -0
- package/public/docs.html +5 -2
- package/public/index.html +54 -28
- package/public/js/auth-nav.js +31 -0
- package/public/js/auth-redirect.js +12 -0
- package/public/js/cookie-consent.js +56 -0
- package/public/js/ws-client.js +74 -0
- package/public/login.html +4 -2
- package/public/premium-dashboard.html +2075 -0
- package/public/premium.html +791 -0
- package/public/privacy.html +295 -0
- package/public/register.html +11 -2
- package/public/terms.html +254 -0
- package/script/ai-agent-bridge.js +253 -22
- package/sdk/index.js +36 -0
- package/server/config/secrets.js +92 -0
- package/server/index.js +102 -26
- package/server/middleware/adminAuth.js +30 -0
- package/server/middleware/auth.js +4 -7
- package/server/middleware/rateLimits.js +24 -0
- package/server/migrations/001_add_analytics_indexes.sql +7 -0
- package/server/migrations/002_premium_features.sql +418 -0
- package/server/models/db.js +360 -4
- package/server/routes/admin.js +247 -0
- package/server/routes/api.js +26 -9
- package/server/routes/billing.js +45 -0
- package/server/routes/discovery.js +329 -0
- package/server/routes/license.js +200 -11
- package/server/routes/noscript.js +543 -0
- package/server/routes/premium.js +724 -0
- package/server/routes/wab-api.js +476 -0
- package/server/services/email.js +204 -0
- package/server/services/fairness.js +420 -0
- package/server/services/premium.js +1680 -0
- package/server/services/stripe.js +192 -0
- package/server/utils/cache.js +125 -0
- package/server/utils/migrate.js +81 -0
- package/server/utils/secureFields.js +50 -0
- package/server/ws.js +33 -13
- package/wab-mcp-adapter/README.md +136 -0
- package/wab-mcp-adapter/index.js +555 -0
- 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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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);
|