hookarmor 1.0.0 ā 1.0.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.md +22 -0
- package/bin/hookarmor.js +5 -4
- package/package.json +2 -2
- package/src/public/landing.html +5 -5
- package/src/server.js +14 -9
package/README.md
CHANGED
|
@@ -169,5 +169,27 @@ HookArmor solves this automatically:
|
|
|
169
169
|
|
|
170
170
|
---
|
|
171
171
|
|
|
172
|
+
## š Production Security & Admin Authentication
|
|
173
|
+
|
|
174
|
+
When running HookArmor locally on your laptop (`localhost`), management endpoints and the replay dashboard operate without authentication for fast developer onboarding.
|
|
175
|
+
|
|
176
|
+
> [!WARNING]
|
|
177
|
+
> **When deploying HookArmor to a public server or self-hosted cloud container (Railway, Render, VPS), you MUST set the `HOOKARMOR_API_KEY` environment variable.**
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
# Generate a strong 256-bit random key
|
|
181
|
+
openssl rand -hex 32
|
|
182
|
+
|
|
183
|
+
# Set in your production environment / Docker container:
|
|
184
|
+
HOOKARMOR_API_KEY=ha_sec_your_secure_random_key_here
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
When `HOOKARMOR_API_KEY` is present:
|
|
188
|
+
* Management endpoints (`/api/endpoints`, `/api/events/replay-all`, and waitlist exports) strictly reject unauthenticated requests and require an `Authorization: Bearer <key>` header.
|
|
189
|
+
* Query parameter authentication (`?api_key=...`) is strictly prohibited to avoid leaking tokens into browser history and proxy access logs.
|
|
190
|
+
* The web dashboard displays an **Admin Login** prompt storing your key only in ephemeral session memory.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
172
194
|
## š License
|
|
173
195
|
MIT License. Created by the HookArmor Team.
|
package/bin/hookarmor.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { Command } = require('commander');
|
|
4
|
-
const
|
|
4
|
+
const _chalk = require('chalk');
|
|
5
|
+
const chalk = _chalk.default || _chalk;
|
|
5
6
|
const { createServer } = require('../src/server');
|
|
6
7
|
const http = require('http');
|
|
7
8
|
|
|
@@ -34,7 +35,7 @@ program
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
server.listen(port, () => {
|
|
37
|
-
console.log(chalk.bold
|
|
38
|
+
console.log(chalk.blue.bold('\nš”ļø HookArmor Webhook Sentinel is LIVE!'));
|
|
38
39
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
39
40
|
console.log(`š” Ingress Gateway : ${chalk.cyan(`http://localhost:${port}/in/:endpointId`)}`);
|
|
40
41
|
console.log(`š Web Dashboard : ${chalk.green.bold(`http://localhost:${port}`)}`);
|
|
@@ -73,7 +74,7 @@ program
|
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
server.listen(port, () => {
|
|
76
|
-
console.log(chalk.bold
|
|
77
|
+
console.log(chalk.cyan.bold(`\nā” HookArmor Listen Active`));
|
|
77
78
|
console.log(`Forwarding: ${chalk.yellow(`http://localhost:${port}/in/${options.endpoint}`)} ā ${chalk.green(targetUrl)}`);
|
|
78
79
|
console.log(`Dashboard: ${chalk.cyan(`http://localhost:${port}`)}\n`);
|
|
79
80
|
});
|
|
@@ -117,7 +118,7 @@ program
|
|
|
117
118
|
try {
|
|
118
119
|
const res = await fetch(`${baseUrl}/api/stats`);
|
|
119
120
|
const data = await res.json();
|
|
120
|
-
console.log(chalk.bold
|
|
121
|
+
console.log(chalk.blue.bold('\nš”ļø HookArmor Metrics'));
|
|
121
122
|
console.log(chalk.gray('āāāāāāāāāāāāāāāāāāāāāāā'));
|
|
122
123
|
console.log(`Total Ingested : ${chalk.cyan(data.total)}`);
|
|
123
124
|
console.log(`Delivered : ${chalk.green(data.delivered)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hookarmor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Zero-loss Webhook Dead-Letter Queue (DLQ), Reliability Proxy, and Replay Gateway for Stripe, Shopify, Clerk, and B2B SaaS.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "https://github.com/pkdoddamani/hookarmor.git"
|
|
21
|
+
"url": "git+https://github.com/pkdoddamani/hookarmor.git"
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://hookarmour.dev",
|
|
24
24
|
"bugs": {
|
package/src/public/landing.html
CHANGED
|
@@ -371,13 +371,13 @@
|
|
|
371
371
|
</div>
|
|
372
372
|
</div>
|
|
373
373
|
|
|
374
|
-
<div class="terminal-box" style="flex-direction: column; align-items: flex-start; gap: 0.5rem; width: 100%; max-width:
|
|
374
|
+
<div class="terminal-box" style="flex-direction: column; align-items: flex-start; gap: 0.5rem; width: 100%; max-width: 520px;">
|
|
375
375
|
<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;">
|
|
376
|
-
<span style="font-size: 0.75rem; color: #
|
|
377
|
-
<button class="copy-btn" onclick="navigator.clipboard.writeText('
|
|
376
|
+
<span style="font-size: 0.75rem; color: #10b981; text-transform: uppercase; letter-spacing: 0.05em; font-family: var(--font-sans); font-weight: 700;">ā Verified on npm (Zero Config)</span>
|
|
377
|
+
<button class="copy-btn" onclick="navigator.clipboard.writeText('npx hookarmor start')">Copy</button>
|
|
378
378
|
</div>
|
|
379
|
-
<div style="color: #38bdf8; font-size:
|
|
380
|
-
$
|
|
379
|
+
<div style="color: #38bdf8; font-size: 1.05rem; font-weight: 600;">
|
|
380
|
+
$ npx hookarmor start
|
|
381
381
|
</div>
|
|
382
382
|
</div>
|
|
383
383
|
|
package/src/server.js
CHANGED
|
@@ -322,39 +322,44 @@ function createServer(options = {}) {
|
|
|
322
322
|
// SEO & Web Crawler Discovery
|
|
323
323
|
app.get('/robots.txt', (req, res) => {
|
|
324
324
|
res.type('text/plain');
|
|
325
|
-
res.sendFile(path.join(__dirname, 'public'
|
|
325
|
+
res.sendFile('robots.txt', { root: path.join(__dirname, 'public') });
|
|
326
326
|
});
|
|
327
327
|
|
|
328
328
|
app.get('/sitemap.xml', (req, res) => {
|
|
329
329
|
res.type('application/xml');
|
|
330
|
-
res.sendFile(path.join(__dirname, 'public'
|
|
330
|
+
res.sendFile('sitemap.xml', { root: path.join(__dirname, 'public') });
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
app.get('/og-preview.png', (req, res) => {
|
|
334
334
|
res.type('image/png');
|
|
335
|
-
res.sendFile(path.join(__dirname, 'public'
|
|
335
|
+
res.sendFile('og-preview.png', { root: path.join(__dirname, 'public') });
|
|
336
336
|
});
|
|
337
337
|
|
|
338
|
-
// Landing
|
|
338
|
+
// Root Route: Dashboard if run locally via CLI; Landing page on cloud deployment
|
|
339
339
|
app.get('/', (req, res) => {
|
|
340
|
-
|
|
340
|
+
const isCloudProduction = Boolean(process.env.RAILWAY_ENVIRONMENT || process.env.RENDER || process.env.NODE_ENV === 'production');
|
|
341
|
+
if (!isCloudProduction) {
|
|
342
|
+
return res.sendFile('index.html', { root: path.join(__dirname, 'public') });
|
|
343
|
+
}
|
|
344
|
+
res.sendFile('landing.html', { root: path.join(__dirname, 'public') });
|
|
341
345
|
});
|
|
342
346
|
|
|
343
347
|
// Interactive Dashboard Routes
|
|
344
348
|
app.get(['/dashboard', '/app'], (req, res) => {
|
|
345
|
-
res.sendFile(path.join(__dirname, 'public'
|
|
349
|
+
res.sendFile('index.html', { root: path.join(__dirname, 'public') });
|
|
346
350
|
});
|
|
347
351
|
|
|
348
352
|
// Engineering Blog Routes
|
|
349
353
|
app.get('/blog', (req, res) => {
|
|
350
|
-
res.sendFile(path.join(__dirname, 'public', 'blog'
|
|
354
|
+
res.sendFile('index.html', { root: path.join(__dirname, 'public', 'blog') });
|
|
351
355
|
});
|
|
352
356
|
|
|
353
357
|
app.get('/blog/:slug', (req, res) => {
|
|
354
358
|
const slug = req.params.slug.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
355
|
-
const
|
|
359
|
+
const fileName = `${slug}.html`;
|
|
360
|
+
const filePath = path.join(__dirname, 'public', 'blog', fileName);
|
|
356
361
|
if (fs.existsSync(filePath)) {
|
|
357
|
-
return res.sendFile(
|
|
362
|
+
return res.sendFile(fileName, { root: path.join(__dirname, 'public', 'blog') });
|
|
358
363
|
}
|
|
359
364
|
res.redirect('/blog');
|
|
360
365
|
});
|