web-agent-bridge 1.1.1 → 1.1.2
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/LICENSE +21 -21
- package/README.ar.md +446 -446
- package/README.md +844 -844
- package/bin/cli.js +80 -80
- package/bin/wab.js +80 -80
- package/docs/DEPLOY.md +118 -118
- package/docs/SPEC.md +1540 -1540
- package/examples/bidi-agent.js +119 -119
- package/examples/mcp-agent.js +94 -94
- package/examples/puppeteer-agent.js +108 -108
- package/examples/vision-agent.js +171 -171
- package/package.json +78 -78
- package/public/admin/dashboard.html +848 -848
- package/public/admin/login.html +84 -84
- package/public/cookies.html +208 -208
- package/public/css/styles.css +1235 -1235
- package/public/dashboard.html +704 -704
- package/public/docs.html +585 -585
- package/public/index.html +332 -332
- package/public/js/auth-nav.js +31 -31
- package/public/js/auth-redirect.js +12 -12
- package/public/js/cookie-consent.js +56 -56
- package/public/js/ws-client.js +74 -74
- package/public/login.html +83 -83
- package/public/privacy.html +295 -295
- package/public/register.html +103 -103
- package/public/terms.html +254 -254
- package/script/ai-agent-bridge.js +1513 -1513
- package/sdk/README.md +55 -55
- package/sdk/index.js +203 -203
- package/sdk/package.json +14 -14
- package/server/config/secrets.js +92 -92
- package/server/index.js +181 -181
- package/server/middleware/adminAuth.js +30 -30
- package/server/middleware/auth.js +41 -41
- package/server/middleware/rateLimits.js +24 -24
- package/server/migrations/001_add_analytics_indexes.sql +7 -7
- package/server/models/adapters/index.js +33 -33
- package/server/models/adapters/mysql.js +183 -183
- package/server/models/adapters/postgresql.js +172 -172
- package/server/models/adapters/sqlite.js +7 -7
- package/server/models/db.js +561 -561
- package/server/routes/admin.js +247 -247
- package/server/routes/api.js +138 -138
- package/server/routes/auth.js +51 -51
- package/server/routes/billing.js +45 -45
- package/server/routes/discovery.js +329 -329
- package/server/routes/license.js +240 -240
- package/server/routes/noscript.js +543 -543
- package/server/routes/wab-api.js +476 -476
- package/server/services/email.js +204 -204
- package/server/services/fairness.js +420 -420
- package/server/services/stripe.js +192 -192
- package/server/utils/cache.js +125 -125
- package/server/utils/migrate.js +81 -81
- package/server/utils/secureFields.js +50 -50
- package/server/ws.js +101 -101
- package/wab-mcp-adapter/README.md +136 -136
- package/wab-mcp-adapter/index.js +555 -555
- package/wab-mcp-adapter/package.json +17 -17
- package/public/css/premium.css +0 -317
- package/public/premium-dashboard.html +0 -2075
- package/public/premium.html +0 -791
- package/server/migrations/002_premium_features.sql +0 -418
- package/server/routes/premium.js +0 -724
- package/server/services/premium.js +0 -1680
package/server/config/secrets.js
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Central JWT and startup secret checks.
|
|
3
|
-
* User tokens and admin tokens use different secrets and audiences in production.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const jwt = require('jsonwebtoken');
|
|
7
|
-
|
|
8
|
-
const JWT_ISSUER = 'wab';
|
|
9
|
-
const JWT_AUD_USER = 'wab:user';
|
|
10
|
-
const JWT_AUD_ADMIN = 'wab:admin';
|
|
11
|
-
|
|
12
|
-
const jwtVerifyUser = { issuer: JWT_ISSUER, audience: JWT_AUD_USER };
|
|
13
|
-
const jwtVerifyAdmin = { issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN };
|
|
14
|
-
|
|
15
|
-
function isTest() {
|
|
16
|
-
return process.env.NODE_ENV === 'test';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function isProd() {
|
|
20
|
-
return process.env.NODE_ENV === 'production';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function assertSecretsAtStartup() {
|
|
24
|
-
if (isTest()) return;
|
|
25
|
-
if (isProd()) {
|
|
26
|
-
if (!process.env.JWT_SECRET) {
|
|
27
|
-
throw new Error('FATAL: JWT_SECRET is required in production');
|
|
28
|
-
}
|
|
29
|
-
if (!process.env.JWT_SECRET_ADMIN) {
|
|
30
|
-
throw new Error('FATAL: JWT_SECRET_ADMIN is required in production');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getJwtUserSecret() {
|
|
36
|
-
if (isTest()) {
|
|
37
|
-
return process.env.JWT_SECRET || 'test-secret-key-for-testing';
|
|
38
|
-
}
|
|
39
|
-
if (isProd()) {
|
|
40
|
-
return process.env.JWT_SECRET;
|
|
41
|
-
}
|
|
42
|
-
return process.env.JWT_SECRET || 'dev-user-secret-change-in-development';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getJwtAdminSecret() {
|
|
46
|
-
if (isTest()) {
|
|
47
|
-
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'test-secret-key-for-testing-admin';
|
|
48
|
-
}
|
|
49
|
-
if (isProd()) {
|
|
50
|
-
return process.env.JWT_SECRET_ADMIN;
|
|
51
|
-
}
|
|
52
|
-
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'dev-admin-secret-change-in-development';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function signUserToken(payload, options = {}) {
|
|
56
|
-
return jwt.sign(
|
|
57
|
-
{ ...payload },
|
|
58
|
-
getJwtUserSecret(),
|
|
59
|
-
{ expiresIn: options.expiresIn || '7d', issuer: JWT_ISSUER, audience: JWT_AUD_USER }
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function signAdminToken(payload, options = {}) {
|
|
64
|
-
return jwt.sign(
|
|
65
|
-
{ ...payload },
|
|
66
|
-
getJwtAdminSecret(),
|
|
67
|
-
{ expiresIn: options.expiresIn || '12h', issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN }
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function verifyUserToken(token) {
|
|
72
|
-
return jwt.verify(token, getJwtUserSecret(), jwtVerifyUser);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function verifyAdminToken(token) {
|
|
76
|
-
return jwt.verify(token, getJwtAdminSecret(), jwtVerifyAdmin);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = {
|
|
80
|
-
assertSecretsAtStartup,
|
|
81
|
-
getJwtUserSecret,
|
|
82
|
-
getJwtAdminSecret,
|
|
83
|
-
signUserToken,
|
|
84
|
-
signAdminToken,
|
|
85
|
-
verifyUserToken,
|
|
86
|
-
verifyAdminToken,
|
|
87
|
-
JWT_ISSUER,
|
|
88
|
-
JWT_AUD_USER,
|
|
89
|
-
JWT_AUD_ADMIN,
|
|
90
|
-
jwtVerifyUser,
|
|
91
|
-
jwtVerifyAdmin
|
|
92
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Central JWT and startup secret checks.
|
|
3
|
+
* User tokens and admin tokens use different secrets and audiences in production.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const jwt = require('jsonwebtoken');
|
|
7
|
+
|
|
8
|
+
const JWT_ISSUER = 'wab';
|
|
9
|
+
const JWT_AUD_USER = 'wab:user';
|
|
10
|
+
const JWT_AUD_ADMIN = 'wab:admin';
|
|
11
|
+
|
|
12
|
+
const jwtVerifyUser = { issuer: JWT_ISSUER, audience: JWT_AUD_USER };
|
|
13
|
+
const jwtVerifyAdmin = { issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN };
|
|
14
|
+
|
|
15
|
+
function isTest() {
|
|
16
|
+
return process.env.NODE_ENV === 'test';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isProd() {
|
|
20
|
+
return process.env.NODE_ENV === 'production';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertSecretsAtStartup() {
|
|
24
|
+
if (isTest()) return;
|
|
25
|
+
if (isProd()) {
|
|
26
|
+
if (!process.env.JWT_SECRET) {
|
|
27
|
+
throw new Error('FATAL: JWT_SECRET is required in production');
|
|
28
|
+
}
|
|
29
|
+
if (!process.env.JWT_SECRET_ADMIN) {
|
|
30
|
+
throw new Error('FATAL: JWT_SECRET_ADMIN is required in production');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getJwtUserSecret() {
|
|
36
|
+
if (isTest()) {
|
|
37
|
+
return process.env.JWT_SECRET || 'test-secret-key-for-testing';
|
|
38
|
+
}
|
|
39
|
+
if (isProd()) {
|
|
40
|
+
return process.env.JWT_SECRET;
|
|
41
|
+
}
|
|
42
|
+
return process.env.JWT_SECRET || 'dev-user-secret-change-in-development';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getJwtAdminSecret() {
|
|
46
|
+
if (isTest()) {
|
|
47
|
+
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'test-secret-key-for-testing-admin';
|
|
48
|
+
}
|
|
49
|
+
if (isProd()) {
|
|
50
|
+
return process.env.JWT_SECRET_ADMIN;
|
|
51
|
+
}
|
|
52
|
+
return process.env.JWT_SECRET_ADMIN || process.env.JWT_SECRET || 'dev-admin-secret-change-in-development';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function signUserToken(payload, options = {}) {
|
|
56
|
+
return jwt.sign(
|
|
57
|
+
{ ...payload },
|
|
58
|
+
getJwtUserSecret(),
|
|
59
|
+
{ expiresIn: options.expiresIn || '7d', issuer: JWT_ISSUER, audience: JWT_AUD_USER }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function signAdminToken(payload, options = {}) {
|
|
64
|
+
return jwt.sign(
|
|
65
|
+
{ ...payload },
|
|
66
|
+
getJwtAdminSecret(),
|
|
67
|
+
{ expiresIn: options.expiresIn || '12h', issuer: JWT_ISSUER, audience: JWT_AUD_ADMIN }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function verifyUserToken(token) {
|
|
72
|
+
return jwt.verify(token, getJwtUserSecret(), jwtVerifyUser);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function verifyAdminToken(token) {
|
|
76
|
+
return jwt.verify(token, getJwtAdminSecret(), jwtVerifyAdmin);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
assertSecretsAtStartup,
|
|
81
|
+
getJwtUserSecret,
|
|
82
|
+
getJwtAdminSecret,
|
|
83
|
+
signUserToken,
|
|
84
|
+
signAdminToken,
|
|
85
|
+
verifyUserToken,
|
|
86
|
+
verifyAdminToken,
|
|
87
|
+
JWT_ISSUER,
|
|
88
|
+
JWT_AUD_USER,
|
|
89
|
+
JWT_AUD_ADMIN,
|
|
90
|
+
jwtVerifyUser,
|
|
91
|
+
jwtVerifyAdmin
|
|
92
|
+
};
|
package/server/index.js
CHANGED
|
@@ -1,181 +1,181 @@
|
|
|
1
|
-
require('dotenv').config();
|
|
2
|
-
|
|
3
|
-
const { assertSecretsAtStartup } = require('./config/secrets');
|
|
4
|
-
assertSecretsAtStartup();
|
|
5
|
-
|
|
6
|
-
const express = require('express');
|
|
7
|
-
const http = require('http');
|
|
8
|
-
const cors = require('cors');
|
|
9
|
-
const helmet = require('helmet');
|
|
10
|
-
const rateLimit = require('express-rate-limit');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
const { setupWebSocket } = require('./ws');
|
|
13
|
-
const { runMigrations } = require('./utils/migrate');
|
|
14
|
-
const { maybeBootstrapAdmin } = require('./models/db');
|
|
15
|
-
|
|
16
|
-
const authRoutes = require('./routes/auth');
|
|
17
|
-
const apiRoutes = require('./routes/api');
|
|
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');
|
|
25
|
-
|
|
26
|
-
const app = express();
|
|
27
|
-
const PORT = process.env.PORT || 3000;
|
|
28
|
-
|
|
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
|
-
|
|
88
|
-
app.use(express.json());
|
|
89
|
-
|
|
90
|
-
const apiLimiter = rateLimit({
|
|
91
|
-
windowMs: 15 * 60 * 1000,
|
|
92
|
-
max: 200,
|
|
93
|
-
standardHeaders: true,
|
|
94
|
-
legacyHeaders: false,
|
|
95
|
-
message: { error: 'Too many requests, please try again later' }
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const licenseLimiter = rateLimit({
|
|
99
|
-
windowMs: 60 * 1000,
|
|
100
|
-
max: 120,
|
|
101
|
-
standardHeaders: true,
|
|
102
|
-
legacyHeaders: false,
|
|
103
|
-
keyGenerator: (req) => {
|
|
104
|
-
const key = req.body?.licenseKey || req.body?.siteId || req.ip;
|
|
105
|
-
return `${req.ip}:${key}`;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
app.use(express.static(path.join(__dirname, '..', 'public')));
|
|
110
|
-
app.use('/script', express.static(path.join(__dirname, '..', 'script')));
|
|
111
|
-
|
|
112
|
-
app.use('/api/auth', apiLimiter, authRoutes);
|
|
113
|
-
app.use('/api', apiLimiter, apiRoutes);
|
|
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);
|
|
120
|
-
|
|
121
|
-
app.get('/dashboard', (req, res) => {
|
|
122
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
|
|
123
|
-
});
|
|
124
|
-
app.get('/docs', (req, res) => {
|
|
125
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'docs.html'));
|
|
126
|
-
});
|
|
127
|
-
app.get('/login', (req, res) => {
|
|
128
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'login.html'));
|
|
129
|
-
});
|
|
130
|
-
app.get('/register', (req, res) => {
|
|
131
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
|
|
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
|
-
});
|
|
151
|
-
|
|
152
|
-
const pkg = require('../package.json');
|
|
153
|
-
app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
|
|
154
|
-
app.use('/latest', express.static(path.join(__dirname, '..', 'script')));
|
|
155
|
-
|
|
156
|
-
app.get('*', (req, res) => {
|
|
157
|
-
if (req.accepts('html')) {
|
|
158
|
-
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
|
|
159
|
-
} else {
|
|
160
|
-
res.status(404).json({ error: 'Not found' });
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
if (process.env.NODE_ENV !== 'test') {
|
|
165
|
-
console.log('Running database migrations...');
|
|
166
|
-
runMigrations();
|
|
167
|
-
maybeBootstrapAdmin();
|
|
168
|
-
|
|
169
|
-
const server = http.createServer(app);
|
|
170
|
-
setupWebSocket(server);
|
|
171
|
-
|
|
172
|
-
server.listen(PORT, () => {
|
|
173
|
-
console.log(`\n ╔══════════════════════════════════════════╗`);
|
|
174
|
-
console.log(` ║ Web Agent Bridge v${pkg.version} ║`);
|
|
175
|
-
console.log(` ║ Server running on http://localhost:${PORT} ║`);
|
|
176
|
-
console.log(` ║ WebSocket: ws://localhost:${PORT}/ws/analytics ║`);
|
|
177
|
-
console.log(` ╚══════════════════════════════════════════╝\n`);
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
module.exports = app;
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
|
|
3
|
+
const { assertSecretsAtStartup } = require('./config/secrets');
|
|
4
|
+
assertSecretsAtStartup();
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const cors = require('cors');
|
|
9
|
+
const helmet = require('helmet');
|
|
10
|
+
const rateLimit = require('express-rate-limit');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { setupWebSocket } = require('./ws');
|
|
13
|
+
const { runMigrations } = require('./utils/migrate');
|
|
14
|
+
const { maybeBootstrapAdmin } = require('./models/db');
|
|
15
|
+
|
|
16
|
+
const authRoutes = require('./routes/auth');
|
|
17
|
+
const apiRoutes = require('./routes/api');
|
|
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');
|
|
25
|
+
|
|
26
|
+
const app = express();
|
|
27
|
+
const PORT = process.env.PORT || 3000;
|
|
28
|
+
|
|
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
|
+
|
|
88
|
+
app.use(express.json());
|
|
89
|
+
|
|
90
|
+
const apiLimiter = rateLimit({
|
|
91
|
+
windowMs: 15 * 60 * 1000,
|
|
92
|
+
max: 200,
|
|
93
|
+
standardHeaders: true,
|
|
94
|
+
legacyHeaders: false,
|
|
95
|
+
message: { error: 'Too many requests, please try again later' }
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const licenseLimiter = rateLimit({
|
|
99
|
+
windowMs: 60 * 1000,
|
|
100
|
+
max: 120,
|
|
101
|
+
standardHeaders: true,
|
|
102
|
+
legacyHeaders: false,
|
|
103
|
+
keyGenerator: (req) => {
|
|
104
|
+
const key = req.body?.licenseKey || req.body?.siteId || req.ip;
|
|
105
|
+
return `${req.ip}:${key}`;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
app.use(express.static(path.join(__dirname, '..', 'public')));
|
|
110
|
+
app.use('/script', express.static(path.join(__dirname, '..', 'script')));
|
|
111
|
+
|
|
112
|
+
app.use('/api/auth', apiLimiter, authRoutes);
|
|
113
|
+
app.use('/api', apiLimiter, apiRoutes);
|
|
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);
|
|
120
|
+
|
|
121
|
+
app.get('/dashboard', (req, res) => {
|
|
122
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'dashboard.html'));
|
|
123
|
+
});
|
|
124
|
+
app.get('/docs', (req, res) => {
|
|
125
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'docs.html'));
|
|
126
|
+
});
|
|
127
|
+
app.get('/login', (req, res) => {
|
|
128
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'login.html'));
|
|
129
|
+
});
|
|
130
|
+
app.get('/register', (req, res) => {
|
|
131
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'register.html'));
|
|
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
|
+
});
|
|
151
|
+
|
|
152
|
+
const pkg = require('../package.json');
|
|
153
|
+
app.use(`/v${pkg.version.split('.')[0]}`, express.static(path.join(__dirname, '..', 'script')));
|
|
154
|
+
app.use('/latest', express.static(path.join(__dirname, '..', 'script')));
|
|
155
|
+
|
|
156
|
+
app.get('*', (req, res) => {
|
|
157
|
+
if (req.accepts('html')) {
|
|
158
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
|
|
159
|
+
} else {
|
|
160
|
+
res.status(404).json({ error: 'Not found' });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
165
|
+
console.log('Running database migrations...');
|
|
166
|
+
runMigrations();
|
|
167
|
+
maybeBootstrapAdmin();
|
|
168
|
+
|
|
169
|
+
const server = http.createServer(app);
|
|
170
|
+
setupWebSocket(server);
|
|
171
|
+
|
|
172
|
+
server.listen(PORT, () => {
|
|
173
|
+
console.log(`\n ╔══════════════════════════════════════════╗`);
|
|
174
|
+
console.log(` ║ Web Agent Bridge v${pkg.version} ║`);
|
|
175
|
+
console.log(` ║ Server running on http://localhost:${PORT} ║`);
|
|
176
|
+
console.log(` ║ WebSocket: ws://localhost:${PORT}/ws/analytics ║`);
|
|
177
|
+
console.log(` ╚══════════════════════════════════════════╝\n`);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = app;
|
|
@@ -1,30 +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
|
+
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 };
|