@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.
Files changed (32) 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/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/classes.smartregistry.ts +39 -4
  24. package/ts/core/classes.authmanager.ts +74 -412
  25. package/ts/core/classes.defaultauthprovider.ts +393 -0
  26. package/ts/core/classes.registrystorage.ts +199 -5
  27. package/ts/core/index.ts +8 -1
  28. package/ts/core/interfaces.auth.ts +91 -0
  29. package/ts/core/interfaces.core.ts +39 -0
  30. package/ts/core/interfaces.storage.ts +130 -0
  31. package/ts/upstream/classes.baseupstream.ts +20 -15
  32. package/ts/upstream/classes.upstreamcache.ts +256 -53
@@ -0,0 +1,91 @@
1
+ import type { IAuthToken, ICredentials, TRegistryProtocol } from './interfaces.core.js';
2
+
3
+ /**
4
+ * Options for creating a token
5
+ */
6
+ export interface ITokenOptions {
7
+ /** Whether the token is readonly */
8
+ readonly?: boolean;
9
+ /** Permission scopes */
10
+ scopes?: string[];
11
+ /** Expiration time in seconds */
12
+ expiresIn?: number;
13
+ }
14
+
15
+ /**
16
+ * Pluggable authentication provider interface.
17
+ * Implement this to integrate external auth systems (LDAP, OAuth, SSO, OIDC).
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * class LdapAuthProvider implements IAuthProvider {
22
+ * constructor(private ldap: LdapClient, private redis: RedisClient) {}
23
+ *
24
+ * async authenticate(credentials: ICredentials): Promise<string | null> {
25
+ * return await this.ldap.bind(credentials.username, credentials.password);
26
+ * }
27
+ *
28
+ * async validateToken(token: string): Promise<IAuthToken | null> {
29
+ * return await this.redis.get(`token:${token}`);
30
+ * }
31
+ * // ...
32
+ * }
33
+ * ```
34
+ */
35
+ export interface IAuthProvider {
36
+ /**
37
+ * Initialize the auth provider (optional)
38
+ */
39
+ init?(): Promise<void>;
40
+
41
+ /**
42
+ * Authenticate user credentials (login flow)
43
+ * @param credentials - Username and password
44
+ * @returns User ID on success, null on failure
45
+ */
46
+ authenticate(credentials: ICredentials): Promise<string | null>;
47
+
48
+ /**
49
+ * Validate an existing token
50
+ * @param token - Token string (UUID or JWT)
51
+ * @param protocol - Optional protocol hint for optimization
52
+ * @returns Auth token info or null if invalid
53
+ */
54
+ validateToken(token: string, protocol?: TRegistryProtocol): Promise<IAuthToken | null>;
55
+
56
+ /**
57
+ * Create a new token for a user
58
+ * @param userId - User ID
59
+ * @param protocol - Protocol type (npm, oci, maven, etc.)
60
+ * @param options - Token options (readonly, scopes, expiration)
61
+ * @returns Token string
62
+ */
63
+ createToken(userId: string, protocol: TRegistryProtocol, options?: ITokenOptions): Promise<string>;
64
+
65
+ /**
66
+ * Revoke a token
67
+ * @param token - Token string to revoke
68
+ */
69
+ revokeToken(token: string): Promise<void>;
70
+
71
+ /**
72
+ * Check if user has permission for an action
73
+ * @param token - Auth token (or null for anonymous)
74
+ * @param resource - Resource being accessed (e.g., "npm:package:lodash")
75
+ * @param action - Action being performed (read, write, push, pull, delete)
76
+ * @returns true if authorized
77
+ */
78
+ authorize(token: IAuthToken | null, resource: string, action: string): Promise<boolean>;
79
+
80
+ /**
81
+ * List all tokens for a user (optional)
82
+ * @param userId - User ID
83
+ * @returns List of token info
84
+ */
85
+ listUserTokens?(userId: string): Promise<Array<{
86
+ key: string;
87
+ readonly: boolean;
88
+ created: string;
89
+ protocol?: TRegistryProtocol;
90
+ }>>;
91
+ }
@@ -4,6 +4,8 @@
4
4
 
5
5
  import type * as plugins from '../plugins.js';
6
6
  import type { IProtocolUpstreamConfig } from '../upstream/interfaces.upstream.js';
7
+ import type { IAuthProvider } from './interfaces.auth.js';
8
+ import type { IStorageHooks } from './interfaces.storage.js';
7
9
 
8
10
  /**
9
11
  * Registry protocol types
@@ -97,6 +99,20 @@ export interface IProtocolConfig {
97
99
  export interface IRegistryConfig {
98
100
  storage: IStorageConfig;
99
101
  auth: IAuthConfig;
102
+
103
+ /**
104
+ * Custom authentication provider.
105
+ * If not provided, uses the default in-memory auth provider.
106
+ * Implement IAuthProvider to integrate LDAP, OAuth, SSO, etc.
107
+ */
108
+ authProvider?: IAuthProvider;
109
+
110
+ /**
111
+ * Storage event hooks for quota tracking, audit logging, etc.
112
+ * Called before/after storage operations.
113
+ */
114
+ storageHooks?: IStorageHooks;
115
+
100
116
  oci?: IProtocolConfig;
