@push.rocks/smartregistry 2.2.3 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/cargo/classes.cargoregistry.d.ts +7 -1
  3. package/dist_ts/cargo/classes.cargoregistry.js +42 -4
  4. package/dist_ts/cargo/classes.cargoupstream.d.ts +44 -0
  5. package/dist_ts/cargo/classes.cargoupstream.js +129 -0
  6. package/dist_ts/cargo/index.d.ts +1 -0
  7. package/dist_ts/cargo/index.js +2 -1
  8. package/dist_ts/classes.smartregistry.d.ts +33 -2
  9. package/dist_ts/classes.smartregistry.js +45 -12
  10. package/dist_ts/composer/classes.composerregistry.d.ts +7 -1
  11. package/dist_ts/composer/classes.composerregistry.js +34 -3
  12. package/dist_ts/composer/classes.composerupstream.d.ts +40 -0
  13. package/dist_ts/composer/classes.composerupstream.js +159 -0
  14. package/dist_ts/composer/index.d.ts +1 -0
  15. package/dist_ts/composer/index.js +2 -1
  16. package/dist_ts/core/classes.authmanager.d.ts +30 -80
  17. package/dist_ts/core/classes.authmanager.js +63 -337
  18. package/dist_ts/core/classes.defaultauthprovider.d.ts +78 -0
  19. package/dist_ts/core/classes.defaultauthprovider.js +311 -0
  20. package/dist_ts/core/classes.registrystorage.d.ts +70 -4
  21. package/dist_ts/core/classes.registrystorage.js +165 -5
  22. package/dist_ts/core/index.d.ts +3 -0
  23. package/dist_ts/core/index.js +7 -2
  24. package/dist_ts/core/interfaces.auth.d.ts +83 -0
  25. package/dist_ts/core/interfaces.auth.js +2 -0
  26. package/dist_ts/core/interfaces.core.d.ts +38 -0
  27. package/dist_ts/core/interfaces.storage.d.ts +120 -0
  28. package/dist_ts/core/interfaces.storage.js +2 -0
  29. package/dist_ts/index.d.ts +1 -0
  30. package/dist_ts/index.js +3 -1
  31. package/dist_ts/maven/classes.mavenregistry.d.ts +12 -1
  32. package/dist_ts/maven/classes.mavenregistry.js +69 -4
  33. package/dist_ts/maven/classes.mavenupstream.d.ts +45 -0
  34. package/dist_ts/maven/classes.mavenupstream.js +153 -0
  35. package/dist_ts/maven/index.d.ts +1 -0
  36. package/dist_ts/maven/index.js +2 -1
  37. package/dist_ts/npm/classes.npmregistry.d.ts +3 -1
  38. package/dist_ts/npm/classes.npmregistry.js +55 -6
  39. package/dist_ts/npm/classes.npmupstream.d.ts +51 -0
  40. package/dist_ts/npm/classes.npmupstream.js +206 -0
  41. package/dist_ts/npm/index.d.ts +1 -0
  42. package/dist_ts/npm/index.js +2 -1
  43. package/dist_ts/oci/classes.ociregistry.d.ts +4 -1
  44. package/dist_ts/oci/classes.ociregistry.js +78 -17
  45. package/dist_ts/oci/classes.ociupstream.d.ts +62 -0
  46. package/dist_ts/oci/classes.ociupstream.js +206 -0
  47. package/dist_ts/oci/index.d.ts +1 -0
  48. package/dist_ts/oci/index.js +2 -1
  49. package/dist_ts/plugins.d.ts +4 -1
  50. package/dist_ts/plugins.js +6 -2
  51. package/dist_ts/pypi/classes.pypiregistry.d.ts +7 -1
  52. package/dist_ts/pypi/classes.pypiregistry.js +60 -4
  53. package/dist_ts/pypi/classes.pypiupstream.d.ts +48 -0
  54. package/dist_ts/pypi/classes.pypiupstream.js +165 -0
  55. package/dist_ts/pypi/index.d.ts +1 -0
  56. package/dist_ts/pypi/index.js +2 -1
  57. package/dist_ts/rubygems/classes.rubygemsregistry.d.ts +7 -1
  58. package/dist_ts/rubygems/classes.rubygemsregistry.js +35 -4
  59. package/dist_ts/rubygems/classes.rubygemsupstream.d.ts +47 -0
  60. package/dist_ts/rubygems/classes.rubygemsupstream.js +184 -0
  61. package/dist_ts/rubygems/index.d.ts +1 -0
  62. package/dist_ts/rubygems/index.js +2 -1
  63. package/dist_ts/upstream/classes.baseupstream.d.ts +112 -0
  64. package/dist_ts/upstream/classes.baseupstream.js +411 -0
  65. package/dist_ts/upstream/classes.circuitbreaker.d.ts +111 -0
  66. package/dist_ts/upstream/classes.circuitbreaker.js +192 -0
  67. package/dist_ts/upstream/classes.upstreamcache.d.ts +170 -0
  68. package/dist_ts/upstream/classes.upstreamcache.js +485 -0
  69. package/dist_ts/upstream/index.d.ts +6 -0
  70. package/dist_ts/upstream/index.js +7 -0
  71. package/dist_ts/upstream/interfaces.upstream.d.ts +169 -0
  72. package/dist_ts/upstream/interfaces.upstream.js +23 -0
  73. package/package.json +4 -2
  74. package/ts/00_commitinfo_data.ts +1 -1
  75. package/ts/cargo/classes.cargoregistry.ts +48 -3
  76. package/ts/cargo/classes.cargoupstream.ts +159 -0
  77. package/ts/cargo/index.ts +1 -0
  78. package/ts/classes.smartregistry.ts +88 -11
  79. package/ts/composer/classes.composerregistry.ts +39 -2
  80. package/ts/composer/classes.composerupstream.ts +200 -0
  81. package/ts/composer/index.ts +1 -0
  82. package/ts/core/classes.authmanager.ts +74 -412
  83. package/ts/core/classes.defaultauthprovider.ts +393 -0
  84. package/ts/core/classes.registrystorage.ts +199 -5
  85. package/ts/core/index.ts +8 -1
  86. package/ts/core/interfaces.auth.ts +91 -0
  87. package/ts/core/interfaces.core.ts +42 -0
  88. package/ts/core/interfaces.storage.ts +130 -0
  89. package/ts/index.ts +3 -0
  90. package/ts/maven/classes.mavenregistry.ts +84 -3
  91. package/ts/maven/classes.mavenupstream.ts +220 -0
  92. package/ts/maven/index.ts +1 -0
  93. package/ts/npm/classes.npmregistry.ts +61 -5
  94. package/ts/npm/classes.npmupstream.ts +260 -0
  95. package/ts/npm/index.ts +1 -0
  96. package/ts/oci/classes.ociregistry.ts +89 -17
  97. package/ts/oci/classes.ociupstream.ts +263 -0
  98. package/ts/oci/index.ts +1 -0
  99. package/ts/plugins.ts +7 -1
  100. package/ts/pypi/classes.pypiregistry.ts +68 -3
  101. package/ts/pypi/classes.pypiupstream.ts +211 -0
  102. package/ts/pypi/index.ts +1 -0
  103. package/ts/rubygems/classes.rubygemsregistry.ts +40 -3
  104. package/ts/rubygems/classes.rubygemsupstream.ts +230 -0
  105. package/ts/rubygems/index.ts +1 -0
  106. package/ts/upstream/classes.baseupstream.ts +526 -0
  107. package/ts/upstream/classes.circuitbreaker.ts +238 -0
  108. package/ts/upstream/classes.upstreamcache.ts +626 -0
  109. package/ts/upstream/index.ts +11 -0
  110. package/ts/upstream/interfaces.upstream.ts +195 -0
