@enactprotocol/api 2.0.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/src/types.ts ADDED
@@ -0,0 +1,468 @@
1
+ /**
2
+ * TypeScript types for Enact Registry API v2
3
+ * Based on docs/API.md and docs/REGISTRY-SPEC.md specification
4
+ */
5
+
6
+ /**
7
+ * API error response
8
+ */
9
+ export interface ApiError {
10
+ error: {
11
+ code: string;
12
+ message: string;
13
+ details?: Record<string, unknown> | undefined;
14
+ };
15
+ }
16
+
17
+ /**
18
+ * OAuth provider types
19
+ */
20
+ export type OAuthProvider = "github" | "google" | "microsoft";
21
+
22
+ /**
23
+ * Author/user information from API
24
+ */
25
+ export interface ApiAuthor {
26
+ username: string;
27
+ avatar_url?: string | undefined;
28
+ }
29
+
30
+ /**
31
+ * Version metadata object (v2)
32
+ */
33
+ export interface VersionMetadata {
34
+ /** Version string (e.g., "1.2.0") */
35
+ version: string;
36
+ /** Publication timestamp */
37
+ published_at: string;
38
+ /** Download count for this version */
39
+ downloads: number;
40
+ /** SHA-256 hash of bundle */
41
+ bundle_hash: string;
42
+ /** Bundle size in bytes */
43
+ bundle_size?: number | undefined;
44
+ /** Whether this version is yanked */
45
+ yanked: boolean;
46
+ /** Attestation summary (optional) */
47
+ attestation_summary?:
48
+ | {
49
+ auditor_count: number;
50
+ }
51
+ | undefined;
52
+ }
53
+
54
+ /**
55
+ * Tool search result item
56
+ */
57
+ export interface ToolSearchResult {
58
+ /** Tool name (e.g., "alice/utils/greeter") */
59
+ name: string;
60
+ /** Tool description */
61
+ description: string;
62
+ /** Tool tags */
63
+ tags: string[];
64
+ /** Latest published version */
65
+ version: string;
66
+ /** Tool author */
67
+ author: ApiAuthor;
68
+ /** Total downloads */
69
+ downloads: number;
70
+ /** Trust status */
71
+ trust_status?:
72
+ | {
73
+ auditor_count: number;
74
+ }
75
+ | undefined;
76
+ }
77
+
78
+ /**
79
+ * Tool metadata from GET /tools/{name}
80
+ */
81
+ export interface ToolMetadata {
82
+ /** Tool name */
83
+ name: string;
84
+ /** Tool description */
85
+ description: string;
86
+ /** Tool tags */
87
+ tags: string[];
88
+ /** SPDX license identifier */
89
+ license: string;
90
+ /** Tool author */
91
+ author: ApiAuthor;
92
+ /** Repository URL */
93
+ repository?: string | undefined;
94
+ /** Homepage URL */
95
+ homepage?: string | undefined;
96
+ /** Creation timestamp */
97
+ created_at: string;
98
+ /** Last update timestamp */
99
+ updated_at: string;
100
+ /** Latest version */
101
+ latest_version: string;
102
+ /** Version list (paginated) */
103
+ versions: VersionMetadata[];
104
+ /** Total number of versions */
105
+ versions_total: number;
106
+ /** Total downloads across all versions */
107
+ total_downloads: number;
108
+ }
109
+
110
+ /**
111
+ * Bundle information
112
+ */
113
+ export interface BundleInfo {
114
+ /** SHA-256 hash */
115
+ hash: string;
116
+ /** Size in bytes */
117
+ size: number;
118
+ /** Download URL */
119
+ download_url: string;
120
+ }
121
+
122
+ /**
123
+ * Tool version details from GET /tools/{name}/versions/{version}
124
+ */
125
+ export interface ToolVersionDetails {
126
+ /** Tool name */
127
+ name: string;
128
+ /** Version */
129
+ version: string;
130
+ /** Tool description */
131
+ description: string;
132
+ /** SPDX license identifier */
133
+ license: string;
134
+ /** Whether this version is yanked */
135
+ yanked: boolean;
136
+ /** Yank reason (if yanked) */
137
+ yank_reason?: string | undefined;
138
+ /** Replacement version (if yanked) */
139
+ yank_replacement?: string | undefined;
140
+ /** When it was yanked */
141
+ yanked_at?: string | undefined;
142
+ /** Full manifest object */
143
+ manifest: Record<string, unknown>;
144
+ /** Bundle information */
145
+ bundle: BundleInfo;
146
+ /** List of attestations */
147
+ attestations: Attestation[];
148
+ /** Who published this version */
149
+ published_by: ApiAuthor;
150
+ /** Publication timestamp */
151
+ published_at: string;
152
+ /** Download count for this version */
153
+ downloads: number;
154
+ }
155
+
156
+ /**
157
+ * Single attestation record (v2 - auditor-only)
158
+ */
159
+ export interface Attestation {
160
+ /** Auditor email (from Sigstore certificate) */
161
+ auditor: string;
162
+ /** OAuth provider used for attestation */
163
+ auditor_provider: string;
164
+ /** Signing timestamp */
165
+ signed_at: string;
166
+ /** Rekor transparency log ID */
167
+ rekor_log_id: string;
168
+ /** Rekor transparency log index */
169
+ rekor_log_index?: number | undefined;
170
+ /** Verification status */
171
+ verification?:
172
+ | {
173
+ verified: boolean;
174
+ verified_at: string;
175
+ rekor_verified: boolean;
176
+ certificate_verified: boolean;
177
+ signature_verified: boolean;
178
+ }
179
+ | undefined;
180
+ }
181
+
182
+ /**
183
+ * Feedback aggregates from GET /tools/{name}/feedback
184
+ */
185
+ export interface FeedbackAggregates {
186
+ /** Average rating (1-5) */
187
+ rating: number;
188
+ /** Number of ratings */
189
+ rating_count: number;
190
+ /** Total downloads */
191
+ downloads: number;
192
+ }
193
+
194
+ /**
195
+ * User profile from GET /users/{username}
196
+ */
197
+ export interface UserProfile {
198
+ /** Username */
199
+ username: string;
200
+ /** Display name */
201
+ display_name?: string | undefined;
202
+ /** Avatar URL */
203
+ avatar_url?: string | undefined;
204
+ /** Account creation date */
205
+ created_at: string;
206
+ /** Number of tools published */
207
+ tools_count?: number | undefined;
208
+ }
209
+
210
+ /**
211
+ * Current user info from GET /auth/me (v2)
212
+ */
213
+ export interface CurrentUser {
214
+ /** User ID */
215
+ id: string;
216
+ /** Username */
217
+ username: string;
218
+ /** Email address */
219
+ email: string;
220
+ /** Namespaces the user owns */
221
+ namespaces: string[];
222
+ /** Account creation date */
223
+ created_at: string;
224
+ }
225
+
226
+ /**
227
+ * OAuth login request
228
+ */
229
+ export interface OAuthLoginRequest {
230
+ /** OAuth provider */
231
+ provider: OAuthProvider;
232
+ /** Redirect URI for callback */
233
+ redirect_uri: string;
234
+ }
235
+
236
+ /**
237
+ * OAuth login response
238
+ */
239
+ export interface OAuthLoginResponse {
240
+ /** Authorization URL to redirect user to */
241
+ auth_url: string;
242
+ }
243
+
244
+ /**
245
+ * OAuth callback request
246
+ */
247
+ export interface OAuthCallbackRequest {
248
+ /** OAuth provider */
249
+ provider: OAuthProvider;
250
+ /** Authorization code from OAuth provider */
251
+ code: string;
252
+ }
253
+
254
+ /**
255
+ * OAuth token response
256
+ */
257
+ export interface OAuthTokenResponse {
258
+ /** Access token (JWT) */
259
+ access_token: string;
260
+ /** Refresh token */
261
+ refresh_token: string;
262
+ /** Token expiration in seconds */
263
+ expires_in: number;
264
+ /** User information */
265
+ user: {
266
+ id: string;
267
+ username: string;
268
+ email: string;
269
+ };
270
+ }
271
+
272
+ /**
273
+ * Refresh token request
274
+ */
275
+ export interface RefreshTokenRequest {
276
+ /** Refresh token */
277
+ refresh_token: string;
278
+ }
279
+
280
+ /**
281
+ * Refresh token response
282
+ */
283
+ export interface RefreshTokenResponse {
284
+ /** New access token */
285
+ access_token: string;
286
+ /** Token expiration in seconds */
287
+ expires_in: number;
288
+ }
289
+
290
+ /**
291
+ * Publish response from POST /tools/{name} (v2 - single POST)
292
+ */
293
+ export interface PublishResponse {
294
+ /** Tool name */
295
+ name: string;
296
+ /** Published version */
297
+ version: string;
298
+ /** Bundle hash */
299
+ bundle_hash: string;
300
+ /** Bundle size */
301
+ bundle_size: number;
302
+ /** Download URL */
303
+ download_url: string;
304
+ /** Publication timestamp */
305
+ published_at: string;
306
+ }
307
+
308
+ /**
309
+ * Yank version request
310
+ */
311
+ export interface YankVersionRequest {
312
+ /** Reason for yanking */
313
+ reason?: string | undefined;
314
+ /** Replacement version to recommend */
315
+ replacement_version?: string | undefined;
316
+ }
317
+
318
+ /**
319
+ * Yank version response
320
+ */
321
+ export interface YankVersionResponse {
322
+ /** Whether version is yanked */
323
+ yanked: true;
324
+ /** Version that was yanked */
325
+ version: string;
326
+ /** Reason for yanking */
327
+ reason?: string | undefined;
328
+ /** Replacement version */
329
+ replacement_version?: string | undefined;
330
+ /** When it was yanked */
331
+ yanked_at: string;
332
+ /** Informational message */
333
+ message?: string | undefined;
334
+ }
335
+
336
+ /**
337
+ * Unyank version response
338
+ */
339
+ export interface UnyankVersionResponse {
340
+ /** Whether version is yanked */
341
+ yanked: false;
342
+ /** Version that was unyanked */
343
+ version: string;
344
+ /** When it was unyanked */
345
+ unyanked_at: string;
346
+ }
347
+
348
+ /**
349
+ * Submit attestation response (v2)
350
+ */
351
+ export interface AttestationResponse {
352
+ /** Auditor email */
353
+ auditor: string;
354
+ /** OAuth provider */
355
+ auditor_provider: string;
356
+ /** Signing timestamp */
357
+ signed_at: string;
358
+ /** Rekor log ID */
359
+ rekor_log_id: string;
360
+ /** Rekor log index */
361
+ rekor_log_index?: number | undefined;
362
+ /** Verification result */
363
+ verification: {
364
+ verified: boolean;
365
+ verified_at: string;
366
+ rekor_verified: boolean;
367
+ certificate_verified: boolean;
368
+ signature_verified: boolean;
369
+ };
370
+ }
371
+
372
+ /**
373
+ * Revoke attestation response
374
+ */
375
+ export interface RevokeAttestationResponse {
376
+ /** Auditor email */
377
+ auditor: string;
378
+ /** Whether revocation succeeded */
379
+ revoked: true;
380
+ /** When it was revoked */
381
+ revoked_at: string;
382
+ }
383
+
384
+ /**
385
+ * Trusted auditor entry
386
+ */
387
+ export interface TrustedAuditor {
388
+ /** Auditor identity (email) */
389
+ identity: string;
390
+ /** When they were added to trust list */
391
+ added_at: string;
392
+ }
393
+
394
+ /**
395
+ * User trust configuration
396
+ */
397
+ export interface UserTrustConfig {
398
+ /** Username */
399
+ username: string;
400
+ /** List of trusted auditors */
401
+ trusted_auditors: TrustedAuditor[];
402
+ }
403
+
404
+ /**
405
+ * Update trust configuration request
406
+ */
407
+ export interface UpdateTrustConfigRequest {
408
+ /** Array of auditor emails to trust */
409
+ trusted_auditors: string[];
410
+ }
411
+
412
+ /**
413
+ * Update trust configuration response
414
+ */
415
+ export interface UpdateTrustConfigResponse {
416
+ /** Updated list of trusted auditors */
417
+ trusted_auditors: TrustedAuditor[];
418
+ /** When the config was updated */
419
+ updated_at: string;
420
+ }
421
+
422
+ /**
423
+ * Rate limit info from response headers
424
+ */
425
+ export interface RateLimitInfo {
426
+ /** Max requests per window */
427
+ limit: number;
428
+ /** Remaining requests */
429
+ remaining: number;
430
+ /** Unix timestamp when limit resets */
431
+ reset: number;
432
+ }
433
+
434
+ /**
435
+ * HTTP error codes from API v2
436
+ */
437
+ export type ApiErrorCode =
438
+ | "BAD_REQUEST"
439
+ | "UNAUTHORIZED"
440
+ | "FORBIDDEN"
441
+ | "NOT_FOUND"
442
+ | "CONFLICT"
443
+ | "VERSION_YANKED"
444
+ | "BUNDLE_TOO_LARGE"
445
+ | "VALIDATION_ERROR"
446
+ | "ATTESTATION_VERIFICATION_FAILED"
447
+ | "RATE_LIMITED"
448
+ | "INTERNAL_ERROR";
449
+
450
+ /**
451
+ * HTTP status codes
452
+ */
453
+ export const HTTP_STATUS = {
454
+ OK: 200,
455
+ CREATED: 201,
456
+ NO_CONTENT: 204,
457
+ REDIRECT: 302,
458
+ BAD_REQUEST: 400,
459
+ UNAUTHORIZED: 401,
460
+ FORBIDDEN: 403,
461
+ NOT_FOUND: 404,
462
+ CONFLICT: 409,
463
+ GONE: 410,
464
+ PAYLOAD_TOO_LARGE: 413,
465
+ VALIDATION_ERROR: 422,
466
+ RATE_LIMITED: 429,
467
+ INTERNAL_ERROR: 500,
468
+ } as const;
package/src/utils.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Utility functions for API package
3
+ * Copied from @enactprotocol/shared to avoid circular browser dependency
4
+ */
5
+
6
+ /**
7
+ * Convert OIDC issuer URL to provider name
8
+ */
9
+ function issuerToProvider(issuer: string): string | undefined {
10
+ if (issuer.includes("github.com")) return "github";
11
+ if (issuer.includes("accounts.google.com")) return "google";
12
+ if (issuer.includes("login.microsoftonline.com")) return "microsoft";
13
+ if (issuer.includes("gitlab.com")) return "gitlab";
14
+ return undefined;
15
+ }
16
+
17
+ /**
18
+ * Convert OIDC identity to provider:identity format
19
+ * @param email - Email from Sigstore certificate
20
+ * @param issuer - OIDC issuer URL (optional, improves accuracy)
21
+ * @param username - Provider username if known (optional)
22
+ * @returns Identity in provider:identity format (e.g., github:keithagroves)
23
+ */
24
+ export function emailToProviderIdentity(email: string, issuer?: string, username?: string): string {
25
+ // If we have a username and can determine the provider, use that
26
+ if (username && issuer) {
27
+ const provider = issuerToProvider(issuer);
28
+ if (provider) {
29
+ return `${provider}:${username}`;
30
+ }
31
+ }
32
+
33
+ // Determine provider from issuer URL if available
34
+ if (issuer) {
35
+ const provider = issuerToProvider(issuer);
36
+ if (provider) {
37
+ // Try to extract username from email for GitHub
38
+ if (provider === "github" && email.endsWith("@users.noreply.github.com")) {
39
+ // GitHub noreply format: "123456+username@users.noreply.github.com"
40
+ // or just "username@users.noreply.github.com"
41
+ const localPart = email.replace("@users.noreply.github.com", "");
42
+ const plusIndex = localPart.indexOf("+");
43
+ const extractedUsername = plusIndex >= 0 ? localPart.slice(plusIndex + 1) : localPart;
44
+ return `github:${extractedUsername}`;
45
+ }
46
+ // Use email as the identity since we don't have username
47
+ return `${provider}:${email}`;
48
+ }
49
+ }
50
+
51
+ // Common OIDC providers and their email domains (fallback)
52
+ const providerMap: Record<string, string> = {
53
+ "@users.noreply.github.com": "github",
54
+ "@github.com": "github",
55
+ "@gmail.com": "google",
56
+ "@googlemail.com": "google",
57
+ "@outlook.com": "microsoft",
58
+ "@hotmail.com": "microsoft",
59
+ "@live.com": "microsoft",
60
+ };
61
+
62
+ // Try to match provider by email domain
63
+ for (const [domain, provider] of Object.entries(providerMap)) {
64
+ if (email.endsWith(domain)) {
65
+ let extractedUsername = email.substring(0, email.length - domain.length);
66
+ // Handle GitHub noreply format: "123456+username@users.noreply.github.com"
67
+ if (provider === "github" && domain === "@users.noreply.github.com") {
68
+ const plusIndex = extractedUsername.indexOf("+");
69
+ if (plusIndex >= 0) {
70
+ extractedUsername = extractedUsername.slice(plusIndex + 1);
71
+ }
72
+ }
73
+ return `${provider}:${extractedUsername}`;
74
+ }
75
+ }
76
+
77
+ // If no match, check for GitHub workflow identity
78
+ // Format: https://github.com/{org}/{workflow}
79
+ if (email.startsWith("https://github.com/")) {
80
+ const path = email.replace("https://github.com/", "");
81
+ return `github:${path}`;
82
+ }
83
+
84
+ // Fall back to email as-is
85
+ return email;
86
+ }