feat(external-registry): Enhance authentication handling and update UI for external registries
This commit is contained in:
		| @@ -32,11 +32,11 @@ export class ExternalRegistry extends plugins.smartdata.SmartDataDbDoc<ExternalR | |||||||
|       type: registryDataArg.type || 'docker', |       type: registryDataArg.type || 'docker', | ||||||
|       name: registryDataArg.name || '', |       name: registryDataArg.name || '', | ||||||
|       url: registryDataArg.url || '', |       url: registryDataArg.url || '', | ||||||
|       username: registryDataArg.username || '', |       username: registryDataArg.username, | ||||||
|       password: registryDataArg.password || '', |       password: registryDataArg.password, | ||||||
|       description: registryDataArg.description, |       description: registryDataArg.description, | ||||||
|       isDefault: registryDataArg.isDefault || false, |       isDefault: registryDataArg.isDefault || false, | ||||||
|       authType: registryDataArg.authType || 'basic', |       authType: registryDataArg.authType || (registryDataArg.username || registryDataArg.password ? 'basic' : 'none'), | ||||||
|       insecure: registryDataArg.insecure || false, |       insecure: registryDataArg.insecure || false, | ||||||
|       namespace: registryDataArg.namespace, |       namespace: registryDataArg.namespace, | ||||||
|       proxy: registryDataArg.proxy, |       proxy: registryDataArg.proxy, | ||||||
| @@ -123,26 +123,38 @@ export class ExternalRegistry extends plugins.smartdata.SmartDataDbDoc<ExternalR | |||||||
|       // For Docker registries, try to access the v2 API |       // For Docker registries, try to access the v2 API | ||||||
|       if (this.data.type === 'docker') { |       if (this.data.type === 'docker') { | ||||||
|         const registryUrl = this.data.url.replace(/\/$/, ''); // Remove trailing slash |         const registryUrl = this.data.url.replace(/\/$/, ''); // Remove trailing slash | ||||||
|         const authHeader = 'Basic ' + Buffer.from(`${this.data.username}:${this.data.password}`).toString('base64'); |          | ||||||
|  |         // Build headers based on auth type | ||||||
|  |         const headers: any = {}; | ||||||
|  |         if (this.data.authType === 'basic' && this.data.username && this.data.password) { | ||||||
|  |           headers['Authorization'] = 'Basic ' + Buffer.from(`${this.data.username}:${this.data.password}`).toString('base64'); | ||||||
|  |         } else if (this.data.authType === 'token' && this.data.password) { | ||||||
|  |           // For token auth, password field contains the token | ||||||
|  |           headers['Authorization'] = `Bearer ${this.data.password}`; | ||||||
|  |         } | ||||||
|  |         // For 'none' auth type or missing credentials, no auth header is added | ||||||
|          |          | ||||||
|         // Try to access the Docker Registry v2 API |         // Try to access the Docker Registry v2 API | ||||||
|         const response = await fetch(`${registryUrl}/v2/`, { |         const response = await fetch(`${registryUrl}/v2/`, { | ||||||
|           headers: { |           headers, | ||||||
|             'Authorization': authHeader, |  | ||||||
|           }, |  | ||||||
|           // Allow insecure if configured |           // Allow insecure if configured | ||||||
|           ...(this.data.insecure ? { rejectUnauthorized: false } : {}), |           ...(this.data.insecure ? { rejectUnauthorized: false } : {}), | ||||||
|         }).catch(err => { |         }).catch(err => { | ||||||
|           throw new Error(`Failed to connect: ${err.message}`); |           throw new Error(`Failed to connect: ${err.message}`); | ||||||
|         }); |         }); | ||||||
|          |          | ||||||
|         if (response.status === 200 || response.status === 401) { |         if (response.status === 200) { | ||||||
|           // 200 means successful auth, 401 means registry exists but needs auth |           // 200 means successful (either public or authenticated) | ||||||
|           this.data.status = 'active'; |           this.data.status = 'active'; | ||||||
|           this.data.lastVerified = Date.now(); |           this.data.lastVerified = Date.now(); | ||||||
|           this.data.lastError = undefined; |           this.data.lastError = undefined; | ||||||
|           await this.save(); |           await this.save(); | ||||||
|           return { success: true, message: 'Registry connection successful' }; |           return { success: true, message: 'Registry connection successful' }; | ||||||
|  |         } else if (response.status === 401 && this.data.authType === 'none') { | ||||||
|  |           // 401 with no auth means registry exists but needs auth | ||||||
|  |           throw new Error('Registry requires authentication'); | ||||||
|  |         } else if (response.status === 401) { | ||||||
|  |           throw new Error('Authentication failed - check credentials'); | ||||||
|         } else { |         } else { | ||||||
|           throw new Error(`Registry returned status ${response.status}`); |           throw new Error(`Registry returned status ${response.status}`); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -19,14 +19,14 @@ export interface IExternalRegistry { | |||||||
|     url: string; |     url: string; | ||||||
|      |      | ||||||
|     /** |     /** | ||||||
|      * Username for authentication |      * Username for authentication (optional for token-based or public registries) | ||||||
|      */ |      */ | ||||||
|     username: string; |     username?: string; | ||||||
|      |      | ||||||
|     /** |     /** | ||||||
|      * Password or access token for authentication |      * Password, access token, or API key for authentication (optional for public registries) | ||||||
|      */ |      */ | ||||||
|     password: string; |     password?: string; | ||||||
|      |      | ||||||
|     /** |     /** | ||||||
|      * Optional description |      * Optional description | ||||||
| @@ -41,7 +41,7 @@ export interface IExternalRegistry { | |||||||
|     /** |     /** | ||||||
|      * Authentication type |      * Authentication type | ||||||
|      */ |      */ | ||||||
|     authType?: 'basic' | 'token' | 'oauth2'; |     authType?: 'none' | 'basic' | 'token' | 'oauth2'; | ||||||
|      |      | ||||||
|     /** |     /** | ||||||
|      * Allow insecure registry connections (HTTP or self-signed certs) |      * Allow insecure registry connections (HTTP or self-signed certs) | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ export class CloudlyViewExternalRegistries extends DeesElement { | |||||||
|             Name: html`${registry.data.name}${registry.data.isDefault ? html`<span class="default-badge">DEFAULT</span>` : ''}`, |             Name: html`${registry.data.name}${registry.data.isDefault ? html`<span class="default-badge">DEFAULT</span>` : ''}`, | ||||||
|             Type: html`<span class="type-badge type-${registry.data.type}">${registry.data.type.toUpperCase()}</span>`, |             Type: html`<span class="type-badge type-${registry.data.type}">${registry.data.type.toUpperCase()}</span>`, | ||||||
|             URL: registry.data.url, |             URL: registry.data.url, | ||||||
|             Username: registry.data.username, |             Auth: registry.data.authType === 'none' ? 'Public' : (registry.data.username || 'Token Auth'), | ||||||
|             Namespace: registry.data.namespace || '-', |             Namespace: registry.data.namespace || '-', | ||||||
|             Status: html`<span class="status-badge status-${registry.data.status || 'unverified'}">${(registry.data.status || 'unverified').toUpperCase()}</span>`, |             Status: html`<span class="status-badge status-${registry.data.status || 'unverified'}">${(registry.data.status || 'unverified').toUpperCase()}</span>`, | ||||||
|             'Last Verified': registry.data.lastVerified ? new Date(registry.data.lastVerified).toLocaleString() : 'Never', |             'Last Verified': registry.data.lastVerified ? new Date(registry.data.lastVerified).toLocaleString() : 'Never', | ||||||
| @@ -130,16 +130,14 @@ export class CloudlyViewExternalRegistries extends DeesElement { | |||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
|                       .key=${'username'}  |                       .key=${'username'}  | ||||||
|                       .label=${'Username'}  |                       .label=${'Username (only needed for basic auth)'}  | ||||||
|                       .placeholder=${'username'} |                       .placeholder=${'username or leave empty for token auth'}> | ||||||
|                       .required=${true}> |  | ||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
|                       .key=${'password'}  |                       .key=${'password'}  | ||||||
|                       .label=${'Password / Access Token'}  |                       .label=${'Password / Token (NPM _authToken, Docker access token, etc.)'}  | ||||||
|                       .placeholder=${'••••••••'} |                       .placeholder=${'Token or password'} | ||||||
|                       .isPasswordBool=${true} |                       .isPasswordBool=${true}> | ||||||
|                       .required=${true}> |  | ||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
|                       .key=${'namespace'}  |                       .key=${'namespace'}  | ||||||
| @@ -155,11 +153,12 @@ export class CloudlyViewExternalRegistries extends DeesElement { | |||||||
|                       .key=${'authType'}  |                       .key=${'authType'}  | ||||||
|                       .label=${'Authentication Type'}  |                       .label=${'Authentication Type'}  | ||||||
|                       .options=${[ |                       .options=${[ | ||||||
|                         {key: 'basic', option: 'Basic Auth'}, |                         {key: 'none', option: 'No Authentication (Public Registry)'}, | ||||||
|                         {key: 'token', option: 'Token'}, |                         {key: 'basic', option: 'Basic Auth (Username + Password)'}, | ||||||
|                         {key: 'oauth2', option: 'OAuth2'} |                         {key: 'token', option: 'Token Only (NPM, GitHub, GitLab tokens)'}, | ||||||
|  |                         {key: 'oauth2', option: 'OAuth2 (Advanced)'} | ||||||
|                       ]} |                       ]} | ||||||
|                       .value=${'basic'}> |                       .value=${'none'}> | ||||||
|                     </dees-input-dropdown> |                     </dees-input-dropdown> | ||||||
|                     <dees-input-checkbox  |                     <dees-input-checkbox  | ||||||
|                       .key=${'isDefault'}  |                       .key=${'isDefault'}  | ||||||
| @@ -242,14 +241,14 @@ export class CloudlyViewExternalRegistries extends DeesElement { | |||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
|                       .key=${'username'}  |                       .key=${'username'}  | ||||||
|                       .label=${'Username'}  |                       .label=${'Username (only needed for basic auth)'}  | ||||||
|                       .value=${registry.data.username} |                       .value=${registry.data.username || ''} | ||||||
|                       .required=${true}> |                       .placeholder=${'Leave empty for token auth'}> | ||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
|                       .key=${'password'}  |                       .key=${'password'}  | ||||||
|                       .label=${'Password / Access Token (leave empty to keep current)'}  |                       .label=${'Password / Token (leave empty to keep current)'}  | ||||||
|                       .placeholder=${'••••••••'} |                       .placeholder=${'New token or password'} | ||||||
|                       .isPasswordBool=${true}> |                       .isPasswordBool=${true}> | ||||||
|                     </dees-input-text> |                     </dees-input-text> | ||||||
|                     <dees-input-text  |                     <dees-input-text  | ||||||
| @@ -266,11 +265,12 @@ export class CloudlyViewExternalRegistries extends DeesElement { | |||||||
|                       .key=${'authType'}  |                       .key=${'authType'}  | ||||||
|                       .label=${'Authentication Type'}  |                       .label=${'Authentication Type'}  | ||||||
|                       .options=${[ |                       .options=${[ | ||||||
|                         {key: 'basic', option: 'Basic Auth'}, |                         {key: 'none', option: 'No Authentication (Public Registry)'}, | ||||||
|                         {key: 'token', option: 'Token'}, |                         {key: 'basic', option: 'Basic Auth (Username + Password)'}, | ||||||
|                         {key: 'oauth2', option: 'OAuth2'} |                         {key: 'token', option: 'Token Only (NPM, GitHub, GitLab tokens)'}, | ||||||
|  |                         {key: 'oauth2', option: 'OAuth2 (Advanced)'} | ||||||
|                       ]} |                       ]} | ||||||
|                       .value=${registry.data.authType || 'basic'}> |                       .value=${registry.data.authType || 'none'}> | ||||||
|                     </dees-input-dropdown> |                     </dees-input-dropdown> | ||||||
|                     <dees-input-checkbox  |                     <dees-input-checkbox  | ||||||
|                       .key=${'isDefault'}  |                       .key=${'isDefault'}  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user