add support for http.
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
function envFlagEnabled(value: string): boolean {
|
||||||
|
const v = value.trim().toLowerCase();
|
||||||
|
return v === "1" || v === "true" || v === "yes" || v === "on";
|
||||||
|
}
|
||||||
|
|
||||||
|
function envFlagDisabled(value: string): boolean {
|
||||||
|
const v = value.trim().toLowerCase();
|
||||||
|
return v === "0" || v === "false" || v === "no" || v === "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldUseSecureAuthCookie(req: Request): boolean {
|
||||||
|
const mode = String(process.env.OVERLORD_AUTH_COOKIE_SECURE || "auto");
|
||||||
|
if (envFlagEnabled(mode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (envFlagDisabled(mode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const forwardedProto = req.headers
|
||||||
|
.get("x-forwarded-proto")
|
||||||
|
?.split(",")[0]
|
||||||
|
?.trim()
|
||||||
|
?.toLowerCase();
|
||||||
|
if (forwardedProto === "https") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (forwardedProto === "http") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URL(req.url).protocol === "https:";
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeAuthCookie(token: string, maxAgeSeconds: number, req: Request): string {
|
||||||
|
const securePart = shouldUseSecureAuthCookie(req) ? "Secure; " : "";
|
||||||
|
return `overlord_token=${token}; HttpOnly; ${securePart}SameSite=Strict; Path=/; Max-Age=${maxAgeSeconds}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeAuthCookieClear(req: Request): string {
|
||||||
|
const securePart = shouldUseSecureAuthCookie(req) ? "Secure; " : "";
|
||||||
|
return `overlord_token=; HttpOnly; ${securePart}SameSite=Strict; Path=/; Max-Age=0`;
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
recordSuccessfulAttempt,
|
recordSuccessfulAttempt,
|
||||||
} from "../../rateLimit";
|
} from "../../rateLimit";
|
||||||
import { getUserById } from "../../users";
|
import { getUserById } from "../../users";
|
||||||
|
import { makeAuthCookie, makeAuthCookieClear } from "./auth-cookie";
|
||||||
|
|
||||||
type RequestIpProvider = {
|
type RequestIpProvider = {
|
||||||
requestIP: (req: Request) => { address?: string } | null | undefined;
|
requestIP: (req: Request) => { address?: string } | null | undefined;
|
||||||
@@ -95,7 +96,7 @@ export async function handleAuthRoutes(
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Set-Cookie": `overlord_token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${sessionTtlSeconds}`,
|
"Set-Cookie": makeAuthCookie(token, sessionTtlSeconds, req),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -148,7 +149,7 @@ export async function handleAuthRoutes(
|
|||||||
return new Response(JSON.stringify({ ok: true }), {
|
return new Response(JSON.stringify({ ok: true }), {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Set-Cookie": `overlord_token=; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=0`,
|
"Set-Cookie": makeAuthCookieClear(req),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
updateUserPassword,
|
updateUserPassword,
|
||||||
updateUserRole,
|
updateUserRole,
|
||||||
} from "../../users";
|
} from "../../users";
|
||||||
|
import { makeAuthCookie } from "./auth-cookie";
|
||||||
|
|
||||||
type RequestIpProvider = {
|
type RequestIpProvider = {
|
||||||
requestIP: (req: Request) => { address?: string } | null | undefined;
|
requestIP: (req: Request) => { address?: string } | null | undefined;
|
||||||
@@ -131,7 +132,7 @@ export async function handleUsersRoutes(
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Set-Cookie": `overlord_token=${newToken}; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=${sessionTtlSeconds}`,
|
"Set-Cookie": makeAuthCookie(newToken, sessionTtlSeconds, req),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ services:
|
|||||||
- OVERLORD_TLS_KEY=/app/certs/server.key
|
- OVERLORD_TLS_KEY=/app/certs/server.key
|
||||||
- OVERLORD_TLS_CA=
|
- OVERLORD_TLS_CA=
|
||||||
- OVERLORD_TLS_OFFLOAD=false
|
- OVERLORD_TLS_OFFLOAD=false
|
||||||
|
- OVERLORD_AUTH_COOKIE_SECURE=auto
|
||||||
- OVERLORD_TLS_CERTBOT_ENABLED=false
|
- OVERLORD_TLS_CERTBOT_ENABLED=false
|
||||||
- OVERLORD_TLS_CERTBOT_LIVE_PATH=/etc/letsencrypt/live
|
- OVERLORD_TLS_CERTBOT_LIVE_PATH=/etc/letsencrypt/live
|
||||||
- OVERLORD_TLS_CERTBOT_DOMAIN=
|
- OVERLORD_TLS_CERTBOT_DOMAIN=
|
||||||
@@ -66,7 +67,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- overlord-network
|
- overlord-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "-k", "https://localhost:5173/health"]
|
test: ["CMD-SHELL", "curl -f ${OVERLORD_HEALTHCHECK_URL:-https://localhost:5173/health} >/dev/null 2>&1 || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
@@ -348,6 +349,8 @@ Override with:
|
|||||||
If your platform terminates TLS before traffic reaches Overlord, set:
|
If your platform terminates TLS before traffic reaches Overlord, set:
|
||||||
|
|
||||||
- `OVERLORD_TLS_OFFLOAD=true`
|
- `OVERLORD_TLS_OFFLOAD=true`
|
||||||
|
- `OVERLORD_HEALTHCHECK_URL=http://localhost:5173/health`
|
||||||
|
- `OVERLORD_PUBLISH_HOST=127.0.0.1` (recommended for local proxies like ngrok)
|
||||||
|
|
||||||
When enabled:
|
When enabled:
|
||||||
|
|
||||||
@@ -355,3 +358,22 @@ When enabled:
|
|||||||
- external URL remains `https://...` through your platform proxy
|
- external URL remains `https://...` through your platform proxy
|
||||||
- health checks should use `http://localhost:$PORT/health` inside the container
|
- health checks should use `http://localhost:$PORT/health` inside the container
|
||||||
- do not expose internal container HTTP port directly to the internet
|
- do not expose internal container HTTP port directly to the internet
|
||||||
|
|
||||||
|
For ngrok/local reverse proxy use, a common setup is:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
OVERLORD_TLS_OFFLOAD=true
|
||||||
|
OVERLORD_HEALTHCHECK_URL=http://localhost:5173/health
|
||||||
|
OVERLORD_PUBLISH_HOST=127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Then point ngrok at local HTTP:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ngrok http http://127.0.0.1:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Keep `HOST=0.0.0.0` inside the container. Limiting exposure should be done with publish binding (`OVERLORD_PUBLISH_HOST`), not server bind host.
|
||||||
|
- If your `.env` secret/password includes `$`, escape as `$$` to avoid Docker Compose variable-expansion warnings.
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ services:
|
|||||||
- type=local,dest=.docker-cache/buildx,mode=max
|
- type=local,dest=.docker-cache/buildx,mode=max
|
||||||
container_name: overlord-server
|
container_name: overlord-server
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
# Bind host-published port. Use 127.0.0.1 for local reverse proxies (ngrok/caddy/traefik).
|
||||||
|
- "${OVERLORD_PUBLISH_HOST:-0.0.0.0}:5173:5173"
|
||||||
environment:
|
environment:
|
||||||
# Optional username for first admin user
|
# Optional username for first admin user
|
||||||
- OVERLORD_USER=admin
|
- OVERLORD_USER=admin
|
||||||
@@ -28,6 +29,9 @@ services:
|
|||||||
# Set true only behind a trusted reverse proxy that terminates TLS (for example Render).
|
# Set true only behind a trusted reverse proxy that terminates TLS (for example Render).
|
||||||
# Default false keeps current behavior: Overlord terminates TLS in-process.
|
# Default false keeps current behavior: Overlord terminates TLS in-process.
|
||||||
- OVERLORD_TLS_OFFLOAD=false
|
- OVERLORD_TLS_OFFLOAD=false
|
||||||
|
# Cookie security mode: auto (default), true, or false.
|
||||||
|
# auto: Secure cookie on HTTPS requests / X-Forwarded-Proto=https.
|
||||||
|
- OVERLORD_AUTH_COOKIE_SECURE=auto
|
||||||
# Optional certbot integration (used only when NODE_ENV=production)
|
# Optional certbot integration (used only when NODE_ENV=production)
|
||||||
- OVERLORD_TLS_CERTBOT_ENABLED=false
|
- OVERLORD_TLS_CERTBOT_ENABLED=false
|
||||||
- OVERLORD_TLS_CERTBOT_LIVE_PATH=/etc/letsencrypt/live
|
- OVERLORD_TLS_CERTBOT_LIVE_PATH=/etc/letsencrypt/live
|
||||||
@@ -50,7 +54,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- overlord-network
|
- overlord-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "-k", "https://localhost:5173/health"]
|
# Use http:// when OVERLORD_TLS_OFFLOAD=true.
|
||||||
|
test: ["CMD-SHELL", "curl -f ${OVERLORD_HEALTHCHECK_URL:-https://localhost:5173/health} >/dev/null 2>&1 || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
+7
-2
@@ -10,7 +10,8 @@ services:
|
|||||||
- type=local,dest=.docker-cache/buildx,mode=max
|
- type=local,dest=.docker-cache/buildx,mode=max
|
||||||
container_name: overlord-server
|
container_name: overlord-server
|
||||||
ports:
|
ports:
|
||||||
- "${PORT:-5173}:5173"
|
# Bind host-published port. Use 127.0.0.1 for local reverse proxies (ngrok/caddy/traefik).
|
||||||
|
- "${OVERLORD_PUBLISH_HOST:-0.0.0.0}:${PORT:-5173}:5173"
|
||||||
environment:
|
environment:
|
||||||
PORT: ${PORT:-5173}
|
PORT: ${PORT:-5173}
|
||||||
HOST: ${HOST:-0.0.0.0}
|
HOST: ${HOST:-0.0.0.0}
|
||||||
@@ -28,6 +29,9 @@ services:
|
|||||||
# Set true only behind a trusted reverse proxy that terminates TLS (for example Render).
|
# Set true only behind a trusted reverse proxy that terminates TLS (for example Render).
|
||||||
# Default false keeps current behavior: Overlord terminates TLS in-process.
|
# Default false keeps current behavior: Overlord terminates TLS in-process.
|
||||||
OVERLORD_TLS_OFFLOAD: ${OVERLORD_TLS_OFFLOAD:-false}
|
OVERLORD_TLS_OFFLOAD: ${OVERLORD_TLS_OFFLOAD:-false}
|
||||||
|
# Cookie security mode: auto (default), true, or false.
|
||||||
|
# auto: Secure cookie on HTTPS requests / X-Forwarded-Proto=https.
|
||||||
|
OVERLORD_AUTH_COOKIE_SECURE: ${OVERLORD_AUTH_COOKIE_SECURE:-auto}
|
||||||
# Optional certbot integration (used only when NODE_ENV=production)
|
# Optional certbot integration (used only when NODE_ENV=production)
|
||||||
OVERLORD_TLS_CERTBOT_ENABLED: ${OVERLORD_TLS_CERTBOT_ENABLED:-false}
|
OVERLORD_TLS_CERTBOT_ENABLED: ${OVERLORD_TLS_CERTBOT_ENABLED:-false}
|
||||||
OVERLORD_TLS_CERTBOT_LIVE_PATH: ${OVERLORD_TLS_CERTBOT_LIVE_PATH:-/etc/letsencrypt/live}
|
OVERLORD_TLS_CERTBOT_LIVE_PATH: ${OVERLORD_TLS_CERTBOT_LIVE_PATH:-/etc/letsencrypt/live}
|
||||||
@@ -53,7 +57,8 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- overlord-network
|
- overlord-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "-k", "https://localhost:5173/health"]
|
# Use http:// when OVERLORD_TLS_OFFLOAD=true.
|
||||||
|
test: ["CMD-SHELL", "curl -f ${OVERLORD_HEALTHCHECK_URL:-https://localhost:5173/health} >/dev/null 2>&1 || exit 1"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
Reference in New Issue
Block a user