@@ -1,109 +1,79 @@
1
1
  import type { IAuthConfig, IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
2
- import * as crypto from 'crypto';
2
+ import type { IAuthProvider, ITokenOptions } from './interfaces.auth.js';
3
+ import { DefaultAuthProvider } from './classes.defaultauthprovider.js';
3
4
 
4
5
  /**
5
- * Unified authentication manager for all registry protocols
6
- * Handles both NPM UUID tokens and OCI JWT tokens
6
+ * Unified authentication manager for all registry protocols.
7
+ * Delegates to a pluggable IAuthProvider for actual auth operations.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Use default in-memory provider
12
+ * const auth = new AuthManager(config);
13
+ *
14
+ * // Use custom provider (LDAP, OAuth, etc.)
15
+ * const auth = new AuthManager(config, new LdapAuthProvider(ldapClient));
16
+ * ```
7
17
  */
8
18
  export class AuthManager {
9
- private tokenStore: Map<string, IAuthToken> = new Map();
10
- private userCredentials: Map<string, string> = new Map(); // username -> password hash (mock)
19
+ private provider: IAuthProvider;
11
20
 
12
- constructor(private config: IAuthConfig) {}
21
+ constructor(
22
+ private config: IAuthConfig,
23
+ provider?: IAuthProvider
24
+ ) {
25
+ // Use provided provider or default in-memory implementation
26
+ this.provider = provider || new DefaultAuthProvider(config);
27
+ }
13
28
 
14
29
  /**
15
30
  * Initialize the auth manager
16
31
  */
17
32
  public async init(): Promise<void> {
18
- // Initialize token store (in-memory for now)
19
- // In production, this could be Redis or a database
33
+ if (this.provider.init) {
34
+ await this.provider.init();
35
+ }
20
36
  }
21
37
 
22
38
  // ========================================================================
23
- // UUID TOKEN CREATION (Base method for NPM, Maven, etc.)
39
+ // UNIFIED AUTHENTICATION (Delegated to Provider)
24
40
  // ========================================================================
25
41
 
26
42
  /**
27
- * Create a UUID-based token with custom scopes (base method)
28
- * @param userId - User ID
29
- * @param protocol - Protocol type
30
- * @param scopes - Permission scopes
31
- * @param readonly - Whether the token is readonly
32
- * @returns UUID token string
33
- */
34
- private async createUuidToken(
35
- userId: string,
36
- protocol: TRegistryProtocol,
37
- scopes: string[],
38
- readonly: boolean = false
39
- ): Promise<string> {
40
- const token = this.generateUuid();
41
- const authToken: IAuthToken = {
42
- type: protocol,
43
- userId,
44
- scopes,
45
- readonly,
46
- metadata: {
47
- created: new Date().toISOString(),
48
- },
49
- };
50
-
51
- this.tokenStore.set(token, authToken);
52
- return token;
53
- }
54
-
55
- /**
56
- * Generic protocol token creation (internal helper)
57
- * @param userId - User ID
58
- * @param protocol - Protocol type (npm, maven, composer, etc.)
59
- * @param readonly - Whether the token is readonly
60
- * @returns UUID token string
43
+ * Authenticate user credentials
44
+ * @param credentials - Username and password
45
+ * @returns User ID or null
61
46
  */
62
- private async createProtocolToken(
63
- userId: string,
64
- protocol: TRegistryProtocol,
65
- readonly: boolean
66
- ): Promise<string> {
67
- const scopes = readonly
68
- ? [`${protocol}:*:*:read`]
69
- : [`${protocol}:*:*:*`];
70
- return this.createUuidToken(userId, protocol, scopes, readonly);
47
+ public async authenticate(credentials: ICredentials): Promise<string | null> {
48
+ return this.provider.authenticate(credentials);
71
49
  }
72
50
 
73
51
  /**
74
- * Generic protocol token validation (internal helper)
75
- * @param token - UUID token string
76
- * @param protocol - Expected protocol type
52
+ * Validate any token (NPM, Maven, OCI, PyPI, RubyGems, Composer, Cargo)
53
+ * @param tokenString - Token string (UUID or JWT)
54
+ * @param protocol - Expected protocol type (optional, improves performance)
77
55
  * @returns Auth token object or null
78
56
  */
79
- private async validateProtocolToken(
80
- token: string,
81
- protocol: TRegistryProtocol
57
+ public async validateToken(
58
+ tokenString: string,
59
+ protocol?: TRegistryProtocol
82
60
  ): Promise<IAuthToken | null> {
83
- if (!this.isValidUuid(token)) {
84
- return null;
85
- }
86
-
87
- const authToken = this.tokenStore.get(token);
88
- if (!authToken || authToken.type !== protocol) {
89
- return null;
90
- }
91
-
92
- // Check expiration if set
93
- if (authToken.expiresAt && authToken.expiresAt < new Date()) {
94
- this.tokenStore.delete(token);
95
- return null;
96
- }
97
-
98
- return authToken;
61
+ return this.provider.validateToken(tokenString, protocol);
99
62
  }
100
63
 
101
64
  /**
102
- * Generic protocol token revocation (internal helper)
103
- * @param token - UUID token string
65
+ * Check if token has permission for an action
66
+ * @param token - Auth token (or null for anonymous)
67
+ * @param resource - Resource being accessed (e.g., "npm:package:foo")
68
+ * @param action - Action being performed (read, write, push, pull, delete)
69
+ * @returns true if authorized
104
70
  */
105
- private async revokeProtocolToken(token: string): Promise<void> {
106
- this.tokenStore.delete(token);
71
+ public async authorize(
72
+ token: IAuthToken | null,
73
+ resource: string,
74
+ action: string
75
+ ): Promise<boolean> {
76
+ return this.provider.authorize(token, resource, action);
107
77
  }
108
78
 
109
79
  // ========================================================================
@@ -120,7 +90,7 @@ export class AuthManager {
120
90
  if (!this.config.npmTokens.enabled) {
121
91
  throw new Error('NPM tokens are not enabled');
122
92
  }
123
- return this.createProtocolToken(userId, 'npm', readonly);
93
+ return this.provider.createToken(userId, 'npm', { readonly });
124
94
  }
125
95
 
126
96
  /**
@@ -129,7 +99,7 @@ export class AuthManager {
129
99
  * @returns Auth token object or null
130
100
  */
131
101
  public async validateNpmToken(token: string): Promise<IAuthToken | null> {
132
- return this.validateProtocolToken(token, 'npm');
102
+ return this.provider.validateToken(token, 'npm');
133
103
  }
134
104
 
135
105
  /**
@@ -137,7 +107,7 @@ export class AuthManager {
137
107
  * @param token - NPM UUID token
138
108
  */
139
109
  public async revokeNpmToken(token: string): Promise<void> {
140
- return this.revokeProtocolToken(token);
110
+ return this.provider.revokeToken(token);
141
111
  }
142
112
 
143
113
  /**
@@ -149,20 +119,12 @@ export class AuthManager {
149
119
  key: string;
150
120
  readonly: boolean;
151
121
  created: string;
122
+ protocol?: TRegistryProtocol;
152
123
  }>> {
153
- const tokens: Array<{key: string; readonly: boolean; created: string}> = [];
154
-
155
- for (const [token, authToken] of this.tokenStore.entries()) {
156
- if (authToken.userId === userId) {
157
- tokens.push({
158
- key: this.hashToken(token),
159
- readonly: authToken.readonly || false,
160
- created: authToken.metadata?.created || 'unknown',
161
- });
162
- }
124
+ if (this.provider.listUserTokens) {
125
+ return this.provider.listUserTokens(userId);
163
126
  }
164
-
165
- return tokens;
127
+ return [];
166
128
  }
167
129
 
168
130
  // ========================================================================
@@ -174,39 +136,17 @@ export class AuthManager {
174
136
  * @param userId - User ID
175
137
  * @param scopes - Permission scopes
176
138
  * @param expiresIn - Expiration time in seconds
177
- * @returns JWT token string (HMAC-SHA256 signed)
139
+ * @returns JWT token string
178
140
  */
179
141
  public async createOciToken(
180
142
  userId: string,
181
143
  scopes: string[],
182
144
  expiresIn: number = 3600
183
145
  ): Promise<string> {
184
- if (!this.config.ociTokens.enabled) {
146
+ if (!this.config.ociTokens?.enabled) {
185
147
  throw new Error('OCI tokens are not enabled');
186
148
  }
187
-
188
- const now = Math.floor(Date.now() / 1000);
189
- const payload = {
190
- iss: this.config.ociTokens.realm,
191
- sub: userId,
192
- aud: this.config.ociTokens.service,
193
- exp: now + expiresIn,
194
- nbf: now,
195
- iat: now,
196
- access: this.scopesToOciAccess(scopes),
197
- };
198
-
199
- // Create JWT with HMAC-SHA256 signature
200
- const header = { alg: 'HS256', typ: 'JWT' };
201
- const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
202
- const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
203
-
204
- const signature = crypto
205
- .createHmac('sha256', this.config.jwtSecret)
206
- .update(`${headerB64}.${payloadB64}`)
207
- .digest('base64url');
208
-
209
- return `${headerB64}.${payloadB64}.${signature}`;
149
+ return this.provider.createToken(userId, 'oci', { scopes, expiresIn });
210
150
  }
211
151
 
212
152
  /**
@@ -215,80 +155,7 @@ export class AuthManager {
215
155
  * @returns Auth token object or null
216
156
  */
217
157
  public async validateOciToken(jwt: string): Promise<IAuthToken | null> {
218
- try {
219
- const parts = jwt.split('.');
220
- if (parts.length !== 3) {
221
- return null;
222
- }
223
-
224
- const [headerB64, payloadB64, signatureB64] = parts;
225
-
226
- // Verify signature
227
- const expectedSignature = crypto
228
- .createHmac('sha256', this.config.jwtSecret)
229
- .update(`${headerB64}.${payloadB64}`)
230
- .digest('base64url');
231
-
232
- if (signatureB64 !== expectedSignature) {
233
- return null;
234
- }
235
-
236
- // Decode and parse payload
237
- const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8'));
238
-
239
- // Check expiration
240
- const now = Math.floor(Date.now() / 1000);
241
- if (payload.exp && payload.exp < now) {
242
- return null;
243
- }
244
-
245
- // Check not-before time
246
- if (payload.nbf && payload.nbf > now) {
247
- return null;
248
- }
249
-
250
- // Convert to unified token format
251
- const scopes = this.ociAccessToScopes(payload.access || []);
252
-
253
- return {
254
- type: 'oci',
255
- userId: payload.sub,
256
- scopes,
257
- expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
258
- metadata: {
259
- iss: payload.iss,
260
- aud: payload.aud,
261
- },
262
- };
263
- } catch (error) {
264
- return null;
265
- }
266
- }
267
-
268
- // ========================================================================
269
- // UNIFIED AUTHENTICATION
270
- // ========================================================================
271
-
272
- /**
273
- * Authenticate user credentials
274
- * @param credentials - Username and password
275
- * @returns User ID or null
276
- */
277
- public async authenticate(credentials: ICredentials): Promise<string | null> {
278
- // Mock authentication - in production, verify against database
279
- const storedPassword = this.userCredentials.get(credentials.username);
280
-
281
- if (!storedPassword) {
282
- // Auto-register for testing (remove in production)
283
- this.userCredentials.set(credentials.username, credentials.password);
284
- return credentials.username;
285
- }
286
-
287
- if (storedPassword === credentials.password) {
288
- return credentials.username;
289
- }
290
-
291
- return null;
158
+ return this.provider.validateToken(jwt, 'oci');
292
159
  }
293
160
 
294
161
  // ========================================================================
@@ -302,7 +169,7 @@ export class AuthManager {
302
169
  * @returns Maven UUID token
303
170
  */
304
171
  public async createMavenToken(userId: string, readonly: boolean = false): Promise<string> {
305
- return this.createProtocolToken(userId, 'maven', readonly);
172
+ return this.provider.createToken(userId, 'maven', { readonly });
306
173
  }
307
174
 
308
175
  /**
@@ -311,7 +178,7 @@ export class AuthManager {
311
178
  * @returns Auth token object or null
312
179
  */
313
180
  public async validateMavenToken(token: string): Promise<IAuthToken | null> {
314
- return this.validateProtocolToken(token, 'maven');
181
+ return this.provider.validateToken(token, 'maven');
315
182
  }
316
183
 
317
184
  /**
@@ -319,7 +186,7 @@ export class AuthManager {
319
186
  * @param token - Maven UUID token
320
187
  */
321
188
  public async revokeMavenToken(token: string): Promise<void> {
322
- return this.revokeProtocolToken(token);
189
+ return this.provider.revokeToken(token);
323
190
  }
324
191
 
325
192
  // ========================================================================
@@ -333,7 +200,7 @@ export class AuthManager {
333
200
  * @returns Composer UUID token
334
201
  */
335
202
  public async createComposerToken(userId: string, readonly: boolean = false): Promise<string> {
336
- return this.createProtocolToken(userId, 'composer', readonly);
203
+ return this.provider.createToken(userId, 'composer', { readonly });
337
204
  }
338
205
 
339
206
  /**
@@ -342,7 +209,7 @@ export class AuthManager {
342
209
  * @returns Auth token object or null
343
210
  */
344
211
  public async validateComposerToken(token: string): Promise<IAuthToken | null> {
345
- return this.validateProtocolToken(token, 'composer');
212
+ return this.provider.validateToken(token, 'composer');
346
213
  }
347
214
 
348
215
  /**
@@ -350,7 +217,7 @@ export class AuthManager {
350
217
  * @param token - Composer UUID token
351
218
  */
352
219
  public async revokeComposerToken(token: string): Promise<void> {
353
- return this.revokeProtocolToken(token);
220
+ return this.provider.revokeToken(token);
354
221
  }
355
222
 
356
223
  // ========================================================================
@@ -364,7 +231,7 @@ export class AuthManager {
364
231
  * @returns Cargo UUID token
365
232
  */
366
233
  public async createCargoToken(userId: string, readonly: boolean = false): Promise<string> {
367
- return this.createProtocolToken(userId, 'cargo', readonly);
234
+ return this.provider.createToken(userId, 'cargo', { readonly });
368
235
  }
369
236
 
370
237
  /**
@@ -373,7 +240,7 @@ export class AuthManager {
373
240
  * @returns Auth token object or null
374
241
  */
375
242
  public async validateCargoToken(token: string): Promise<IAuthToken | null> {
376
- return this.validateProtocolToken(token, 'cargo');
243
+ return this.provider.validateToken(token, 'cargo');
377
244
  }
378
245
 
379
246
  /**
@@ -381,7 +248,7 @@ export class AuthManager {
381
248
  * @param token - Cargo UUID token
382
249
  */
383
250
  public async revokeCargoToken(token: string): Promise<void> {
384
- return this.revokeProtocolToken(token);
251
+ return this.provider.revokeToken(token);
385
252
  }
386
253
 
387
254
  // ========================================================================
@@ -395,7 +262,7 @@ export class AuthManager {
395
262
  * @returns PyPI UUID token
396
263
  */
397
264
  public async createPypiToken(userId: string, readonly: boolean = false): Promise<string> {
398
- return this.createProtocolToken(userId, 'pypi', readonly);
265
+ return this.provider.createToken(userId, 'pypi', { readonly });
399
266
  }
400
267
 
401
268
  /**
@@ -404,7 +271,7 @@ export class AuthManager {
404
271
  * @returns Auth token object or null
405
272
  */
406
273
  public async validatePypiToken(token: string): Promise<IAuthToken | null> {
407
- return this.validateProtocolToken(token, 'pypi');
274
+ return this.provider.validateToken(token, 'pypi');
408
275
  }
409
276
 
410
277
  /**
@@ -412,7 +279,7 @@ export class AuthManager {
412
279
  * @param token - PyPI UUID token
413
280
  */
414
281
  public async revokePypiToken(token: string): Promise<void> {
415
- return this.revokeProtocolToken(token);
282
+ return this.provider.revokeToken(token);
416
283
  }
417
284
 
418
285
  // ========================================================================
@@ -426,7 +293,7 @@ export class AuthManager {
426
293
  * @returns RubyGems UUID token
427
294
  */
428
295
  public async createRubyGemsToken(userId: string, readonly: boolean = false): Promise<string> {
429
- return this.createProtocolToken(userId, 'rubygems', readonly);
296
+ return this.provider.createToken(userId, 'rubygems', { readonly });
430
297
  }
431
298
 
432
299
  /**
@@ -435,7 +302,7 @@ export class AuthManager {
435
302
  * @returns Auth token object or null
436
303
  */
437
304
  public async validateRubyGemsToken(token: string): Promise<IAuthToken | null> {
438
- return this.validateProtocolToken(token, 'rubygems');
305
+ return this.provider.validateToken(token, 'rubygems');
439
306
  }
440
307
 
441
308
  /**
@@ -443,211 +310,6 @@ export class AuthManager {
443
310
  * @param token - RubyGems UUID token
444
311
  */
445
312
  public async revokeRubyGemsToken(token: string): Promise<void> {
446
- return this.revokeProtocolToken(token);
447
- }
448
-
449
- // ========================================================================
450
- // UNIFIED AUTHENTICATION
451
- // ========================================================================
452
-
453
- /**
454
- * Validate any token (NPM, Maven, OCI, PyPI, RubyGems, Composer, Cargo)
455
- * Optimized: O(1) lookup when protocol hint provided
456
- * @param tokenString - Token string (UUID or JWT)
457
- * @param protocol - Expected protocol type (optional, improves performance)
458
- * @returns Auth token object or null
459
- */
460
- public async validateToken(
461
- tokenString: string,
462
- protocol?: TRegistryProtocol
463
- ): Promise<IAuthToken | null> {
464
- // OCI uses JWT (contains dots), not UUID - check first if OCI is expected
465
- if (protocol === 'oci' || tokenString.includes('.')) {
466
- const ociToken = await this.validateOciToken(tokenString);
467
- if (ociToken && (!protocol || protocol === 'oci')) {
468
- return ociToken;
469
- }
470
- // If protocol was explicitly OCI but validation failed, return null
471
- if (protocol === 'oci') {
472
- return null;
473
- }
474
- }
475
-
476
- // UUID-based tokens: single O(1) Map lookup
477
- if (this.isValidUuid(tokenString)) {
478
- const authToken = this.tokenStore.get(tokenString);
479
- if (authToken) {
480
- // If protocol specified, verify it matches
481
- if (protocol && authToken.type !== protocol) {
482
- return null;
483
- }
484
- // Check expiration
485
- if (authToken.expiresAt && authToken.expiresAt < new Date()) {
486
- this.tokenStore.delete(tokenString);
487
- return null;
488
- }
489
- return authToken;
490
- }
491
- }
492
-
493
- return null;
494
- }
495
-
496
- /**
497
- * Check if token has permission for an action
498
- * @param token - Auth token
499
- * @param resource - Resource being accessed (e.g., "package:foo" or "repository:bar")
500
- * @param action - Action being performed (read, write, push, pull, delete)
501
- * @returns true if authorized
502
- */
503
- public async authorize(
504
- token: IAuthToken | null,
505
- resource: string,
506
- action: string
507
- ): Promise<boolean> {
508
- if (!token) {
509
- return false;
510
- }
511
-
512
- // Check readonly flag
513
- if (token.readonly && ['write', 'push', 'delete'].includes(action)) {
514
- return false;
515
- }
516
-
517
- // Check scopes
518
- for (const scope of token.scopes) {
519
- if (this.matchesScope(scope, resource, action)) {
520
- return true;
521
- }
522
- }
523
-
524
- return false;
525
- }
526
-
527
- // ========================================================================
528
- // HELPER METHODS
529
- // ========================================================================
530
-
531
- /**
532
- * Check if a scope matches a resource and action
533
- * Scope format: "{protocol}:{type}:{name}:{action}"
534
- * Examples:
535
- * - "npm:*:*" - All NPM access
536
- * - "npm:package:foo:*" - All actions on package foo
537
- * - "npm:package:foo:read" - Read-only on package foo
538
- * - "oci:repository:*:pull" - Pull from any OCI repo
539
- */
540
- private matchesScope(scope: string, resource: string, action: string): boolean {
541
- const scopeParts = scope.split(':');
542
- const resourceParts = resource.split(':');
543
-
544
- // Scope must have at least protocol:type:name:action
545
- if (scopeParts.length < 4) {
546
- return false;
547
- }
548
-
549
- const [scopeProtocol, scopeType, scopeName, scopeAction] = scopeParts;
550
- const [resourceProtocol, resourceType, resourceName] = resourceParts;
551
-
552
- // Check protocol
553
- if (scopeProtocol !== '*' && scopeProtocol !== resourceProtocol) {
554
- return false;
555
- }
556
-
557
- // Check type
558
- if (scopeType !== '*' && scopeType !== resourceType) {
559
- return false;
560
- }
561
-
562
- // Check name
563
- if (scopeName !== '*' && scopeName !== resourceName) {
564
- return false;
565
- }
566
-
567
- // Check action
568
- if (scopeAction !== '*' && scopeAction !== action) {
569
- // Map action aliases
570
- const actionAliases: Record<string, string[]> = {
571
- read: ['pull', 'get'],
572
- write: ['push', 'put', 'post'],
573
- };
574
-
575
- const aliases = actionAliases[scopeAction] || [];
576
- if (!aliases.includes(action)) {
577
- return false;
578
- }
579
- }
580
-
581
- return true;
582
- }
583
-
584
- /**
585
- * Convert unified scopes to OCI access array
586
- */
587
- private scopesToOciAccess(scopes: string[]): Array<{
588
- type: string;
589
- name: string;
590
- actions: string[];
591
- }> {
592
- const access: Array<{type: string; name: string; actions: string[]}> = [];
593
-
594
- for (const scope of scopes) {
595
- const parts = scope.split(':');
596
- if (parts.length >= 4 && parts[0] === 'oci') {
597
- access.push({
598
- type: parts[1],
599
- name: parts[2],
600
- actions: [parts[3]],
601
- });
602
- }
603
- }
604
-
605
- return access;
606
- }
607
-
608
- /**
609
- * Convert OCI access array to unified scopes
610
- */
611
- private ociAccessToScopes(access: Array<{
612
- type: string;
613
- name: string;
614
- actions: string[];
615
- }>): string[] {
616
- const scopes: string[] = [];
617
-
618
- for (const item of access) {
619
- for (const action of item.actions) {
620
- scopes.push(`oci:${item.type}:${item.name}:${action}`);
621
- }
622
- }
623
-
624
- return scopes;
625
- }
626
-
627
- /**
628
- * Generate UUID for NPM tokens
629
- */
630
- private generateUuid(): string {
631
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
632
- const r = (Math.random() * 16) | 0;
633
- const v = c === 'x' ? r : (r & 0x3) | 0x8;
634
- return v.toString(16);
635
- });
636
- }
637
-
638
- /**
639
- * Check if string is a valid UUID
640
- */
641
- private isValidUuid(str: string): boolean {
642
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
643
- return uuidRegex.test(str);
644
- }
645
-
646
- /**
647
- * Hash a token for identification (SHA-512 mock)
648
- */
649
- private hashToken(token: string): string {
650
- // In production, use actual SHA-512
651
- return `sha512-${token.substring(0, 16)}...`;
313
+ return this.provider.revokeToken(token);
652
314
  }
653
315
  }