From 7a619e0edc0d7195e0202fc9cf362f6cfd593b4f Mon Sep 17 00:00:00 2001 From: Xi Xu Date: Fri, 25 Jul 2025 16:24:17 +0800 Subject: [PATCH] Improve Docker registry authentication handling Enhanced the unauthorized response to include proper headers and JSON error details. Added logic to attempt anonymous token retrieval for public Docker images and retry requests with the token if available, improving support for unauthenticated access. --- src/index.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/src/index.js b/src/index.js index 586d6dc..d554003 100644 --- a/src/index.js +++ b/src/index.js @@ -190,12 +190,24 @@ function responseUnauthorized(url) { const headers = new Headers(); headers.set( 'WWW-Authenticate', - `Bearer realm="https://${url.hostname}/v2/auth",service="cloudflare-docker-proxy"` + `Bearer realm="https://${url.hostname}/v2/auth",service="xget.xi-xu.me"` + ); + headers.set('Content-Type', 'application/json'); + return new Response( + JSON.stringify({ + errors: [ + { + code: 'UNAUTHORIZED', + message: 'authentication required', + detail: null + } + ] + }), + { + status: 401, + headers: headers + } ); - return new Response(JSON.stringify({ message: 'UNAUTHORIZED' }), { - status: 401, - headers: headers - }); } /** @@ -469,6 +481,55 @@ async function handleRequest(request, env, ctx) { // For Docker registry, handle authentication challenges if (isDocker && response.status === 401) { monitor.mark('docker_auth_challenge'); + + // Try to get an anonymous token for public images + const authenticateStr = response.headers.get('WWW-Authenticate'); + if (authenticateStr) { + try { + const wwwAuthenticate = parseAuthenticate(authenticateStr); + let scope = null; + + // Extract scope from the original request path if it's a manifest request + if (url.pathname.includes('/manifests/')) { + const pathParts = url.pathname.split('/'); + const registryPrefix = platform.replace('cr-', ''); + if (registryPrefix === 'docker') { + // For Docker Hub, construct scope from path + // /v2/cr/docker/library/nginx/manifests/latest -> repository:library/nginx:pull + const imagePathStart = pathParts.indexOf('manifests'); + if (imagePathStart >= 3) { + const imageName = pathParts.slice(3, imagePathStart).join('/'); + scope = `repository:${imageName}:pull`; + } + } + } + + // Try to get anonymous token + const tokenResponse = await fetchToken(wwwAuthenticate, scope || '', ''); + if (tokenResponse.ok) { + // Retry the original request with the token + const tokenData = await tokenResponse.json(); + if (tokenData.token) { + const retryHeaders = new Headers(request.headers); + retryHeaders.set('Authorization', `Bearer ${tokenData.token}`); + + const retryResponse = await fetch(targetUrl, { + ...finalFetchOptions, + headers: retryHeaders + }); + + if (retryResponse.ok) { + response = retryResponse; + break; + } + } + } + } catch (error) { + console.log('Anonymous token fetch failed:', error); + } + } + + // If anonymous access fails, return unauthorized response return responseUnauthorized(url); }