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 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 chalk = require('chalk');
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.blue('\nšŸ›”ļø HookArmor Webhook Sentinel is LIVE!'));
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.cyan(`\n⚔ HookArmor Listen Active`));
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.blue('\nšŸ›”ļø HookArmor Metrics'));
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.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": {
@@ -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: 620px;">
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: #64748b; text-transform: uppercase; letter-spacing: 0.05em; font-family: var(--font-sans); font-weight: 600;">Self-Host Quickstart (Zero Dependencies)</span>
377
- <button class="copy-btn" onclick="navigator.clipboard.writeText('git clone https://github.com/pkdoddamani/hookarmor.git && cd hookarmor && npm install && npm start')">Copy</button>
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: 0.85rem; word-break: break-all;">
380
- $ git clone https://github.com/pkdoddamani/hookarmor.git && cd hookarmor && npm start
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', 'robots.txt'));
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', 'sitemap.xml'));
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', 'og-preview.png'));
335
+ res.sendFile('og-preview.png', { root: path.join(__dirname, 'public') });
336
336
  });
337
337
 
338
- // Landing Page Route
338
+ // Root Route: Dashboard if run locally via CLI; Landing page on cloud deployment
339
339
  app.get('/', (req, res) => {
340
- res.sendFile(path.join(__dirname, 'public', 'landing.html'));
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', 'index.html'));
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', 'index.html'));
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 filePath = path.join(__dirname, 'public', 'blog', `${slug}.html`);
359
+ const fileName = `${slug}.html`;
360
+ const filePath = path.join(__dirname, 'public', 'blog', fileName);
356
361
  if (fs.existsSync(filePath)) {
357
- return res.sendFile(filePath);
362
+ return res.sendFile(fileName, { root: path.join(__dirname, 'public', 'blog') });
358
363
  }
359
364
  res.redirect('/blog');
360
365
  });