fix(registry): align registry integrations with updated auth, storage, repository, and audit models

This commit is contained in:
2026-03-20 14:14:39 +00:00
parent fe3cb75095
commit d71ae08645
18 changed files with 451 additions and 523 deletions

View File

@@ -86,6 +86,7 @@ export class StackGalleryRegistry {
// Initialize storage hooks
this.storageHooks = new StackGalleryStorageHooks({
bucket: this.smartBucket,
bucketName: this.config.s3Bucket,
basePath: this.config.storagePath!,
});
@@ -95,16 +96,22 @@ export class StackGalleryRegistry {
authProvider: this.authProvider,
storageHooks: this.storageHooks,
storage: {
type: 's3',
bucket: this.smartBucket,
basePath: this.config.storagePath,
endpoint: this.config.s3Endpoint,
accessKey: this.config.s3AccessKey,
accessSecret: this.config.s3SecretKey,
bucketName: this.config.s3Bucket,
region: this.config.s3Region,
},
auth: {
jwtSecret: this.config.jwtSecret || 'change-me-in-production',
tokenStore: 'database',
npmTokens: { enabled: true },
ociTokens: {
enabled: true,
realm: 'stack.gallery',
service: 'registry',
},
},
upstreamCache: this.config.enableUpstreamCache
? {
enabled: true,
expiryHours: this.config.upstreamCacheExpiry,
}
: undefined,
});
console.log('[StackGalleryRegistry] smartregistry initialized');
@@ -161,30 +168,34 @@ export class StackGalleryRegistry {
}
// Registry protocol endpoints (handled by smartregistry)
// NPM: /-/..., /@scope/package (but not /packages which is UI route)
// OCI: /v2/...
// Maven: /maven2/...
// PyPI: /simple/..., /pypi/...
// Cargo: /api/v1/crates/...
// Composer: /packages.json, /p/...
// RubyGems: /api/v1/gems/..., /gems/...
const registryPaths = ['/-/', '/v2/', '/maven2/', '/simple/', '/pypi/', '/api/v1/crates/', '/packages.json', '/p/', '/api/v1/gems/', '/gems/'];
const isRegistryPath = registryPaths.some(p => path.startsWith(p)) ||
(path.startsWith('/@') && !path.startsWith('/@stack'));
const registryPaths = [
'/-/',
'/v2/',
'/maven2/',
'/simple/',
'/pypi/',
'/api/v1/crates/',
'/packages.json',
'/p/',
'/api/v1/gems/',
'/gems/',
];
const isRegistryPath =
registryPaths.some((p) => path.startsWith(p)) ||
(path.startsWith('/@') && !path.startsWith('/@stack'));
if (this.smartRegistry && isRegistryPath) {
try {
const response = await this.smartRegistry.handleRequest(request);
if (response) return response;
// Convert Request to IRequestContext
const requestContext = await this.requestToContext(request);
const response = await this.smartRegistry.handleRequest(requestContext);
if (response) return this.contextResponseToResponse(response);
} catch (error) {
console.error('[StackGalleryRegistry] Request error:', error);
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
return new Response(JSON.stringify({ error: 'Internal server error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
@@ -197,6 +208,82 @@ export class StackGalleryRegistry {
return this.serveStaticFile(path);
}
/**
* Convert a Deno Request to smartregistry IRequestContext
*/
private async requestToContext(
request: Request
): Promise<plugins.smartregistry.IRequestContext> {
const url = new URL(request.url);
const headers: Record<string, string> = {};
request.headers.forEach((value, key) => {
headers[key] = value;
});
const query: Record<string, string> = {};
url.searchParams.forEach((value, key) => {
query[key] = value;
});
let body: unknown = undefined;
// deno-lint-ignore no-explicit-any
let rawBody: any = undefined;
if (request.body && request.method !== 'GET' && request.method !== 'HEAD') {
try {
const bytes = new Uint8Array(await request.arrayBuffer());
rawBody = bytes;
const contentType = request.headers.get('content-type') || '';
if (contentType.includes('json')) {
body = JSON.parse(new TextDecoder().decode(bytes));
}
} catch {
// Body parsing failed, continue with undefined body
}
}
// Extract token from Authorization header
let token: string | undefined;
const authHeader = headers['authorization'];
if (authHeader?.startsWith('Bearer ')) {
token = authHeader.substring(7);
}
return {
method: request.method,
path: url.pathname,
headers,
query,
body,
rawBody,
token,
};
}
/**
* Convert smartregistry IResponse to Deno Response
*/
private contextResponseToResponse(response: plugins.smartregistry.IResponse): Response {
const headers = new Headers(response.headers || {});
let body: BodyInit | null = null;
if (response.body !== undefined) {
if (typeof response.body === 'string') {
body = response.body;
} else if (response.body instanceof Uint8Array) {
body = response.body as unknown as BodyInit;
} else {
body = JSON.stringify(response.body);
if (!headers.has('content-type')) {
headers.set('content-type', 'application/json');
}
}
}
return new Response(body, {
status: response.status,
headers,
});
}
/**
* Serve static files from embedded UI
*/
@@ -206,7 +293,7 @@ export class StackGalleryRegistry {
// Get embedded file
const embeddedFile = getEmbeddedFile(filePath);
if (embeddedFile) {
return new Response(embeddedFile.data, {
return new Response(embeddedFile.data as unknown as BodyInit, {
status: 200,
headers: { 'Content-Type': embeddedFile.contentType },
});
@@ -215,7 +302,7 @@ export class StackGalleryRegistry {
// SPA fallback: serve index.html for unknown paths
const indexFile = getEmbeddedFile('/index.html');
if (indexFile) {
return new Response(indexFile.data, {
return new Response(indexFile.data as unknown as BodyInit, {
status: 200,
headers: { 'Content-Type': 'text/html' },
});
@@ -229,13 +316,10 @@ export class StackGalleryRegistry {
*/
private async handleApiRequest(request: Request): Promise<Response> {
if (!this.apiRouter) {
return new Response(
JSON.stringify({ error: 'API router not initialized' }),
{
status: 503,
headers: { 'Content-Type': 'application/json' },
}
);
return new Response(JSON.stringify({ error: 'API router not initialized' }), {
status: 503,
headers: { 'Content-Type': 'application/json' },
});
}
return await this.apiRouter.handle(request);
@@ -336,7 +420,9 @@ export async function createRegistryFromEnvFile(): Promise<StackGalleryRegistry>
const s3Endpoint = `${s3Protocol}://${env.S3_HOST || 'localhost'}:${env.S3_PORT || '9000'}`;
const config: IRegistryConfig = {
mongoUrl: env.MONGODB_URL || `mongodb://${env.MONGODB_USER}:${env.MONGODB_PASS}@${env.MONGODB_HOST || 'localhost'}:${env.MONGODB_PORT || '27017'}/${env.MONGODB_NAME}?authSource=admin`,
mongoUrl:
env.MONGODB_URL ||
`mongodb://${env.MONGODB_USER}:${env.MONGODB_PASS}@${env.MONGODB_HOST || 'localhost'}:${env.MONGODB_PORT || '27017'}/${env.MONGODB_NAME}?authSource=admin`,
mongoDb: env.MONGODB_NAME || 'stackgallery',
s3Endpoint: s3Endpoint,
s3AccessKey: env.S3_ACCESSKEY || env.S3_ACCESS_KEY || 'minioadmin',
@@ -356,7 +442,10 @@ export async function createRegistryFromEnvFile(): Promise<StackGalleryRegistry>
if (error instanceof Deno.errors.NotFound) {
console.log('[StackGalleryRegistry] No .nogit/env.json found, using environment variables');
} else {
console.warn('[StackGalleryRegistry] Error reading .nogit/env.json, falling back to env vars:', error);
console.warn(
'[StackGalleryRegistry] Error reading .nogit/env.json, falling back to env vars:',
error
);
}
return createRegistryFromEnv();
}