@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
|
@@ -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
|
}
|