|
|
|
|
@@ -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<string> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<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;
|
|
|
|
|
return this.validateProtocolToken(token, 'maven');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -298,7 +319,7 @@ export class AuthManager {
|
|
|
|
|
* @param token - Maven UUID token
|
|
|
|
|
*/
|
|
|
|
|
public async revokeMavenToken(token: string): Promise<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<string> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
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<void> {
|
|
|
|
|
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<IAuthToken | null> {
|
|
|
|
|
// 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;
|
|
|
|
|
|