feat(auth): Implement HMAC-SHA256 OCI JWTs; enhance PyPI & RubyGems uploads and normalize responses

This commit is contained in:
2025-11-25 14:28:19 +00:00
parent 2d6059ba7f
commit 547c262578
14 changed files with 765 additions and 158 deletions

View File

@@ -1,4 +1,5 @@
import type { IAuthConfig, IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
import * as crypto from 'crypto';
/**
* Unified authentication manager for all registry protocols
@@ -136,7 +137,7 @@ export class AuthManager {
* @param userId - User ID
* @param scopes - Permission scopes
* @param expiresIn - Expiration time in seconds
* @returns JWT token string
* @returns JWT token string (HMAC-SHA256 signed)
*/
public async createOciToken(
userId: string,
@@ -158,9 +159,17 @@ export class AuthManager {
access: this.scopesToOciAccess(scopes),
};
// In production, use proper JWT library with signing
// For now, return JSON string (mock JWT)
return JSON.stringify(payload);
// Create JWT with HMAC-SHA256 signature
const header = { alg: 'HS256', typ: 'JWT' };
const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
const signature = crypto
.createHmac('sha256', this.config.jwtSecret)
.update(`${headerB64}.${payloadB64}`)
.digest('base64url');
return `${headerB64}.${payloadB64}.${signature}`;
}
/**
@@ -170,8 +179,25 @@ export class AuthManager {
*/
public async validateOciToken(jwt: string): Promise<IAuthToken | null> {
try {
// In production, verify JWT signature
const payload = JSON.parse(jwt);
const parts = jwt.split('.');
if (parts.length !== 3) {
return null;
}
const [headerB64, payloadB64, signatureB64] = parts;
// Verify signature
const expectedSignature = crypto
.createHmac('sha256', this.config.jwtSecret)
.update(`${headerB64}.${payloadB64}`)
.digest('base64url');
if (signatureB64 !== expectedSignature) {
return null;
}
// Decode and parse payload
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8'));
// Check expiration
const now = Math.floor(Date.now() / 1000);
@@ -179,6 +205,11 @@ export class AuthManager {
return null;
}
// Check not-before time
if (payload.nbf && payload.nbf > now) {
return null;
}
// Convert to unified token format
const scopes = this.ociAccessToScopes(payload.access || []);