Skip to content

Commit c00084f

Browse files
authored
Merge pull request #6 from magicpages/add-public-url-variable
Update environment configuration and versioning
2 parents 69ecf89 + fbb70ce commit c00084f

File tree

9 files changed

+59
-13
lines changed

9 files changed

+59
-13
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ The `magicpages/ghost-cache-invalidation-proxy` Docker image is available on [Do
1818

1919
### Environment Variables
2020

21+
#### URL Configuration
22+
23+
The proxy supports two URL configurations to handle different deployment scenarios:
24+
25+
- **`GHOST_URL`**: The internal URL used to communicate with your Ghost instance (e.g., `http://ghost:2368` in Docker networks)
26+
- **`GHOST_PUBLIC_URL`** (optional): The public-facing URL of your Ghost site (e.g., `https://yourdomain.com`)
27+
28+
When `GHOST_PUBLIC_URL` is not provided, the proxy will send relative URLs (like `/post-slug`) to your webhook endpoint. When `GHOST_PUBLIC_URL` is configured, the proxy will convert these to absolute URLs (like `https://yourdomain.com/post-slug`).
29+
30+
This is particularly useful for CDNs that require absolute URLs for cache purging, or when your internal Ghost URL differs from your public domain.
31+
2132
#### Required variables
2233

2334
- `GHOST_URL`: The URL of your Ghost CMS instance. Ideally, the hostname of your Ghost container and the port it listens on (e.g., `http://ghost:2368`).
@@ -57,6 +68,7 @@ services:
5768
image: magicpages/ghost-cache-invalidation-proxy:latest
5869
environment:
5970
- GHOST_URL=http://ghost:2368
71+
- GHOST_PUBLIC_URL=https://yourdomain.com
6072
- PORT=4000
6173
- DEBUG=true
6274
- WEBHOOK_URL=https://api.example.com/invalidate

docker-compose.example.yml

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ services:
1414
build:
1515
context: .
1616
environment:
17-
- GHOST_URL=http://ghost:2368
18-
- PORT=4000
19-
- DEBUG=true
20-
- WEBHOOK_URL=https://api.example.com/invalidate
21-
- WEBHOOK_METHOD=POST
22-
- WEBHOOK_SECRET=your_secret_key
23-
- WEBHOOK_HEADERS={"Custom-Header": "Value", "Authorization": "Bearer ${secret}"}
24-
- WEBHOOK_BODY_TEMPLATE={"urls": ${urls}, "timestamp": "${timestamp}", "purgeAll": ${purgeAll}}
25-
- WEBHOOK_RETRY_COUNT=3
26-
- WEBHOOK_RETRY_DELAY=1000
17+
GHOST_URL: http://ghost:2368
18+
GHOST_PUBLIC_URL: https://your-blog.com # Optional: If set, webhook URLs will be absolute
19+
WEBHOOK_URL: https://api.example.com/invalidate
20+
PORT: 4000
21+
DEBUG: "true"
22+
WEBHOOK_METHOD: POST
23+
WEBHOOK_SECRET: your_secret_key
24+
WEBHOOK_HEADERS: '{"AccessKey": "$${secret}", "Content-Type": "application/json"}'
25+
WEBHOOK_BODY_TEMPLATE: '{"urls": $${urls}}'
26+
WEBHOOK_RETRY_COUNT: 3
27+
WEBHOOK_RETRY_DELAY: 1000
2728
ports:
2829
- "4000:4000"
2930
depends_on:

example.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
# The URL of your Ghost CMS instance (required)
55
GHOST_URL=http://ghost:2368
66

7+
# Optional: The public-facing URL of your Ghost site (for absolute URL generation in cache purge requests)
8+
# Use this when your internal Ghost URL differs from the public domain
9+
# GHOST_PUBLIC_URL=https://yourdomain.com
10+
711
# The URL for the webhook endpoint that will be called when cache invalidation is needed (required)
812
WEBHOOK_URL=https://api.example.com/invalidate
913

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ghost-cache-invalidation-proxy",
3-
"version": "1.0.1",
3+
"version": "1.1.0",
44
"description": "A proxy between a Ghost CMS instance and configurable webhooks for cache invalidation.",
55
"main": "dist/index.js",
66
"author": "Jannis Fedoruk-Betschki <[email protected]>",

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { MiddlewareConfig } from './types.js'
55
// Schema for runtime environment validation
66
const baseSchema = z.object({
77
GHOST_URL: z.string().url().default('http://localhost:2368'),
8+
GHOST_PUBLIC_URL: z.string().url().optional(),
89
PORT: z.string().transform(Number).default('3000'),
910
DEBUG: z.string().transform(val => val === 'true').default('false'),
1011
WEBHOOK_URL: z.string().url(),
@@ -57,6 +58,7 @@ export function loadConfig(): MiddlewareConfig {
5758

5859
return {
5960
ghostUrl: data.GHOST_URL.replace(/\/$/, ''),
61+
ghostPublicUrl: data.GHOST_PUBLIC_URL?.replace(/\/$/, ''),
6062
port: data.PORT,
6163
debug: data.DEBUG,
6264
webhook: {

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ async function bootstrap() {
77
const config = loadConfig();
88
const app = express();
99

10+
// Disable X-Powered-By header for security
11+
app.disable('x-powered-by');
12+
1013
// Basic security
1114
app.set('trust proxy', config.security.trustProxy);
1215

@@ -34,6 +37,9 @@ async function bootstrap() {
3437
console.log(`🚀 Server running on port ${config.port}`);
3538
config.debug && console.log('🐛 Debug mode enabled');
3639
console.log(`🔗 Connected to Ghost CMS at ${config.ghostUrl}`);
40+
if (config.ghostPublicUrl) {
41+
console.log(`🌐 Public site URL: ${config.ghostPublicUrl}`);
42+
}
3743
console.log(`📣 Webhook configured at ${config.webhook.url}`);
3844
});
3945
} catch (error: unknown) {

src/middleware.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,17 @@ export class ProxyMiddleware {
7676
console.log('📋 Response headers:', proxyRes.headers);
7777
}
7878

79+
// Filter out potentially sensitive headers
80+
const filteredHeaders = { ...proxyRes.headers };
81+
82+
// Remove headers that might expose server information
83+
delete filteredHeaders['x-powered-by'];
84+
delete filteredHeaders['server'];
85+
delete filteredHeaders['x-aspnet-version'];
86+
delete filteredHeaders['x-aspnetmvc-version'];
87+
7988
// Forward the response headers and status code
80-
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
89+
res.writeHead(proxyRes.statusCode || 200, filteredHeaders);
8190

8291
// Pipe the response body
8392
proxyRes.pipe(res);

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface MiddlewareConfig {
22
ghostUrl: string;
3+
ghostPublicUrl?: string;
34
port: number;
45
debug: boolean;
56
webhook: {

src/webhook.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,21 @@ export class WebhookManager {
4545
// - "/post-permalink" - Single post
4646
// - "/, /page/*, /rss" - Multiple pages
4747
// - "/page/*" - Wildcard paths
48-
const urls = purgeAll
48+
let urls = purgeAll
4949
? ['/*']
5050
: pattern.split(',').map(url => url.trim());
5151

52+
// If ghostPublicUrl is configured, convert relative URLs to absolute URLs
53+
if (this.config.ghostPublicUrl && !purgeAll) {
54+
urls = urls.map(url => {
55+
// Only process relative URLs (starting with /)
56+
if (url.startsWith('/')) {
57+
return `${this.config.ghostPublicUrl}${url}`;
58+
}
59+
return url;
60+
});
61+
}
62+
5263
// From Ghost documentation, common patterns include:
5364
// - "/" - Home page
5465
// - "/page/*" - Paginated pages

0 commit comments

Comments
 (0)