41 lines
2.2 KiB
TypeScript
41 lines
2.2 KiB
TypeScript
/**
|
|
* Pure catalog-failure classification (#873). No docker/db imports, so it's
|
|
* unit-testable without pulling better-sqlite3 into the test process.
|
|
*
|
|
* GitLab, Harbor, and other registries refuse /v2/_catalog to non-admins by returning
|
|
* 401 with `error="insufficient_scope"` in WWW-Authenticate - the token is VALID, the
|
|
* operation just isn't permitted. That is NOT an auth failure. A real bad/expired token
|
|
* instead returns `error="invalid_token"`. Verified empirically on gitlab.com and a
|
|
* self-hosted GitLab (admin gets catalog 200; non-admin and gitlab.com get
|
|
* insufficient_scope; a bad token gets invalid_token).
|
|
*/
|
|
|
|
/**
|
|
* @param status HTTP status of the catalog response
|
|
* @param wwwAuthenticate the response's WWW-Authenticate header (may be null)
|
|
* @param authState 'authed' (we obtained and sent a bearer token), 'rejected' (registry
|
|
* HAS stored credentials but the token exchange failed - bad/expired creds), or 'anon'
|
|
* (no credentials configured)
|
|
* @returns 'not_supported' (registry won't list - point user to search) or 'auth_failed'
|
|
*/
|
|
export function classifyCatalogFailure(
|
|
status: number,
|
|
wwwAuthenticate: string | null,
|
|
authState: 'authed' | 'rejected' | 'anon'
|
|
): 'not_supported' | 'auth_failed' {
|
|
const err = (wwwAuthenticate || '').match(/error="([^"]+)"/i)?.[1]?.toLowerCase();
|
|
// An explicit error code is authoritative regardless of how we authenticated.
|
|
if (err === 'insufficient_scope') return 'not_supported';
|
|
if (err === 'invalid_token') return 'auth_failed';
|
|
// Credentials were configured but the token exchange failed -> real auth problem.
|
|
if (authState === 'rejected') return 'auth_failed';
|
|
// We authenticated fine but still can't list -> the token lacks catalog permission
|
|
// (GitLab/Harbor restrict it to admins). Anonymous 401 is the normal login challenge.
|
|
if ((status === 401 || status === 403) && authState === 'authed') return 'not_supported';
|
|
return 'auth_failed';
|
|
}
|
|
|
|
/** User-facing message for a registry that won't allow catalog listing (#873). */
|
|
export const CATALOG_NOT_SUPPORTED_MSG =
|
|
'This registry does not allow listing all images (GitLab and Harbor restrict it to admins). Search for an image by its full path instead.';
|