fix(oci): Improve OCI manifest permission response and tag handling: include WWW-Authenticate header on unauthorized manifest GETs, accept optional headers in manifest lookup, and persist tags as a unified tags.json mapping when pushing manifests.

This commit is contained in:
2025-11-20 19:48:32 +00:00
parent 3d5b87ec05
commit 52dc1c0549
3 changed files with 19 additions and 7 deletions

View File

@@ -1,5 +1,13 @@
# Changelog # Changelog
## 2025-11-20 - 1.1.1 - fix(oci)
Improve OCI manifest permission response and tag handling: include WWW-Authenticate header on unauthorized manifest GETs, accept optional headers in manifest lookup, and persist tags as a unified tags.json mapping when pushing manifests.
- getManifest now accepts an optional headers parameter for better request context handling.
- Unauthorized GET manifest responses now include a WWW-Authenticate header with realm/service/scope to comply with OCI auth expectations.
- PUT manifest logic no longer writes individual tag objects; it updates a consolidated oci/tags/{repository}/tags.json mapping using getTagsData and putObject.
- Simplified tag update flow when pushing a manifest: tags[reference] = digest and persist tags.json.
## 2025-11-20 - 1.1.0 - feat(oci) ## 2025-11-20 - 1.1.0 - feat(oci)
Support monolithic OCI blob uploads; add registry cleanup/destroy hooks; update tests and docs Support monolithic OCI blob uploads; add registry cleanup/destroy hooks; update tests and docs

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartregistry', name: '@push.rocks/smartregistry',
version: '1.1.0', version: '1.1.1',
description: 'a registry for npm modules and oci images' description: 'a registry for npm modules and oci images'
} }

View File

@@ -284,12 +284,15 @@ export class OciRegistry extends BaseRegistry {
private async getManifest( private async getManifest(
repository: string, repository: string,
reference: string, reference: string,
token: IAuthToken | null token: IAuthToken | null,
headers?: Record<string, string>
): Promise<IResponse> { ): Promise<IResponse> {
if (!await this.checkPermission(token, repository, 'pull')) { if (!await this.checkPermission(token, repository, 'pull')) {
return { return {
status: 401, status: 401,
headers: {}, headers: {
'WWW-Authenticate': `Bearer realm="${this.basePath}/v2/token",service="registry",scope="repository:${repository}:pull"`,
},
body: this.createError('DENIED', 'Insufficient permissions'), body: this.createError('DENIED', 'Insufficient permissions'),
}; };
} }
@@ -402,11 +405,12 @@ export class OciRegistry extends BaseRegistry {
// Store manifest by digest // Store manifest by digest
await this.storage.putOciManifest(repository, digest, manifestData, contentType); await this.storage.putOciManifest(repository, digest, manifestData, contentType);
// If reference is a tag (not a digest), create tag reference // If reference is a tag (not a digest), update tags mapping
if (!reference.startsWith('sha256:')) { if (!reference.startsWith('sha256:')) {
// Store tag -> digest mapping const tags = await this.getTagsData(repository);
const tagPath = `oci/repositories/${repository}/tags/${reference}`; tags[reference] = digest;
await this.storage.putObject(tagPath, Buffer.from(digest, 'utf-8')); const tagsPath = `oci/tags/${repository}/tags.json`;
await this.storage.putObject(tagsPath, Buffer.from(JSON.stringify(tags), 'utf-8'));
} }
return { return {