101
117
  npm?: IProtocolConfig;
102
118
  maven?: IProtocolConfig;
@@ -152,6 +168,24 @@ export interface IRegistryError {
152
168
  }>;
153
169
  }
154
170
 
171
+ /**
172
+ * Actor information - identifies who is performing the request
173
+ */
174
+ export interface IRequestActor {
175
+ /** User ID (from validated token) */
176
+ userId?: string;
177
+ /** Token ID/hash for audit purposes */
178
+ tokenId?: string;
179
+ /** Client IP address */
180
+ ip?: string;
181
+ /** Client User-Agent */
182
+ userAgent?: string;
183
+ /** Organization ID (for multi-tenant setups) */
184
+ orgId?: string;
185
+ /** Session ID */
186
+ sessionId?: string;
187
+ }
188
+
155
189
  /**
156
190
  * Base request context
157
191
  */
@@ -168,6 +202,11 @@ export interface IRequestContext {
168
202
  */
169
203
  rawBody?: Buffer;
170
204
  token?: string;
205
+ /**
206
+ * Actor information - identifies who is performing the request.
207
+ * Populated after authentication for audit logging, quota enforcement, etc.
208
+ */
209
+ actor?: IRequestActor;
171
210
  }
172
211
 
173
212
  /**
@@ -0,0 +1,130 @@
1
+ import type { TRegistryProtocol } from './interfaces.core.js';
2
+
3
+ /**
4
+ * Actor information from request context
5
+ */
6
+ export interface IStorageActor {
7
+ userId?: string;
8
+ tokenId?: string;
9
+ ip?: string;
10
+ userAgent?: string;
11
+ orgId?: string;
12
+ sessionId?: string;
13
+ }
14
+
15
+ /**
16
+ * Metadata about the storage operation
17
+ */
18
+ export interface IStorageMetadata {
19
+ /** Content type of the object */
20
+ contentType?: string;
21
+ /** Size in bytes */
22
+ size?: number;
23
+ /** Content digest (e.g., sha256:abc123) */
24
+ digest?: string;
25
+ /** Package/artifact name */
26
+ packageName?: string;
27
+ /** Version */
28
+ version?: string;
29
+ }
30
+
31
+ /**
32
+ * Context passed to storage hooks
33
+ */
34
+ export interface IStorageHookContext {
35
+ /** Type of operation */
36
+ operation: 'put' | 'delete' | 'get';
37
+ /** Storage key/path */
38
+ key: string;
39
+ /** Protocol that triggered this operation */
40
+ protocol: TRegistryProtocol;
41
+ /** Actor who performed the operation (if known) */
42
+ actor?: IStorageActor;
43
+ /** Metadata about the object */
44
+ metadata?: IStorageMetadata;
45
+ /** Timestamp of the operation */
46
+ timestamp: Date;
47
+ }
48
+
49
+ /**
50
+ * Result from a beforePut hook that can modify the operation
51
+ */
52
+ export interface IBeforePutResult {
53
+ /** Whether to allow the operation */
54
+ allowed: boolean;
55
+ /** Optional reason for rejection */
56
+ reason?: string;
57
+ /** Optional modified metadata */
58
+ metadata?: IStorageMetadata;
59
+ }
60
+
61
+ /**
62
+ * Result from a beforeDelete hook
63
+ */
64
+ export interface IBeforeDeleteResult {
65
+ /** Whether to allow the operation */
66
+ allowed: boolean;
67
+ /** Optional reason for rejection */
68
+ reason?: string;
69
+ }
70
+
71
+ /**
72
+ * Storage event hooks for quota tracking, audit logging, cache invalidation, etc.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const quotaHooks: IStorageHooks = {
77
+ * async beforePut(context) {
78
+ * const quota = await getQuota(context.actor?.orgId);
79
+ * const currentUsage = await getUsage(context.actor?.orgId);
80
+ * if (currentUsage + (context.metadata?.size || 0) > quota) {
81
+ * return { allowed: false, reason: 'Quota exceeded' };
82
+ * }
83
+ * return { allowed: true };
84
+ * },
85
+ *
86
+ * async afterPut(context) {
87
+ * await updateUsage(context.actor?.orgId, context.metadata?.size || 0);
88
+ * await auditLog('storage.put', context);
89
+ * },
90
+ *
91
+ * async afterDelete(context) {
92
+ * await invalidateCache(context.key);
93
+ * }
94
+ * };
95
+ * ```
96
+ */
97
+ export interface IStorageHooks {
98
+ /**
99
+ * Called before storing an object.
100
+ * Return { allowed: false } to reject the operation.
101
+ * Use for quota checks, virus scanning, validation, etc.
102
+ */
103
+ beforePut?(context: IStorageHookContext): Promise<IBeforePutResult>;
104
+
105
+ /**
106
+ * Called after successfully storing an object.
107
+ * Use for quota tracking, audit logging, notifications, etc.
108
+ */
109
+ afterPut?(context: IStorageHookContext): Promise<void>;
110
+
111
+ /**
112
+ * Called before deleting an object.
113
+ * Return { allowed: false } to reject the operation.
114
+ * Use for preventing deletion of protected resources.
115
+ */
116
+ beforeDelete?(context: IStorageHookContext): Promise<IBeforeDeleteResult>;
117
+
118
+ /**
119
+ * Called after successfully deleting an object.
120
+ * Use for quota updates, audit logging, cache invalidation, etc.
121
+ */
122
+ afterDelete?(context: IStorageHookContext): Promise<void>;
123
+
124
+ /**
125
+ * Called after reading an object.
126
+ * Use for access logging, analytics, etc.
127
+ * Note: This is called even for cache hits.
128
+ */
129
+ afterGet?(context: IStorageHookContext): Promise<void>;
130
+ }
@@ -110,8 +110,18 @@ export abstract class BaseUpstream {
110
110
  return null;
111
111
  }
