@push.rocks/smartregistry 1.1.1
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.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.smartregistry.d.ts +45 -0
- package/dist_ts/classes.smartregistry.js +113 -0
- package/dist_ts/core/classes.authmanager.d.ts +108 -0
- package/dist_ts/core/classes.authmanager.js +315 -0
- package/dist_ts/core/classes.baseregistry.d.ts +28 -0
- package/dist_ts/core/classes.baseregistry.js +6 -0
- package/dist_ts/core/classes.registrystorage.d.ts +109 -0
- package/dist_ts/core/classes.registrystorage.js +226 -0
- package/dist_ts/core/index.d.ts +7 -0
- package/dist_ts/core/index.js +10 -0
- package/dist_ts/core/interfaces.core.d.ts +142 -0
- package/dist_ts/core/interfaces.core.js +5 -0
- package/dist_ts/index.d.ts +8 -0
- package/dist_ts/index.js +13 -0
- package/dist_ts/npm/classes.npmregistry.d.ts +36 -0
- package/dist_ts/npm/classes.npmregistry.js +717 -0
- package/dist_ts/npm/index.d.ts +5 -0
- package/dist_ts/npm/index.js +6 -0
- package/dist_ts/npm/interfaces.npm.d.ts +245 -0
- package/dist_ts/npm/interfaces.npm.js +6 -0
- package/dist_ts/oci/classes.ociregistry.d.ts +43 -0
- package/dist_ts/oci/classes.ociregistry.js +565 -0
- package/dist_ts/oci/index.d.ts +5 -0
- package/dist_ts/oci/index.js +6 -0
- package/dist_ts/oci/interfaces.oci.d.ts +103 -0
- package/dist_ts/oci/interfaces.oci.js +5 -0
- package/dist_ts/paths.d.ts +1 -0
- package/dist_ts/paths.js +3 -0
- package/dist_ts/plugins.d.ts +6 -0
- package/dist_ts/plugins.js +9 -0
- package/npmextra.json +18 -0
- package/package.json +49 -0
- package/readme.hints.md +3 -0
- package/readme.md +486 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.smartregistry.ts +129 -0
- package/ts/core/classes.authmanager.ts +388 -0
- package/ts/core/classes.baseregistry.ts +36 -0
- package/ts/core/classes.registrystorage.ts +270 -0
- package/ts/core/index.ts +11 -0
- package/ts/core/interfaces.core.ts +159 -0
- package/ts/index.ts +16 -0
- package/ts/npm/classes.npmregistry.ts +890 -0
- package/ts/npm/index.ts +6 -0
- package/ts/npm/interfaces.npm.ts +263 -0
- package/ts/oci/classes.ociregistry.ts +734 -0
- package/ts/oci/index.ts +6 -0
- package/ts/oci/interfaces.oci.ts +101 -0
- package/ts/paths.ts +5 -0
- package/ts/plugins.ts +11 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { RegistryStorage } from './core/classes.registrystorage.js';
|
|
2
|
+
import { AuthManager } from './core/classes.authmanager.js';
|
|
3
|
+
import { BaseRegistry } from './core/classes.baseregistry.js';
|
|
4
|
+
import type { IRegistryConfig, IRequestContext, IResponse } from './core/interfaces.core.js';
|
|
5
|
+
import { OciRegistry } from './oci/classes.ociregistry.js';
|
|
6
|
+
import { NpmRegistry } from './npm/classes.npmregistry.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Main registry orchestrator
|
|
10
|
+
* Routes requests to appropriate protocol handlers (OCI or NPM)
|
|
11
|
+
*/
|
|
12
|
+
export class SmartRegistry {
|
|
13
|
+
private storage: RegistryStorage;
|
|
14
|
+
private authManager: AuthManager;
|
|
15
|
+
private registries: Map<string, BaseRegistry> = new Map();
|
|
16
|
+
private config: IRegistryConfig;
|
|
17
|
+
private initialized: boolean = false;
|
|
18
|
+
|
|
19
|
+
constructor(config: IRegistryConfig) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.storage = new RegistryStorage(config.storage);
|
|
22
|
+
this.authManager = new AuthManager(config.auth);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the registry system
|
|
27
|
+
*/
|
|
28
|
+
public async init(): Promise<void> {
|
|
29
|
+
if (this.initialized) return;
|
|
30
|
+
|
|
31
|
+
// Initialize storage
|
|
32
|
+
await this.storage.init();
|
|
33
|
+
|
|
34
|
+
// Initialize auth manager
|
|
35
|
+
await this.authManager.init();
|
|
36
|
+
|
|
37
|
+
// Initialize OCI registry if enabled
|
|
38
|
+
if (this.config.oci?.enabled) {
|
|
39
|
+
const ociBasePath = this.config.oci.basePath || '/oci';
|
|
40
|
+
const ociRegistry = new OciRegistry(this.storage, this.authManager, ociBasePath);
|
|
41
|
+
await ociRegistry.init();
|
|
42
|
+
this.registries.set('oci', ociRegistry);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Initialize NPM registry if enabled
|
|
46
|
+
if (this.config.npm?.enabled) {
|
|
47
|
+
const npmBasePath = this.config.npm.basePath || '/npm';
|
|
48
|
+
const registryUrl = `http://localhost:5000${npmBasePath}`; // TODO: Make configurable
|
|
49
|
+
const npmRegistry = new NpmRegistry(this.storage, this.authManager, npmBasePath, registryUrl);
|
|
50
|
+
await npmRegistry.init();
|
|
51
|
+
this.registries.set('npm', npmRegistry);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.initialized = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Handle an incoming HTTP request
|
|
59
|
+
* Routes to the appropriate protocol handler based on path
|
|
60
|
+
*/
|
|
61
|
+
public async handleRequest(context: IRequestContext): Promise<IResponse> {
|
|
62
|
+
const path = context.path;
|
|
63
|
+
|
|
64
|
+
// Route to OCI registry
|
|
65
|
+
if (this.config.oci?.enabled && path.startsWith(this.config.oci.basePath)) {
|
|
66
|
+
const ociRegistry = this.registries.get('oci');
|
|
67
|
+
if (ociRegistry) {
|
|
68
|
+
return ociRegistry.handleRequest(context);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Route to NPM registry
|
|
73
|
+
if (this.config.npm?.enabled && path.startsWith(this.config.npm.basePath)) {
|
|
74
|
+
const npmRegistry = this.registries.get('npm');
|
|
75
|
+
if (npmRegistry) {
|
|
76
|
+
return npmRegistry.handleRequest(context);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// No matching registry
|
|
81
|
+
return {
|
|
82
|
+
status: 404,
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: {
|
|
85
|
+
error: 'NOT_FOUND',
|
|
86
|
+
message: 'No registry handler for this path',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the storage instance (for testing/advanced use)
|
|
93
|
+
*/
|
|
94
|
+
public getStorage(): RegistryStorage {
|
|
95
|
+
return this.storage;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the auth manager instance (for testing/advanced use)
|
|
100
|
+
*/
|
|
101
|
+
public getAuthManager(): AuthManager {
|
|
102
|
+
return this.authManager;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get a specific registry handler
|
|
107
|
+
*/
|
|
108
|
+
public getRegistry(protocol: 'oci' | 'npm'): BaseRegistry | undefined {
|
|
109
|
+
return this.registries.get(protocol);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if the registry is initialized
|
|
114
|
+
*/
|
|
115
|
+
public isInitialized(): boolean {
|
|
116
|
+
return this.initialized;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Clean up resources (timers, connections, etc.)
|
|
121
|
+
*/
|
|
122
|
+
public destroy(): void {
|
|
123
|
+
for (const registry of this.registries.values()) {
|
|
124
|
+
if (typeof (registry as any).destroy === 'function') {
|
|
125
|
+
(registry as any).destroy();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import type { IAuthConfig, IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unified authentication manager for all registry protocols
|
|
5
|
+
* Handles both NPM UUID tokens and OCI JWT tokens
|
|
6
|
+
*/
|
|
7
|
+
export class AuthManager {
|
|
8
|
+
private tokenStore: Map<string, IAuthToken> = new Map();
|
|
9
|
+
private userCredentials: Map<string, string> = new Map(); // username -> password hash (mock)
|
|
10
|
+
|
|
11
|
+
constructor(private config: IAuthConfig) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the auth manager
|
|
15
|
+
*/
|
|
16
|
+
public async init(): Promise<void> {
|
|
17
|
+
// Initialize token store (in-memory for now)
|
|
18
|
+
// In production, this could be Redis or a database
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ========================================================================
|
|
22
|
+
// NPM AUTHENTICATION
|
|
23
|
+
// ========================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create an NPM token
|
|
27
|
+
* @param userId - User ID
|
|
28
|
+
* @param readonly - Whether the token is readonly
|
|
29
|
+
* @returns NPM UUID token
|
|
30
|
+
*/
|
|
31
|
+
public async createNpmToken(userId: string, readonly: boolean = false): Promise<string> {
|
|
32
|
+
if (!this.config.npmTokens.enabled) {
|
|
33
|
+
throw new Error('NPM tokens are not enabled');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = this.generateUuid();
|
|
37
|
+
const authToken: IAuthToken = {
|
|
38
|
+
type: 'npm',
|
|
39
|
+
userId,
|
|
40
|
+
scopes: readonly ? ['npm:*:*:read'] : ['npm:*:*:*'],
|
|
41
|
+
readonly,
|
|
42
|
+
metadata: {
|
|
43
|
+
created: new Date().toISOString(),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.tokenStore.set(token, authToken);
|
|
48
|
+
return token;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate an NPM token
|
|
53
|
+
* @param token - NPM UUID token
|
|
54
|
+
* @returns Auth token object or null
|
|
55
|
+
*/
|
|
56
|
+
public async validateNpmToken(token: string): Promise<IAuthToken | null> {
|
|
57
|
+
if (!this.isValidUuid(token)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const authToken = this.tokenStore.get(token);
|
|
62
|
+
if (!authToken || authToken.type !== 'npm') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check expiration if set
|
|
67
|
+
if (authToken.expiresAt && authToken.expiresAt < new Date()) {
|
|
68
|
+
this.tokenStore.delete(token);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return authToken;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Revoke an NPM token
|
|
77
|
+
* @param token - NPM UUID token
|
|
78
|
+
*/
|
|
79
|
+
public async revokeNpmToken(token: string): Promise<void> {
|
|
80
|
+
this.tokenStore.delete(token);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List all tokens for a user
|
|
85
|
+
* @param userId - User ID
|
|
86
|
+
* @returns List of token info (without actual token values)
|
|
87
|
+
*/
|
|
88
|
+
public async listUserTokens(userId: string): Promise<Array<{
|
|
89
|
+
key: string;
|
|
90
|
+
readonly: boolean;
|
|
91
|
+
created: string;
|
|
92
|
+
}>> {
|
|
93
|
+
const tokens: Array<{key: string; readonly: boolean; created: string}> = [];
|
|
94
|
+
|
|
95
|
+
for (const [token, authToken] of this.tokenStore.entries()) {
|
|
96
|
+
if (authToken.userId === userId) {
|
|
97
|
+
tokens.push({
|
|
98
|
+
key: this.hashToken(token),
|
|
99
|
+
readonly: authToken.readonly || false,
|
|
100
|
+
created: authToken.metadata?.created || 'unknown',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return tokens;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ========================================================================
|
|
109
|
+
// OCI AUTHENTICATION (JWT)
|
|
110
|
+
// ========================================================================
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create an OCI JWT token
|
|
114
|
+
* @param userId - User ID
|
|
115
|
+
* @param scopes - Permission scopes
|
|
116
|
+
* @param expiresIn - Expiration time in seconds
|
|
117
|
+
* @returns JWT token string
|
|
118
|
+
*/
|
|
119
|
+
public async createOciToken(
|
|
120
|
+
userId: string,
|
|
121
|
+
scopes: string[],
|
|
122
|
+
expiresIn: number = 3600
|
|
123
|
+
): Promise<string> {
|
|
124
|
+
if (!this.config.ociTokens.enabled) {
|
|
125
|
+
throw new Error('OCI tokens are not enabled');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const now = Math.floor(Date.now() / 1000);
|
|
129
|
+
const payload = {
|
|
130
|
+
iss: this.config.ociTokens.realm,
|
|
131
|
+
sub: userId,
|
|
132
|
+
aud: this.config.ociTokens.service,
|
|
133
|
+
exp: now + expiresIn,
|
|
134
|
+
nbf: now,
|
|
135
|
+
iat: now,
|
|
136
|
+
access: this.scopesToOciAccess(scopes),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// In production, use proper JWT library with signing
|
|
140
|
+
// For now, return JSON string (mock JWT)
|
|
141
|
+
return JSON.stringify(payload);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Validate an OCI JWT token
|
|
146
|
+
* @param jwt - JWT token string
|
|
147
|
+
* @returns Auth token object or null
|
|
148
|
+
*/
|
|
149
|
+
public async validateOciToken(jwt: string): Promise<IAuthToken | null> {
|
|
150
|
+
try {
|
|
151
|
+
// In production, verify JWT signature
|
|
152
|
+
const payload = JSON.parse(jwt);
|
|
153
|
+
|
|
154
|
+
// Check expiration
|
|
155
|
+
const now = Math.floor(Date.now() / 1000);
|
|
156
|
+
if (payload.exp && payload.exp < now) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Convert to unified token format
|
|
161
|
+
const scopes = this.ociAccessToScopes(payload.access || []);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
type: 'oci',
|
|
165
|
+
userId: payload.sub,
|
|
166
|
+
scopes,
|
|
167
|
+
expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
|
|
168
|
+
metadata: {
|
|
169
|
+
iss: payload.iss,
|
|
170
|
+
aud: payload.aud,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ========================================================================
|
|
179
|
+
// UNIFIED AUTHENTICATION
|
|
180
|
+
// ========================================================================
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Authenticate user credentials
|
|
184
|
+
* @param credentials - Username and password
|
|
185
|
+
* @returns User ID or null
|
|
186
|
+
*/
|
|
187
|
+
public async authenticate(credentials: ICredentials): Promise<string | null> {
|
|
188
|
+
// Mock authentication - in production, verify against database
|
|
189
|
+
const storedPassword = this.userCredentials.get(credentials.username);
|
|
190
|
+
|
|
191
|
+
if (!storedPassword) {
|
|
192
|
+
// Auto-register for testing (remove in production)
|
|
193
|
+
this.userCredentials.set(credentials.username, credentials.password);
|
|
194
|
+
return credentials.username;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (storedPassword === credentials.password) {
|
|
198
|
+
return credentials.username;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Validate any token (NPM or OCI)
|
|
206
|
+
* @param tokenString - Token string (UUID or JWT)
|
|
207
|
+
* @param protocol - Expected protocol type
|
|
208
|
+
* @returns Auth token object or null
|
|
209
|
+
*/
|
|
210
|
+
public async validateToken(
|
|
211
|
+
tokenString: string,
|
|
212
|
+
protocol?: TRegistryProtocol
|
|
213
|
+
): Promise<IAuthToken | null> {
|
|
214
|
+
// Try NPM token first (UUID format)
|
|
215
|
+
if (this.isValidUuid(tokenString)) {
|
|
216
|
+
const npmToken = await this.validateNpmToken(tokenString);
|
|
217
|
+
if (npmToken && (!protocol || protocol === 'npm')) {
|
|
218
|
+
return npmToken;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Try OCI JWT
|
|
223
|
+
const ociToken = await this.validateOciToken(tokenString);
|
|
224
|
+
if (ociToken && (!protocol || protocol === 'oci')) {
|
|
225
|
+
return ociToken;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if token has permission for an action
|
|
233
|
+
* @param token - Auth token
|
|
234
|
+
* @param resource - Resource being accessed (e.g., "package:foo" or "repository:bar")
|
|
235
|
+
* @param action - Action being performed (read, write, push, pull, delete)
|
|
236
|
+
* @returns true if authorized
|
|
237
|
+
*/
|
|
238
|
+
public async authorize(
|
|
239
|
+
token: IAuthToken | null,
|
|
240
|
+
resource: string,
|
|
241
|
+
action: string
|
|
242
|
+
): Promise<boolean> {
|
|
243
|
+
if (!token) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check readonly flag
|
|
248
|
+
if (token.readonly && ['write', 'push', 'delete'].includes(action)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Check scopes
|
|
253
|
+
for (const scope of token.scopes) {
|
|
254
|
+
if (this.matchesScope(scope, resource, action)) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ========================================================================
|
|
263
|
+
// HELPER METHODS
|
|
264
|
+
// ========================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if a scope matches a resource and action
|
|
268
|
+
* Scope format: "{protocol}:{type}:{name}:{action}"
|
|
269
|
+
* Examples:
|
|
270
|
+
* - "npm:*:*" - All NPM access
|
|
271
|
+
* - "npm:package:foo:*" - All actions on package foo
|
|
272
|
+
* - "npm:package:foo:read" - Read-only on package foo
|
|
273
|
+
* - "oci:repository:*:pull" - Pull from any OCI repo
|
|
274
|
+
*/
|
|
275
|
+
private matchesScope(scope: string, resource: string, action: string): boolean {
|
|
276
|
+
const scopeParts = scope.split(':');
|
|
277
|
+
const resourceParts = resource.split(':');
|
|
278
|
+
|
|
279
|
+
// Scope must have at least protocol:type:name:action
|
|
280
|
+
if (scopeParts.length < 4) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const [scopeProtocol, scopeType, scopeName, scopeAction] = scopeParts;
|
|
285
|
+
const [resourceProtocol, resourceType, resourceName] = resourceParts;
|
|
286
|
+
|
|
287
|
+
// Check protocol
|
|
288
|
+
if (scopeProtocol !== '*' && scopeProtocol !== resourceProtocol) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check type
|
|
293
|
+
if (scopeType !== '*' && scopeType !== resourceType) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check name
|
|
298
|
+
if (scopeName !== '*' && scopeName !== resourceName) {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check action
|
|
303
|
+
if (scopeAction !== '*' && scopeAction !== action) {
|
|
304
|
+
// Map action aliases
|
|
305
|
+
const actionAliases: Record<string, string[]> = {
|
|
306
|
+
read: ['pull', 'get'],
|
|
307
|
+
write: ['push', 'put', 'post'],
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const aliases = actionAliases[scopeAction] || [];
|
|
311
|
+
if (!aliases.includes(action)) {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Convert unified scopes to OCI access array
|
|
321
|
+
*/
|
|
322
|
+
private scopesToOciAccess(scopes: string[]): Array<{
|
|
323
|
+
type: string;
|
|
324
|
+
name: string;
|
|
325
|
+
actions: string[];
|
|
326
|
+
}> {
|
|
327
|
+
const access: Array<{type: string; name: string; actions: string[]}> = [];
|
|
328
|
+
|
|
329
|
+
for (const scope of scopes) {
|
|
330
|
+
const parts = scope.split(':');
|
|
331
|
+
if (parts.length >= 4 && parts[0] === 'oci') {
|
|
332
|
+
access.push({
|
|
333
|
+
type: parts[1],
|
|
334
|
+
name: parts[2],
|
|
335
|
+
actions: [parts[3]],
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return access;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Convert OCI access array to unified scopes
|
|
345
|
+
*/
|
|
346
|
+
private ociAccessToScopes(access: Array<{
|
|
347
|
+
type: string;
|
|
348
|
+
name: string;
|
|
349
|
+
actions: string[];
|
|
350
|
+
}>): string[] {
|
|
351
|
+
const scopes: string[] = [];
|
|
352
|
+
|
|
353
|
+
for (const item of access) {
|
|
354
|
+
for (const action of item.actions) {
|
|
355
|
+
scopes.push(`oci:${item.type}:${item.name}:${action}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return scopes;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Generate UUID for NPM tokens
|
|
364
|
+
*/
|
|
365
|
+
private generateUuid(): string {
|
|
366
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
367
|
+
const r = (Math.random() * 16) | 0;
|
|
368
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
369
|
+
return v.toString(16);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Check if string is a valid UUID
|
|
375
|
+
*/
|
|
376
|
+
private isValidUuid(str: string): boolean {
|
|
377
|
+
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;
|
|
378
|
+
return uuidRegex.test(str);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Hash a token for identification (SHA-512 mock)
|
|
383
|
+
*/
|
|
384
|
+
private hashToken(token: string): string {
|
|
385
|
+
// In production, use actual SHA-512
|
|
386
|
+
return `sha512-${token.substring(0, 16)}...`;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { IRequestContext, IResponse, IAuthToken } from './interfaces.core.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for all registry protocol implementations
|
|
5
|
+
*/
|
|
6
|
+
export abstract class BaseRegistry {
|
|
7
|
+
/**
|
|
8
|
+
* Initialize the registry
|
|
9
|
+
*/
|
|
10
|
+
abstract init(): Promise<void>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Handle an incoming HTTP request
|
|
14
|
+
* @param context - Request context
|
|
15
|
+
* @returns Response object
|
|
16
|
+
*/
|
|
17
|
+
abstract handleRequest(context: IRequestContext): Promise<IResponse>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the base path for this registry protocol
|
|
21
|
+
*/
|
|
22
|
+
abstract getBasePath(): string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate that a token has the required permissions
|
|
26
|
+
* @param token - Authentication token
|
|
27
|
+
* @param resource - Resource being accessed
|
|
28
|
+
* @param action - Action being performed
|
|
29
|
+
* @returns true if authorized
|
|
30
|
+
*/
|
|
31
|
+
protected abstract checkPermission(
|
|
32
|
+
token: IAuthToken | null,
|
|
33
|
+
resource: string,
|
|
34
|
+
action: string
|
|
35
|
+
): Promise<boolean>;
|
|
36
|
+
}
|