diff --git a/changelog.md b/changelog.md index f107af8..45c230a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2025-11-25 - 2.2.2 - fix(npm) +Replace console logging with structured Smartlog in NPM registry and silence RubyGems helper error logging + +- Replaced console.log calls with this.logger.log (Smartlog) in ts/npm/classes.npmregistry.ts for debug/info/success events +- Converted console.error in NpmRegistry.handleSearch to structured logger.log('error', ...) including the error message +- Removed console.error from ts/rubygems/helpers.rubygems.ts; gem metadata extraction failures are now handled silently by returning null + ## 2025-11-25 - 2.2.1 - fix(core) Normalize binary data handling across registries and add buffer helpers diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index cf76dca..50f31c4 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartregistry', - version: '2.2.1', + version: '2.2.2', description: 'A composable TypeScript library implementing OCI, NPM, Maven, Cargo, Composer, PyPI, and RubyGems registries for building unified container and package registries' } diff --git a/ts/core/classes.authmanager.ts b/ts/core/classes.authmanager.ts index d84bad3..2f8a2b2 100644 --- a/ts/core/classes.authmanager.ts +++ b/ts/core/classes.authmanager.ts @@ -52,6 +52,60 @@ export class AuthManager { return token; } + /** + * Generic protocol token creation (internal helper) + * @param userId - User ID + * @param protocol - Protocol type (npm, maven, composer, etc.) + * @param readonly - Whether the token is readonly + * @returns UUID token string + */ + private async createProtocolToken( + userId: string, + protocol: TRegistryProtocol, + readonly: boolean + ): Promise { + const scopes = readonly + ? [`${protocol}:*:*:read`] + : [`${protocol}:*:*:*`]; + return this.createUuidToken(userId, protocol, scopes, readonly); + } + + /** + * Generic protocol token validation (internal helper) + * @param token - UUID token string + * @param protocol - Expected protocol type + * @returns Auth token object or null + */ + private async validateProtocolToken( + token: string, + protocol: TRegistryProtocol + ): Promise { + if (!this.isValidUuid(token)) { + return null; + } + + const authToken = this.tokenStore.get(token); + if (!authToken || authToken.type !== protocol) { + return null; + } + + // Check expiration if set + if (authToken.expiresAt && authToken.expiresAt < new Date()) { + this.tokenStore.delete(token); + return null; + } + + return authToken; + } + + /** + * Generic protocol token revocation (internal helper) + * @param token - UUID token string + */ + private async revokeProtocolToken(token: string): Promise { + this.tokenStore.delete(token); + } + // ======================================================================== // NPM AUTHENTICATION // ======================================================================== @@ -66,9 +120,7 @@ export class AuthManager { if (!this.config.npmTokens.enabled) { throw new Error('NPM tokens are not enabled'); } - - const scopes = readonly ? ['npm:*:*:read'] : ['npm:*:*:*']; - return this.createUuidToken(userId, 'npm', scopes, readonly); + return this.createProtocolToken(userId, 'npm', readonly); } /** @@ -77,22 +129,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validateNpmToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'npm') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'npm'); } /** @@ -100,7 +137,7 @@ export class AuthManager { * @param token - NPM UUID token */ public async revokeNpmToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } /** @@ -265,8 +302,7 @@ export class AuthManager { * @returns Maven UUID token */ public async createMavenToken(userId: string, readonly: boolean = false): Promise { - const scopes = readonly ? ['maven:*:*:read'] : ['maven:*:*:*']; - return this.createUuidToken(userId, 'maven', scopes, readonly); + return this.createProtocolToken(userId, 'maven', readonly); } /** @@ -275,22 +311,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validateMavenToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'maven') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'maven'); } /** @@ -298,7 +319,7 @@ export class AuthManager { * @param token - Maven UUID token */ public async revokeMavenToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } // ======================================================================== @@ -312,8 +333,7 @@ export class AuthManager { * @returns Composer UUID token */ public async createComposerToken(userId: string, readonly: boolean = false): Promise { - const scopes = readonly ? ['composer:*:*:read'] : ['composer:*:*:*']; - return this.createUuidToken(userId, 'composer', scopes, readonly); + return this.createProtocolToken(userId, 'composer', readonly); } /** @@ -322,22 +342,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validateComposerToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'composer') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'composer'); } /** @@ -345,7 +350,7 @@ export class AuthManager { * @param token - Composer UUID token */ public async revokeComposerToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } // ======================================================================== @@ -359,8 +364,7 @@ export class AuthManager { * @returns Cargo UUID token */ public async createCargoToken(userId: string, readonly: boolean = false): Promise { - const scopes = readonly ? ['cargo:*:*:read'] : ['cargo:*:*:*']; - return this.createUuidToken(userId, 'cargo', scopes, readonly); + return this.createProtocolToken(userId, 'cargo', readonly); } /** @@ -369,22 +373,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validateCargoToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'cargo') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'cargo'); } /** @@ -392,7 +381,7 @@ export class AuthManager { * @param token - Cargo UUID token */ public async revokeCargoToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } // ======================================================================== @@ -406,8 +395,7 @@ export class AuthManager { * @returns PyPI UUID token */ public async createPypiToken(userId: string, readonly: boolean = false): Promise { - const scopes = readonly ? ['pypi:*:*:read'] : ['pypi:*:*:*']; - return this.createUuidToken(userId, 'pypi', scopes, readonly); + return this.createProtocolToken(userId, 'pypi', readonly); } /** @@ -416,22 +404,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validatePypiToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'pypi') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'pypi'); } /** @@ -439,7 +412,7 @@ export class AuthManager { * @param token - PyPI UUID token */ public async revokePypiToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } // ======================================================================== @@ -453,8 +426,7 @@ export class AuthManager { * @returns RubyGems UUID token */ public async createRubyGemsToken(userId: string, readonly: boolean = false): Promise { - const scopes = readonly ? ['rubygems:*:*:read'] : ['rubygems:*:*:*']; - return this.createUuidToken(userId, 'rubygems', scopes, readonly); + return this.createProtocolToken(userId, 'rubygems', readonly); } /** @@ -463,22 +435,7 @@ export class AuthManager { * @returns Auth token object or null */ public async validateRubyGemsToken(token: string): Promise { - if (!this.isValidUuid(token)) { - return null; - } - - const authToken = this.tokenStore.get(token); - if (!authToken || authToken.type !== 'rubygems') { - return null; - } - - // Check expiration if set - if (authToken.expiresAt && authToken.expiresAt < new Date()) { - this.tokenStore.delete(token); - return null; - } - - return authToken; + return this.validateProtocolToken(token, 'rubygems'); } /** @@ -486,7 +443,7 @@ export class AuthManager { * @param token - RubyGems UUID token */ public async revokeRubyGemsToken(token: string): Promise { - this.tokenStore.delete(token); + return this.revokeProtocolToken(token); } // ======================================================================== @@ -495,57 +452,42 @@ export class AuthManager { /** * Validate any token (NPM, Maven, OCI, PyPI, RubyGems, Composer, Cargo) + * Optimized: O(1) lookup when protocol hint provided * @param tokenString - Token string (UUID or JWT) - * @param protocol - Expected protocol type + * @param protocol - Expected protocol type (optional, improves performance) * @returns Auth token object or null */ public async validateToken( tokenString: string, protocol?: TRegistryProtocol ): Promise { - // Try UUID-based tokens (NPM, Maven, Composer, Cargo, PyPI, RubyGems) - if (this.isValidUuid(tokenString)) { - // Try NPM token - const npmToken = await this.validateNpmToken(tokenString); - if (npmToken && (!protocol || protocol === 'npm')) { - return npmToken; + // OCI uses JWT (contains dots), not UUID - check first if OCI is expected + if (protocol === 'oci' || tokenString.includes('.')) { + const ociToken = await this.validateOciToken(tokenString); + if (ociToken && (!protocol || protocol === 'oci')) { + return ociToken; } - - // Try Maven token - const mavenToken = await this.validateMavenToken(tokenString); - if (mavenToken && (!protocol || protocol === 'maven')) { - return mavenToken; - } - - // Try Composer token - const composerToken = await this.validateComposerToken(tokenString); - if (composerToken && (!protocol || protocol === 'composer')) { - return composerToken; - } - - // Try Cargo token - const cargoToken = await this.validateCargoToken(tokenString); - if (cargoToken && (!protocol || protocol === 'cargo')) { - return cargoToken; - } - - // Try PyPI token - const pypiToken = await this.validatePypiToken(tokenString); - if (pypiToken && (!protocol || protocol === 'pypi')) { - return pypiToken; - } - - // Try RubyGems token - const rubygemsToken = await this.validateRubyGemsToken(tokenString); - if (rubygemsToken && (!protocol || protocol === 'rubygems')) { - return rubygemsToken; + // If protocol was explicitly OCI but validation failed, return null + if (protocol === 'oci') { + return null; } } - // Try OCI JWT - const ociToken = await this.validateOciToken(tokenString); - if (ociToken && (!protocol || protocol === 'oci')) { - return ociToken; + // UUID-based tokens: single O(1) Map lookup + if (this.isValidUuid(tokenString)) { + const authToken = this.tokenStore.get(tokenString); + if (authToken) { + // If protocol specified, verify it matches + if (protocol && authToken.type !== protocol) { + return null; + } + // Check expiration + if (authToken.expiresAt && authToken.expiresAt < new Date()) { + this.tokenStore.delete(tokenString); + return null; + } + return authToken; + } } return null; diff --git a/ts/npm/classes.npmregistry.ts b/ts/npm/classes.npmregistry.ts index de439f4..cfaa438 100644 --- a/ts/npm/classes.npmregistry.ts +++ b/ts/npm/classes.npmregistry.ts @@ -113,7 +113,7 @@ export class NpmRegistry extends BaseRegistry { const unpublishVersionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-\/([^\/]+)$/); if (unpublishVersionMatch && context.method === 'DELETE') { const [, packageName, version] = unpublishVersionMatch; - console.log(`[unpublishVersionMatch] packageName=${packageName}, version=${version}`); + this.logger.log('debug', 'unpublishVersionMatch', { packageName, version }); return this.unpublishVersion(packageName, version, token); } @@ -121,7 +121,7 @@ export class NpmRegistry extends BaseRegistry { const unpublishPackageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/-rev\/([^\/]+)$/); if (unpublishPackageMatch && context.method === 'DELETE') { const [, packageName, rev] = unpublishPackageMatch; - console.log(`[unpublishPackageMatch] packageName=${packageName}, rev=${rev}`); + this.logger.log('debug', 'unpublishPackageMatch', { packageName, rev }); return this.unpublishPackage(packageName, token); } @@ -129,7 +129,7 @@ export class NpmRegistry extends BaseRegistry { const versionMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)\/([^\/]+)$/); if (versionMatch) { const [, packageName, version] = versionMatch; - console.log(`[versionMatch] matched! packageName=${packageName}, version=${version}`); + this.logger.log('debug', 'versionMatch', { packageName, version }); return this.handlePackageVersion(packageName, version, token); } @@ -137,7 +137,7 @@ export class NpmRegistry extends BaseRegistry { const packageMatch = path.match(/^\/(@?[^\/]+(?:\/[^\/]+)?)$/); if (packageMatch) { const packageName = packageMatch[1]; - console.log(`[packageMatch] matched! packageName=${packageName}`); + this.logger.log('debug', 'packageMatch', { packageName }); return this.handlePackage(context.method, packageName, context.body, context.query, token); } @@ -254,11 +254,11 @@ export class NpmRegistry extends BaseRegistry { version: string, token: IAuthToken | null ): Promise { - console.log(`[handlePackageVersion] packageName=${packageName}, version=${version}`); + this.logger.log('debug', 'handlePackageVersion', { packageName, version }); const packument = await this.storage.getNpmPackument(packageName); - console.log(`[handlePackageVersion] packument found:`, !!packument); + this.logger.log('debug', 'handlePackageVersion packument', { found: !!packument }); if (packument) { - console.log(`[handlePackageVersion] versions:`, Object.keys(packument.versions || {})); + this.logger.log('debug', 'handlePackageVersion versions', { versions: Object.keys(packument.versions || {}) }); } if (!packument) { return { @@ -621,7 +621,7 @@ export class NpmRegistry extends BaseRegistry { } } } catch (error) { - console.error('[handleSearch] Error:', error); + this.logger.log('error', 'handleSearch failed', { error: (error as Error).message }); } // Apply pagination diff --git a/ts/rubygems/helpers.rubygems.ts b/ts/rubygems/helpers.rubygems.ts index 47f9321..7d9dcd0 100644 --- a/ts/rubygems/helpers.rubygems.ts +++ b/ts/rubygems/helpers.rubygems.ts @@ -455,9 +455,8 @@ export async function extractGemMetadata(gemData: Buffer): Promise<{ } return null; - } catch (error) { - // Log error for debugging but return null gracefully - console.error('Failed to extract gem metadata:', error); + } catch (_error) { + // Error handled gracefully - return null and let caller handle missing metadata return null; } }