feat(maven): Add Maven registry protocol support (storage, auth, routing, interfaces, and exports)

This commit is contained in:
2025-11-21 08:58:29 +00:00
parent 29dea2e0e8
commit 0b31219b7d
16 changed files with 2533 additions and 22 deletions

View File

@@ -18,6 +18,39 @@ export class AuthManager {
// In production, this could be Redis or a database
}
// ========================================================================
// UUID TOKEN CREATION (Base method for NPM, Maven, etc.)
// ========================================================================
/**
* Create a UUID-based token with custom scopes (base method)
* @param userId - User ID
* @param protocol - Protocol type
* @param scopes - Permission scopes
* @param readonly - Whether the token is readonly
* @returns UUID token string
*/
private async createUuidToken(
userId: string,
protocol: TRegistryProtocol,
scopes: string[],
readonly: boolean = false
): Promise<string> {
const token = this.generateUuid();
const authToken: IAuthToken = {
type: protocol,
userId,
scopes,
readonly,
metadata: {
created: new Date().toISOString(),
},
};
this.tokenStore.set(token, authToken);
return token;
}
// ========================================================================
// NPM AUTHENTICATION
// ========================================================================
@@ -33,19 +66,8 @@ export class AuthManager {
throw new Error('NPM tokens are not enabled');
}
const token = this.generateUuid();
const authToken: IAuthToken = {
type: 'npm',
userId,
scopes: readonly ? ['npm:*:*:read'] : ['npm:*:*:*'],
readonly,
metadata: {
created: new Date().toISOString(),
},
};
this.tokenStore.set(token, authToken);
return token;
const scopes = readonly ? ['npm:*:*:read'] : ['npm:*:*:*'];
return this.createUuidToken(userId, 'npm', scopes, readonly);
}
/**
@@ -201,8 +223,59 @@ export class AuthManager {
return null;
}
// ========================================================================
// MAVEN AUTHENTICATION
// ========================================================================
/**
* Validate any token (NPM or OCI)
* Create a Maven token
* @param userId - User ID
* @param readonly - Whether the token is readonly
* @returns Maven UUID token
*/
public async createMavenToken(userId: string, readonly: boolean = false): Promise<string> {
const scopes = readonly ? ['maven:*:*:read'] : ['maven:*:*:*'];
return this.createUuidToken(userId, 'maven', scopes, readonly);
}
/**
* Validate a Maven token
* @param token - Maven UUID token
* @returns Auth token object or null
*/
public async validateMavenToken(token: string): Promise<IAuthToken | null> {
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;
}
/**
* Revoke a Maven token
* @param token - Maven UUID token
*/
public async revokeMavenToken(token: string): Promise<void> {
this.tokenStore.delete(token);
}
// ========================================================================
// UNIFIED AUTHENTICATION
// ========================================================================
/**
* Validate any token (NPM, Maven, or OCI)
* @param tokenString - Token string (UUID or JWT)
* @param protocol - Expected protocol type
* @returns Auth token object or null
@@ -211,12 +284,19 @@ export class AuthManager {
tokenString: string,
protocol?: TRegistryProtocol
): Promise<IAuthToken | null> {
// Try NPM token first (UUID format)
// Try UUID-based tokens (NPM, Maven)
if (this.isValidUuid(tokenString)) {
// Try NPM token
const npmToken = await this.validateNpmToken(tokenString);
if (npmToken && (!protocol || protocol === 'npm')) {
return npmToken;
}
// Try Maven token
const mavenToken = await this.validateMavenToken(tokenString);
if (mavenToken && (!protocol || protocol === 'maven')) {
return mavenToken;
}
}
// Try OCI JWT

View File

@@ -267,4 +267,129 @@ export class RegistryStorage implements IStorageBackend {
const safeName = packageName.replace('@', '').replace('/', '-');
return `npm/packages/${packageName}/${safeName}-${version}.tgz`;
}
// ========================================================================
// MAVEN STORAGE METHODS
// ========================================================================
/**
* Get Maven artifact
*/
public async getMavenArtifact(
groupId: string,
artifactId: string,
version: string,
filename: string
): Promise<Buffer | null> {
const path = this.getMavenArtifactPath(groupId, artifactId, version, filename);
return this.getObject(path);
}
/**
* Store Maven artifact
*/
public async putMavenArtifact(
groupId: string,
artifactId: string,
version: string,
filename: string,
data: Buffer
): Promise<void> {
const path = this.getMavenArtifactPath(groupId, artifactId, version, filename);
return this.putObject(path, data);
}
/**
* Check if Maven artifact exists
*/
public async mavenArtifactExists(
groupId: string,
artifactId: string,
version: string,
filename: string
): Promise<boolean> {
const path = this.getMavenArtifactPath(groupId, artifactId, version, filename);
return this.objectExists(path);
}
/**
* Delete Maven artifact
*/
public async deleteMavenArtifact(
groupId: string,
artifactId: string,
version: string,
filename: string
): Promise<void> {
const path = this.getMavenArtifactPath(groupId, artifactId, version, filename);
return this.deleteObject(path);
}
/**
* Get Maven metadata (maven-metadata.xml)
*/
public async getMavenMetadata(
groupId: string,
artifactId: string
): Promise<Buffer | null> {
const path = this.getMavenMetadataPath(groupId, artifactId);
return this.getObject(path);
}
/**
* Store Maven metadata (maven-metadata.xml)
*/
public async putMavenMetadata(
groupId: string,
artifactId: string,
data: Buffer
): Promise<void> {
const path = this.getMavenMetadataPath(groupId, artifactId);
return this.putObject(path, data);
}
/**
* List Maven versions for an artifact
* Returns all version directories under the artifact path
*/
public async listMavenVersions(
groupId: string,
artifactId: string
): Promise<string[]> {
const groupPath = groupId.replace(/\./g, '/');
const prefix = `maven/artifacts/${groupPath}/${artifactId}/`;
const objects = await this.listObjects(prefix);
const versions = new Set<string>();
// Extract version from paths like: maven/artifacts/com/example/my-lib/1.0.0/my-lib-1.0.0.jar
for (const obj of objects) {
const relativePath = obj.substring(prefix.length);
const parts = relativePath.split('/');
if (parts.length >= 1 && parts[0]) {
versions.add(parts[0]);
}
}
return Array.from(versions).sort();
}
// ========================================================================
// MAVEN PATH HELPERS
// ========================================================================
private getMavenArtifactPath(
groupId: string,
artifactId: string,
version: string,
filename: string
): string {
const groupPath = groupId.replace(/\./g, '/');
return `maven/artifacts/${groupPath}/${artifactId}/${version}/${filename}`;
}
private getMavenMetadataPath(groupId: string, artifactId: string): string {
const groupPath = groupId.replace(/\./g, '/');
return `maven/metadata/${groupPath}/${artifactId}/maven-metadata.xml`;
}
}

View File

@@ -5,7 +5,7 @@
/**
* Registry protocol types
*/
export type TRegistryProtocol = 'oci' | 'npm';
export type TRegistryProtocol = 'oci' | 'npm' | 'maven';
/**
* Unified action types across protocols
@@ -89,6 +89,7 @@ export interface IRegistryConfig {
auth: IAuthConfig;
oci?: IProtocolConfig;
npm?: IProtocolConfig;
maven?: IProtocolConfig;
}
/**