@push.rocks/smartregistry 2.3.0 → 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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.smartregistry.d.ts +33 -2
- package/dist_ts/classes.smartregistry.js +38 -5
- package/dist_ts/core/classes.authmanager.d.ts +30 -80
- package/dist_ts/core/classes.authmanager.js +63 -337
- package/dist_ts/core/classes.defaultauthprovider.d.ts +78 -0
- package/dist_ts/core/classes.defaultauthprovider.js +311 -0
- package/dist_ts/core/classes.registrystorage.d.ts +70 -4
- package/dist_ts/core/classes.registrystorage.js +165 -5
- package/dist_ts/core/index.d.ts +3 -0
- package/dist_ts/core/index.js +7 -2
- package/dist_ts/core/interfaces.auth.d.ts +83 -0
- package/dist_ts/core/interfaces.auth.js +2 -0
- package/dist_ts/core/interfaces.core.d.ts +35 -0
- package/dist_ts/core/interfaces.storage.d.ts +120 -0
- package/dist_ts/core/interfaces.storage.js +2 -0
- package/dist_ts/upstream/classes.baseupstream.d.ts +2 -2
- package/dist_ts/upstream/classes.baseupstream.js +16 -14
- package/dist_ts/upstream/classes.upstreamcache.d.ts +69 -22
- package/dist_ts/upstream/classes.upstreamcache.js +207 -50
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.smartregistry.ts +39 -4
- package/ts/core/classes.authmanager.ts +74 -412
- package/ts/core/classes.defaultauthprovider.ts +393 -0
- package/ts/core/classes.registrystorage.ts +199 -5
- package/ts/core/index.ts +8 -1
- package/ts/core/interfaces.auth.ts +91 -0
- package/ts/core/interfaces.core.ts +39 -0
- package/ts/core/interfaces.storage.ts +130 -0
- package/ts/upstream/classes.baseupstream.ts +20 -15
- package/ts/upstream/classes.upstreamcache.ts +256 -53
|
@@ -1,109 +1,79 @@
|
|
|
1
1
|
import type { IAuthConfig, IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
|
|
2
|
-
import
|
|
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
|
-
*
|
|
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
|
|
10
|
-
private userCredentials: Map<string, string> = new Map(); // username -> password hash (mock)
|
|
19
|
+
private provider: IAuthProvider;
|
|
11
20
|
|
|
12
|
-
constructor(
|
|
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
|
-
|
|
19
|
-
|
|
33
|
+
if (this.provider.init) {
|
|
34
|
+
await this.provider.init();
|
|
35
|
+
}
|
|
20
36
|
}
|
|
21
37
|
|
|
22
38
|
// ========================================================================
|
|
23
|
-
//
|
|
39
|
+
// UNIFIED AUTHENTICATION (Delegated to Provider)
|
|
24
40
|
// ========================================================================
|
|
25
41
|
|
|
26
42
|
/**
|
|
27
|
-
*
|
|
28
|
-
* @param
|
|
29
|
-
* @
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
*
|
|
75
|
-
* @param
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
protocol
|
|
57
|
+
public async validateToken(
|
|
58
|
+
tokenString: string,
|
|
59
|
+
protocol?: TRegistryProtocol
|
|
82
60
|
): Promise<IAuthToken | null> {
|
|
83
|
-
|
|
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
|
-
*
|
|
103
|
-
* @param token -
|
|
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
|
-
|
|
106
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|