112
112
 
113
+ // Get applicable upstreams sorted by priority
114
+ const applicableUpstreams = this.getApplicableUpstreams(context.resource);
115
+
116
+ if (applicableUpstreams.length === 0) {
117
+ return null;
118
+ }
119
+
120
+ // Use the first applicable upstream's URL for cache key
121
+ const primaryUpstreamUrl = applicableUpstreams[0]?.url;
122
+
113
123
  // Check cache first
114
- const cached = this.cache.get(context);
124
+ const cached = await this.cache.get(context, primaryUpstreamUrl);
115
125
  if (cached && !cached.stale) {
116
126
  return {
117
127
  success: true,
@@ -125,7 +135,7 @@ export abstract class BaseUpstream {
125
135
  }
126
136
 
127
137
  // Check for negative cache (recent 404)
128
- if (this.cache.hasNegative(context)) {
138
+ if (await this.cache.hasNegative(context, primaryUpstreamUrl)) {
129
139
  return {
130
140
  success: false,
131
141
  status: 404,
@@ -136,13 +146,6 @@ export abstract class BaseUpstream {
136
146
  };
137
147
  }
138
148
 
139
- // Get applicable upstreams sorted by priority
140
- const applicableUpstreams = this.getApplicableUpstreams(context.resource);
141
-
142
- if (applicableUpstreams.length === 0) {
143
- return null;
144
- }
145
-
146
149
  // If we have stale cache, return it immediately and revalidate in background
147
150
  if (cached?.stale && this.cacheConfig.staleWhileRevalidate) {
148
151
  // Fire and forget revalidation
@@ -173,18 +176,19 @@ export abstract class BaseUpstream {
173
176
 
174
177
  // Cache successful responses
175
178
  if (result.success && result.body) {
176
- this.cache.set(
179
+ await this.cache.set(
177
180
  context,
178
181
  Buffer.isBuffer(result.body) ? result.body : Buffer.from(JSON.stringify(result.body)),
179
182
  result.headers['content-type'] || 'application/octet-stream',
180
183
  result.headers,
181
184
  upstream.id,
185
+ upstream.url,
182
186
  );
183
187
  }
184
188
 
185
189
  // Cache 404 responses
186
190
  if (result.status === 404) {
187
- this.cache.setNegative(context, upstream.id);
191
+ await this.cache.setNegative(context, upstream.id, upstream.url);
188
192
  }
189
193
 
190
194
  return result;
@@ -210,15 +214,15 @@ export abstract class BaseUpstream {
210
214
  /**
211
215
  * Invalidate cache for a resource pattern.
212
216
  */
213
- public invalidateCache(pattern: RegExp): number {
217
+ public async invalidateCache(pattern: RegExp): Promise<number> {
214
218
  return this.cache.invalidatePattern(pattern);
215
219
  }
216
220
 
217
221
  /**
218
222
  * Clear all cache entries.
219
223
  */
220
- public clearCache(): void {
221
- this.cache.clear();
224
+ public async clearCache(): Promise<void> {
225
+ await this.cache.clear();
222
226
  }
223
227
 
224
228
  /**
@@ -501,12 +505,13 @@ export abstract class BaseUpstream {
501
505
  );
502
506
 
503
507
  if (result.success && result.body) {
504
- this.cache.set(
508
+ await this.cache.set(
505
509
  context,
506
510
  Buffer.isBuffer(result.body) ? result.body : Buffer.from(JSON.stringify(result.body)),
507
511
  result.headers['content-type'] || 'application/octet-stream',
508
512
  result.headers,
509
513
  upstream.id,
514
+ upstream.url,
510
515
  );
511
516
  return; // Successfully revalidated
512
517
  }