From d91f1491f150bb628dc9964363937130c13ce7da Mon Sep 17 00:00:00 2001 From: Abel Luck Date: Thu, 26 Feb 2026 14:45:11 +0100 Subject: [PATCH] nix-cache: redirect http to https --- flake.nix | 2 +- nix-cache/src/index.ts | 7 ++++++- nix-cache/test/index.spec.ts | 28 ++++++++++++++++++---------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/flake.nix b/flake.nix index 2f84f54..9bd48f5 100644 --- a/flake.nix +++ b/flake.nix @@ -14,7 +14,7 @@ packages = forAllSystems (pkgs: { nix-cache = pkgs.buildNpmPackage { pname = "nix-cache"; - version = "0.1.0"; + version = "0.1.2"; src = ./nix-cache; npmDepsHash = "sha256-bIujU7pZa1ExCZOMOoxsTeLDSF1GuPN/oMSgiMhY/04="; buildPhase = '' diff --git a/nix-cache/src/index.ts b/nix-cache/src/index.ts index 393ad3b..3335de4 100644 --- a/nix-cache/src/index.ts +++ b/nix-cache/src/index.ts @@ -85,6 +85,12 @@ export default { return new Response(null, { headers: { allow: allowedMethods.join(', ') } }); } + const url = new URL(request.url); + if (url.protocol !== 'https:') { + url.protocol = 'https:'; + return Response.redirect(url.toString(), 301); + } + const authenticated = await authenticate(request, env); if (!authenticated) { return new Response('Unauthorized', { @@ -93,7 +99,6 @@ export default { }); } - const url = new URL(request.url); if (url.pathname === '/') { url.pathname = '/index.html'; } diff --git a/nix-cache/test/index.spec.ts b/nix-cache/test/index.spec.ts index 8f14a11..0bdfb07 100644 --- a/nix-cache/test/index.spec.ts +++ b/nix-cache/test/index.spec.ts @@ -54,15 +54,23 @@ async function makeJWT(overrides: Record = {}): Promise } describe('nix-cache auth', () => { + it('HTTP request redirects to HTTPS', async () => { + const request = new IncomingRequest('http://example.com/nix-cache-info'); + const ctx = createExecutionContext(); + const response = await worker.fetch(request, env, ctx); + expect(response.status).toBe(301); + expect(response.headers.get('location')).toBe('https://example.com/nix-cache-info'); + }); + it('OPTIONS without auth returns 200', async () => { - const request = new IncomingRequest('http://example.com/', { method: 'OPTIONS' }); + const request = new IncomingRequest('https://example.com/', { method: 'OPTIONS' }); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(200); }); it('GET with no Authorization header returns 401', async () => { - const request = new IncomingRequest('http://example.com/nix-cache-info'); + const request = new IncomingRequest('https://example.com/nix-cache-info'); const ctx = createExecutionContext(); const response = await worker.fetch(request, env, ctx); expect(response.status).toBe(401); @@ -70,7 +78,7 @@ describe('nix-cache auth', () => { }); it('GET with unrecognized auth scheme returns 401', async () => { - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: 'Token xyz' }, }); const ctx = createExecutionContext(); @@ -80,7 +88,7 @@ describe('nix-cache auth', () => { it('GET with Bearer + valid JWT passes auth', async () => { const token = await makeJWT(); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); @@ -91,7 +99,7 @@ describe('nix-cache auth', () => { it('GET with Basic auth :token format passes auth', async () => { const token = await makeJWT(); const basic = btoa(':' + token); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Basic ${basic}` }, }); const ctx = createExecutionContext(); @@ -102,7 +110,7 @@ describe('nix-cache auth', () => { it('GET with Basic auth user:token format passes auth', async () => { const token = await makeJWT(); const basic = btoa('user:' + token); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Basic ${basic}` }, }); const ctx = createExecutionContext(); @@ -111,7 +119,7 @@ describe('nix-cache auth', () => { }); it('GET with invalid Bearer token returns 401', async () => { - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: 'Bearer garbage-token' }, }); const ctx = createExecutionContext(); @@ -121,7 +129,7 @@ describe('nix-cache auth', () => { it('GET with expired JWT returns 401', async () => { const token = await makeJWT({ exp: Math.floor(Date.now() / 1000) - 3600 }); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); @@ -131,7 +139,7 @@ describe('nix-cache auth', () => { it('GET with JWT missing groups claim returns 401', async () => { const token = await makeJWT({ groups: undefined }); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext(); @@ -141,7 +149,7 @@ describe('nix-cache auth', () => { it('GET with JWT where groups does not contain required group returns 401', async () => { const token = await makeJWT({ groups: ['some-other-group'] }); - const request = new IncomingRequest('http://example.com/nix-cache-info', { + const request = new IncomingRequest('https://example.com/nix-cache-info', { headers: { Authorization: `Bearer ${token}` }, }); const ctx = createExecutionContext();