@push.rocks/smartregistry 2.3.0 → 2.5.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 (33) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.smartregistry.d.ts +33 -2
  3. package/dist_ts/classes.smartregistry.js +38 -5
  4. package/dist_ts/core/classes.authmanager.d.ts +30 -80
  5. package/dist_ts/core/classes.authmanager.js +63 -337
  6. package/dist_ts/core/classes.defaultauthprovider.d.ts +78 -0
  7. package/dist_ts/core/classes.defaultauthprovider.js +311 -0
  8. package/dist_ts/core/classes.registrystorage.d.ts +70 -4
  9. package/dist_ts/core/classes.registrystorage.js +165 -5
  10. package/dist_ts/core/index.d.ts +3 -0
  11. package/dist_ts/core/index.js +7 -2
  12. package/dist_ts/core/interfaces.auth.d.ts +83 -0
  13. package/dist_ts/core/interfaces.auth.js +2 -0
  14. package/dist_ts/core/interfaces.core.d.ts +35 -0
  15. package/dist_ts/core/interfaces.storage.d.ts +120 -0
  16. package/dist_ts/core/interfaces.storage.js +2 -0
  17. package/dist_ts/upstream/classes.baseupstream.d.ts +2 -2
  18. package/dist_ts/upstream/classes.baseupstream.js +16 -14
  19. package/dist_ts/upstream/classes.upstreamcache.d.ts +69 -22
  20. package/dist_ts/upstream/classes.upstreamcache.js +207 -50
  21. package/package.json +1 -1
  22. package/readme.md +225 -1
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/classes.smartregistry.ts +39 -4
  25. package/ts/core/classes.authmanager.ts +74 -412
  26. package/ts/core/classes.defaultauthprovider.ts +393 -0
  27. package/ts/core/classes.registrystorage.ts +199 -5
  28. package/ts/core/index.ts +8 -1
  29. package/ts/core/interfaces.auth.ts +91 -0
  30. package/ts/core/interfaces.core.ts +39 -0
  31. package/ts/core/interfaces.storage.ts +130 -0
  32. package/ts/upstream/classes.baseupstream.ts +20 -15
  33. package/ts/upstream/classes.upstreamcache.ts +256 -53
