diff --git a/README.md b/README.md index 88d20b0ec..1648a0887 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ For configuration options, examples and more, [please check out the homepage doc Please note that when using features such as widgets, Homepage can access personal information (for example from your home automation system). To keep your information private, if Homepage is reachable from any untrusted network, it: 1. **must** sit behind a reverse proxy (and/or VPN) that enforces authentication, TLS, and strictly validates Host headers. -2. An optional built-in OIDC login flow is available (opt-in) offering a simple “authenticated or not” guard. +2. An optional built-in OIDC login flow or simple password login is available (opt-in) offering a simple “authenticated or not” guard. ## With Docker diff --git a/docs/installation/index.md b/docs/installation/index.md index d31f540c4..9ed9b48ab 100644 --- a/docs/installation/index.md +++ b/docs/installation/index.md @@ -60,4 +60,6 @@ For OIDC login (overrides password login): - `HOMEPAGE_EXTERNAL_URL` (external URL to your Homepage instance; used for callbacks) - Optional: `HOMEPAGE_OIDC_NAME` (display name), `HOMEPAGE_OIDC_SCOPE` (defaults to `openid email profile`) -All app pages and `/api` routes will require a signed-in session. Static assets remain public. Homepage still does not implement per-user dashboards or roles; authentication is a simple gate only. +All app pages and `/api` routes except `/api/healthcheck` will require a signed-in session. Static assets remain public. + +Configure your OIDC provider with the a callback URI like `https://homepage.example.com/api/auth/callback/homepage-oidc`. diff --git a/docs/widgets/services/arcane.md b/docs/widgets/services/arcane.md index c8d88207f..6c59b9ee3 100644 --- a/docs/widgets/services/arcane.md +++ b/docs/widgets/services/arcane.md @@ -16,3 +16,5 @@ widget: key: your-api-key fields: ["running", "stopped", "total", "image_updates"] # optional ``` + +Using an api key for the widget requires permissions for: `containers:list` `images:list and `image-updates:read` diff --git a/src/middleware.js b/src/middleware.js index 6f1e7edfd..e4f72bca8 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -27,7 +27,7 @@ export async function middleware(req) { return NextResponse.json({ error: "Host validation failed. See logs for more details." }, { status: 400 }); } - if (authEnabled) { + if (authEnabled && !new URL(req.url).pathname.startsWith("/api/healthcheck")) { if (new URL(req.url).pathname === "/api/mcp" && hasMcpToken(req)) { return NextResponse.next(); } diff --git a/src/middleware.test.js b/src/middleware.test.js index a795f88d1..8d08f642b 100644 --- a/src/middleware.test.js +++ b/src/middleware.test.js @@ -88,6 +88,18 @@ describe("middleware", () => { expect(res).toEqual({ type: "next" }); }); + it("allows healthcheck requests without auth when host is allowed", async () => { + process.env.HOMEPAGE_AUTH_ENABLED = "true"; + process.env.HOMEPAGE_AUTH_SECRET = "secret"; + + const middleware = await loadMiddleware(); + const res = await middleware(createReq("localhost:3000", "http://localhost:3000/api/healthcheck")); + + expect(getToken).not.toHaveBeenCalled(); + expect(NextResponse.next).toHaveBeenCalled(); + expect(res).toEqual({ type: "next" }); + }); + it("redirects to signin when auth is enabled and no token is present", async () => { process.env.HOMEPAGE_AUTH_ENABLED = "true"; process.env.HOMEPAGE_AUTH_SECRET = "secret"; diff --git a/src/pages/auth/signin.jsx b/src/pages/auth/signin.jsx index 2ff4b8220..bd352911d 100644 --- a/src/pages/auth/signin.jsx +++ b/src/pages/auth/signin.jsx @@ -128,8 +128,6 @@ export default function SignIn({ providers, settings }) { backgroundBrightness && `backdrop-brightness-${settings.background.brightness}`, )} > -
-