@@ -0,0 +1,78 @@
1
+ import type { IAuthProvider, ITokenOptions } from './interfaces.auth.js';
2
+ import type { IAuthConfig, IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
3
+ /**
4
+ * Default in-memory authentication provider.
5
+ * This is the reference implementation that stores tokens in memory.
6
+ * For production use, implement IAuthProvider with Redis, database, or external auth.
7
+ */
8
+ export declare class DefaultAuthProvider implements IAuthProvider {
9
+ private config;
10
+ private tokenStore;
11
+ private userCredentials;
12
+ constructor(config: IAuthConfig);
13
+ /**
14
+ * Initialize the auth provider
15
+ */
16
+ init(): Promise<void>;
17
+ /**
18
+ * Authenticate user credentials
19
+ */
20
+ authenticate(credentials: ICredentials): Promise<string | null>;
21
+ /**
22
+ * Validate any token (NPM, Maven, OCI, PyPI, RubyGems, Composer, Cargo)
23
+ */
24
+ validateToken(tokenString: string, protocol?: TRegistryProtocol): Promise<IAuthToken | null>;
25
+ /**
26
+ * Create a new token for a user
27
+ */
28
+ createToken(userId: string, protocol: TRegistryProtocol, options?: ITokenOptions): Promise<string>;
29
+ /**
30
+ * Revoke a token
31
+ */
32
+ revokeToken(token: string): Promise<void>;
33
+ /**
34
+ * Check if token has permission for an action
35
+ */
36
+ authorize(token: IAuthToken | null, resource: string, action: string): Promise<boolean>;
37
+ /**
38
+ * List all tokens for a user
39
+ */
40
+ listUserTokens(userId: string): Promise<Array<{
41
+ key: string;
42
+ readonly: boolean;
43
+ created: string;
44
+ protocol?: TRegistryProtocol;
45
+ }>>;
46
+ /**
47
+ * Create an OCI JWT token
48
+ */
49
+ private createOciToken;
50
+ /**
51
+ * Validate an OCI JWT token
52
+ */
53
+ private validateOciToken;
54
+ /**
55
+ * Check if a scope matches a resource and action
56
+ */
57
+ private matchesScope;
58
+ /**
59
+ * Convert unified scopes to OCI access array
60
+ */
61
+ private scopesToOciAccess;
62
+ /**
63
+ * Convert OCI access array to unified scopes
64
+ */
65
+ private ociAccessToScopes;
66
+ /**
67
+ * Generate UUID for tokens
68
+ */
69
+ private generateUuid;
70
+ /**
71
+ * Check if string is a valid UUID
72
+ */
73
+ private isValidUuid;
74
+ /**
75
+ * Hash a token for identification
76
+ */
77
+ private hashToken;
78
+ }
@@ -0,0 +1,311 @@
1
+ import * as crypto from 'crypto';
2
+ /**
3
+ * Default in-memory authentication provider.
4
+ * This is the reference implementation that stores tokens in memory.
5
+ * For production use, implement IAuthProvider with Redis, database, or external auth.
6
+ */
7
+ export class DefaultAuthProvider {
8
+ config;
9
+ tokenStore = new Map();
10
+ userCredentials = new Map(); // username -> password hash (mock)
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ /**
15
+ * Initialize the auth provider
16
+ */
17
+ async init() {
18
+ // Initialize token store (in-memory for now)
19
+ // In production, this could be Redis or a database
20
+ }
21
+ // ========================================================================
22
+ // IAuthProvider Implementation
23
+ // ========================================================================
24
+ /**
25
+ * Authenticate user credentials
26
+ */
27
+ async authenticate(credentials) {
28
+ // Mock authentication - in production, verify against database/LDAP
29
+ const storedPassword = this.userCredentials.get(credentials.username);
30
+ if (!storedPassword) {
31
+ // Auto-register for testing (remove in production)
32
+ this.userCredentials.set(credentials.username, credentials.password);
33
+ return credentials.username;
34
+ }
35
+ if (storedPassword === credentials.password) {
36
+ return credentials.username;
37
+ }
38
+ return null;
39
+ }
40
+ /**
41
+ * Validate any token (NPM, Maven, OCI, PyPI, RubyGems, Composer, Cargo)
42
+ */
43
+ async validateToken(tokenString, protocol) {
44
+ // OCI uses JWT (contains dots), not UUID - check first if OCI is expected
45
+ if (protocol === 'oci' || tokenString.includes('.')) {
46
+ const ociToken = await this.validateOciToken(tokenString);
47
+ if (ociToken && (!protocol || protocol === 'oci')) {
48
+ return ociToken;
49
+ }
50
+ // If protocol was explicitly OCI but validation failed, return null
51
+ if (protocol === 'oci') {
52
+ return null;
53
+ }
54
+ }
55
+ // UUID-based tokens: single O(1) Map lookup
56
+ if (this.isValidUuid(tokenString)) {
57
+ const authToken = this.tokenStore.get(tokenString);
58
+ if (authToken) {
59
+ // If protocol specified, verify it matches
60
+ if (protocol && authToken.type !== protocol) {
61
+ return null;
62
+ }
63
+ // Check expiration
64
+ if (authToken.expiresAt && authToken.expiresAt < new Date()) {
65
+ this.tokenStore.delete(tokenString);
66
+ return null;
67
+ }
68
+ return authToken;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * Create a new token for a user
75
+ */
76
+ async createToken(userId, protocol, options) {
77
+ // OCI tokens use JWT
78
+ if (protocol === 'oci') {
79
+ return this.createOciToken(userId, options?.scopes || ['oci:*:*:*'], options?.expiresIn || 3600);
80
+ }
81
+ // All other protocols use UUID tokens
82
+ const token = this.generateUuid();
83
+ const scopes = options?.scopes || (options?.readonly
84
+ ? [`${protocol}:*:*:read`]
85
+ : [`${protocol}:*:*:*`]);
86
+ const authToken = {
87
+ type: protocol,
88
+ userId,
89
+ scopes,
90
+ readonly: options?.readonly,
91
+ expiresAt: options?.expiresIn ? new Date(Date.now() + options.expiresIn * 1000) : undefined,
92
+ metadata: {
93
+ created: new Date().toISOString(),
94
+ },
95
+ };
96
+ this.tokenStore.set(token, authToken);
97
+ return token;
98
+ }
99
+ /**
100
+ * Revoke a token
101
+ */
102
+ async revokeToken(token) {
103
+ this.tokenStore.delete(token);
104
+ }
105
+ /**
106
+ * Check if token has permission for an action
107
+ */
108
+ async authorize(token, resource, action) {
109
+ if (!token) {
110
+ return false;
111
+ }
112
+ // Check readonly flag
113
+ if (token.readonly && ['write', 'push', 'delete'].includes(action)) {
114
+ return false;
115
+ }
116
+ // Check scopes
117
+ for (const scope of token.scopes) {
118
+ if (this.matchesScope(scope, resource, action)) {
119
+ return true;
120
+ }
121
+ }
122
+ return false;
123
+ }
124
+ /**
125
+ * List all tokens for a user
126
+ */
127
+ async listUserTokens(userId) {
128
+ const tokens = [];
129
+ for (const [token, authToken] of this.tokenStore.entries()) {
130
+ if (authToken.userId === userId) {
131
+ tokens.push({
132
+ key: this.hashToken(token),
133
+ readonly: authToken.readonly || false,
134
+ created: authToken.metadata?.created || 'unknown',
135
+ protocol: authToken.type,
136
+ });
137
+ }
138
+ }
139
+ return tokens;
140
+ }
141
+ // ========================================================================
142
+ // OCI JWT Token Methods
143
+ // ========================================================================
144
+ /**
145
+ * Create an OCI JWT token
146
+ */
147
+ async createOciToken(userId, scopes, expiresIn = 3600) {
148
+ if (!this.config.ociTokens?.enabled) {
149
+ throw new Error('OCI tokens are not enabled');
150
+ }
151
+ const now = Math.floor(Date.now() / 1000);
152
+ const payload = {
153
+ iss: this.config.ociTokens.realm,
154
+ sub: userId,
155
+ aud: this.config.ociTokens.service,
156
+ exp: now + expiresIn,
157
+ nbf: now,
158
+ iat: now,
159
+ access: this.scopesToOciAccess(scopes),
160
+ };
161
+ // Create JWT with HMAC-SHA256 signature
162
+ const header = { alg: 'HS256', typ: 'JWT' };
163
+ const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
164
+ const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
165
+ const signature = crypto
166
+ .createHmac('sha256', this.config.jwtSecret)
167
+ .update(`${headerB64}.${payloadB64}`)
168
+ .digest('base64url');
169
+ return `${headerB64}.${payloadB64}.${signature}`;
170
+ }
171
+ /**
172
+ * Validate an OCI JWT token
173
+ */
174
+ async validateOciToken(jwt) {
175
+ try {
176
+ const parts = jwt.split('.');
177
+ if (parts.length !== 3) {
178
+ return null;
179
+ }
180
+ const [headerB64, payloadB64, signatureB64] = parts;
181
+ // Verify signature
182
+ const expectedSignature = crypto
183
+ .createHmac('sha256', this.config.jwtSecret)
184
+ .update(`${headerB64}.${payloadB64}`)
185
+ .digest('base64url');
186
+ if (signatureB64 !== expectedSignature) {
187
+ return null;
188
+ }
189
+ // Decode and parse payload
190
+ const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString('utf-8'));
191
+ // Check expiration
192
+ const now = Math.floor(Date.now() / 1000);
193
+ if (payload.exp && payload.exp < now) {
194
+ return null;
195
+ }
196
+ // Check not-before time
197
+ if (payload.nbf && payload.nbf > now) {
198
+ return null;
199
+ }
200
+ // Convert to unified token format
201
+ const scopes = this.ociAccessToScopes(payload.access || []);
202
+ return {
203
+ type: 'oci',
204
+ userId: payload.sub,
205
+ scopes,
206
+ expiresAt: payload.exp ? new Date(payload.exp * 1000) : undefined,
207
+ metadata: {
208
+ iss: payload.iss,
209
+ aud: payload.aud,
210
+ },
211
+ };
212
+ }
213
+ catch (error) {
214
+ return null;
215
+ }
216
+ }
217
+ // ========================================================================
218
+ // Helper Methods
219
+ // ========================================================================
220
+ /**
221
+ * Check if a scope matches a resource and action
222
+ */
223
+ matchesScope(scope, resource, action) {
224
+ const scopeParts = scope.split(':');
225
+ const resourceParts = resource.split(':');
226
+ // Scope must have at least protocol:type:name:action
227
+ if (scopeParts.length < 4) {
228
+ return false;
229
+ }
230
+ const [scopeProtocol, scopeType, scopeName, scopeAction] = scopeParts;
231
+ const [resourceProtocol, resourceType, resourceName] = resourceParts;
232
+ // Check protocol
233
+ if (scopeProtocol !== '*' && scopeProtocol !== resourceProtocol) {
234
+ return false;
235
+ }
236
+ // Check type
237
+ if (scopeType !== '*' && scopeType !== resourceType) {
238
+ return false;
239
+ }
240
+ // Check name
241
+ if (scopeName !== '*' && scopeName !== resourceName) {
242
+ return false;
243
+ }
244
+ // Check action
245
+ if (scopeAction !== '*' && scopeAction !== action) {
246
+ // Map action aliases
247
+ const actionAliases = {
248
+ read: ['pull', 'get'],
249
+ write: ['push', 'put', 'post'],
250
+ };
251
+ const aliases = actionAliases[scopeAction] || [];
252
+ if (!aliases.includes(action)) {
253
+ return false;
254
+ }
255
+ }
256
+ return true;
257
+ }
258
+ /**
259
+ * Convert unified scopes to OCI access array
260
+ */
261
+ scopesToOciAccess(scopes) {
262
+ const access = [];
263
+ for (const scope of scopes) {
264
+ const parts = scope.split(':');
265
+ if (parts.length >= 4 && parts[0] === 'oci') {
266
+ access.push({
267
+ type: parts[1],
268
+ name: parts[2],
269
+ actions: [parts[3]],
270
+ });
271
+ }
272
+ }
273
+ return access;
274
+ }
275
+ /**
276
+ * Convert OCI access array to unified scopes
277
+ */
278
+ ociAccessToScopes(access) {
279
+ const scopes = [];
280
+ for (const item of access) {
281
+ for (const action of item.actions) {
282
+ scopes.push(`oci:${item.type}:${item.name}:${action}`);
283
+ }
284
+ }
285
+ return scopes;
286
+ }
287
+ /**
288
+ * Generate UUID for tokens
289
+ */
290
+ generateUuid() {
291
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
292
+ const r = (Math.random() * 16) | 0;
293
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
294
+ return v.toString(16);
295
+ });
296
+ }
297
+ /**
298
+ * Check if string is a valid UUID
299
+ */
300
+ isValidUuid(str) {
301
+ 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;
302
+ return uuidRegex.test(str);
303
+ }
304
+ /**
305
+ * Hash a token for identification
306
+ */
307
+ hashToken(token) {
308
+ return `sha512-${token.substring(0, 16)}...`;
309
+ }
310
+ }
311
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5kZWZhdWx0YXV0aHByb3ZpZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvY29yZS9jbGFzc2VzLmRlZmF1bHRhdXRocHJvdmlkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFJakM7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFJVjtJQUhaLFVBQVUsR0FBNEIsSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUNoRCxlQUFlLEdBQXdCLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQyxtQ0FBbUM7SUFFN0YsWUFBb0IsTUFBbUI7UUFBbkIsV0FBTSxHQUFOLE1BQU0sQ0FBYTtJQUFHLENBQUM7SUFFM0M7O09BRUc7SUFDSSxLQUFLLENBQUMsSUFBSTtRQUNmLDZDQUE2QztRQUM3QyxtREFBbUQ7SUFDckQsQ0FBQztJQUVELDJFQUEyRTtJQUMzRSwrQkFBK0I7SUFDL0IsMkVBQTJFO0lBRTNFOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFlBQVksQ0FBQyxXQUF5QjtRQUNqRCxvRUFBb0U7UUFDcEUsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBRXRFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUNwQixtREFBbUQ7WUFDbkQsSUFBSSxDQUFDLGVBQWUsQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLFFBQVEsRUFBRSxXQUFXLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDckUsT0FBTyxXQUFXLENBQUMsUUFBUSxDQUFDO1FBQzlCLENBQUM7UUFFRCxJQUFJLGNBQWMsS0FBSyxXQUFXLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDNUMsT0FBTyxXQUFXLENBQUMsUUFBUSxDQUFDO1FBQzlCLENBQUM7UUFFRCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRDs7T0FFRztJ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
@@ -1,14 +1,42 @@
1
- import type { IStorageConfig, IStorageBackend } from './interfaces.core.js';
1
+ import type { IStorageConfig, IStorageBackend, TRegistryProtocol } from './interfaces.core.js';
2
+ import type { IStorageHooks, IStorageActor, IStorageMetadata } from './interfaces.storage.js';
2
3
  /**
3
- * Storage abstraction layer for registry
4
- * Provides a unified interface over SmartBucket
4
+ * Storage abstraction layer for registry.
5
+ * Provides a unified interface over SmartBucket with optional hooks
6
+ * for quota tracking, audit logging, cache invalidation, etc.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // Basic usage
11
+ * const storage = new RegistryStorage(config);
12
+ *
13
+ * // With hooks for quota tracking
14
+ * const storage = new RegistryStorage(config, {
15
+ * beforePut: async (ctx) => {
16
+ * const quota = await getQuota(ctx.actor?.orgId);
17
+ * const usage = await getUsage(ctx.actor?.orgId);
18
+ * if (usage + (ctx.metadata?.size || 0) > quota) {
19
+ * return { allowed: false, reason: 'Quota exceeded' };
20
+ * }
21
+ * return { allowed: true };
22
+ * },
23
+ * afterPut: async (ctx) => {
24
+ * await updateUsage(ctx.actor?.orgId, ctx.metadata?.size || 0);
25
+ * }
26
+ * });
27
+ * ```
5
28
  */
6
29
  export declare class RegistryStorage implements IStorageBackend {
7
30
  private config;
8
31
  private smartBucket;
9
32
  private bucket;
10
33
  private bucketName;
11
- constructor(config: IStorageConfig);
34
+ private hooks?;
35
+ constructor(config: IStorageConfig, hooks?: IStorageHooks);
36
+ /**
37
+ * Set storage hooks (can be called after construction)
38
+ */
39
+ setHooks(hooks: IStorageHooks): void;
12
40
  /**
13
41
  * Initialize the storage backend
14
42
  */
@@ -25,6 +53,44 @@ export declare class RegistryStorage implements IStorageBackend {
25
53
  * Delete an object
26
54
  */
27
55
  deleteObject(key: string): Promise<void>;
56
+ /**
57
+ * Current operation context for hooks.
58
+ * Set this before performing storage operations to enable hooks.
59
+ */
60
+ private currentContext?;
61
+ /**
62
+ * Set the current operation context for hooks.
63
+ * Call this before performing storage operations.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * storage.setContext({
68
+ * protocol: 'npm',
69
+ * actor: { userId: 'user123', ip: '192.168.1.1' },
70
+ * metadata: { packageName: 'lodash', version: '4.17.21' }
71
+ * });
72
+ * await storage.putNpmTarball('lodash', '4.17.21', tarball);
73
+ * storage.clearContext();
74
+ * ```
75
+ */
76
+ setContext(context: {
77
+ protocol: TRegistryProtocol;
78
+ actor?: IStorageActor;
79
+ metadata?: IStorageMetadata;
80
+ }): void;
81
+ /**
82
+ * Clear the current operation context.
83
+ */
84
+ clearContext(): void;
85
+ /**
86
+ * Execute a function with a temporary context.
87
+ * Context is automatically cleared after execution.
88
+ */
89
+ withContext<T>(context: {
90
+ protocol: TRegistryProtocol;
91
+ actor?: IStorageActor;
92
+ metadata?: IStorageMetadata;
93
+ }, fn: () => Promise<T>): Promise<T>;
28
94
  /**
29
95
  * List objects with a prefix (recursively)
30
96
  */