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', | ||||
|       name: registryDataArg.name || '', | ||||
|       url: registryDataArg.url || '', | ||||
|       username: registryDataArg.username || '', | ||||
|       password: registryDataArg.password || '', | ||||
|       username: registryDataArg.username, | ||||
|       password: registryDataArg.password, | ||||
|       description: registryDataArg.description, | ||||
|       isDefault: registryDataArg.isDefault || false, | ||||
|       authType: registryDataArg.authType || 'basic', | ||||
|       authType: registryDataArg.authType || (registryDataArg.username || registryDataArg.password ? 'basic' : 'none'), | ||||
|       insecure: registryDataArg.insecure || false, | ||||
|       namespace: registryDataArg.namespace, | ||||
|       proxy: registryDataArg.proxy, | ||||
| @@ -123,26 +123,38 @@ export class ExternalRegistry extends plugins.smartdata.SmartDataDbDoc<ExternalR | ||||
|       // For Docker registries, try to access the v2 API | ||||
|       if (this.data.type === 'docker') { | ||||
|         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 | ||||
|         const response = await fetch(`${registryUrl}/v2/`, { | ||||
|           headers: { | ||||
|             'Authorization': authHeader, | ||||
|           }, | ||||
|           headers, | ||||
|           // Allow insecure if configured | ||||
|           ...(this.data.insecure ? { rejectUnauthorized: false } : {}), | ||||
|         }).catch(err => { | ||||
|           throw new Error(`Failed to connect: ${err.message}`); | ||||
|         }); | ||||
|          | ||||
|         if (response.status === 200 || response.status === 401) { | ||||
|           // 200 means successful auth, 401 means registry exists but needs auth | ||||
|         if (response.status === 200) { | ||||
|           // 200 means successful (either public or authenticated) | ||||
|           this.data.status = 'active'; | ||||
|           this.data.lastVerified = Date.now(); | ||||
|           this.data.lastError = undefined; | ||||
|           await this.save(); | ||||
|           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 { | ||||
|           throw new Error(`Registry returned status ${response.status}`); | ||||
|         } | ||||
|   | ||||
| @@ -19,14 +19,14 @@ export interface IExternalRegistry { | ||||
|     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 | ||||
| @@ -41,7 +41,7 @@ export interface IExternalRegistry { | ||||
|     /** | ||||
|      * Authentication type | ||||
|      */ | ||||
|     authType?: 'basic' | 'token' | 'oauth2'; | ||||
|     authType?: 'none' | 'basic' | 'token' | 'oauth2'; | ||||
|      | ||||
|     /** | ||||
|      * 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>` : ''}`, | ||||
|             Type: html`<span class="type-badge type-${registry.data.type}">${registry.data.type.toUpperCase()}</span>`, | ||||
|             URL: registry.data.url, | ||||
|             Username: registry.data.username, | ||||
|             Auth: registry.data.authType === 'none' ? 'Public' : (registry.data.username || 'Token Auth'), | ||||
|             Namespace: registry.data.namespace || '-', | ||||
|             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', | ||||
| @@ -130,16 +130,14 @@ export class CloudlyViewExternalRegistries extends DeesElement { | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'username'}  | ||||
|                       .label=${'Username'}  | ||||
|                       .placeholder=${'username'} | ||||
|                       .required=${true}> | ||||
|                       .label=${'Username (only needed for basic auth)'}  | ||||
|                       .placeholder=${'username or leave empty for token auth'}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'password'}  | ||||
|                       .label=${'Password / Access Token'}  | ||||
|                       .placeholder=${'••••••••'} | ||||
|                       .isPasswordBool=${true} | ||||
|                       .required=${true}> | ||||
|                       .label=${'Password / Token (NPM _authToken, Docker access token, etc.)'}  | ||||
|                       .placeholder=${'Token or password'} | ||||
|                       .isPasswordBool=${true}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'namespace'}  | ||||
| @@ -155,11 +153,12 @@ export class CloudlyViewExternalRegistries extends DeesElement { | ||||
|                       .key=${'authType'}  | ||||
|                       .label=${'Authentication Type'}  | ||||
|                       .options=${[ | ||||
|                         {key: 'basic', option: 'Basic Auth'}, | ||||
|                         {key: 'token', option: 'Token'}, | ||||
|                         {key: 'oauth2', option: 'OAuth2'} | ||||
|                         {key: 'none', option: 'No Authentication (Public Registry)'}, | ||||
|                         {key: 'basic', option: 'Basic Auth (Username + Password)'}, | ||||
|                         {key: 'token', option: 'Token Only (NPM, GitHub, GitLab tokens)'}, | ||||
|                         {key: 'oauth2', option: 'OAuth2 (Advanced)'} | ||||
|                       ]} | ||||
|                       .value=${'basic'}> | ||||
|                       .value=${'none'}> | ||||
|                     </dees-input-dropdown> | ||||
|                     <dees-input-checkbox  | ||||
|                       .key=${'isDefault'}  | ||||
| @@ -242,14 +241,14 @@ export class CloudlyViewExternalRegistries extends DeesElement { | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'username'}  | ||||
|                       .label=${'Username'}  | ||||
|                       .value=${registry.data.username} | ||||
|                       .required=${true}> | ||||
|                       .label=${'Username (only needed for basic auth)'}  | ||||
|                       .value=${registry.data.username || ''} | ||||
|                       .placeholder=${'Leave empty for token auth'}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
|                       .key=${'password'}  | ||||
|                       .label=${'Password / Access Token (leave empty to keep current)'}  | ||||
|                       .placeholder=${'••••••••'} | ||||
|                       .label=${'Password / Token (leave empty to keep current)'}  | ||||
|                       .placeholder=${'New token or password'} | ||||
|                       .isPasswordBool=${true}> | ||||
|                     </dees-input-text> | ||||
|                     <dees-input-text  | ||||
| @@ -266,11 +265,12 @@ export class CloudlyViewExternalRegistries extends DeesElement { | ||||
|                       .key=${'authType'}  | ||||
|                       .label=${'Authentication Type'}  | ||||
|                       .options=${[ | ||||
|                         {key: 'basic', option: 'Basic Auth'}, | ||||
|                         {key: 'token', option: 'Token'}, | ||||
|                         {key: 'oauth2', option: 'OAuth2'} | ||||
|                         {key: 'none', option: 'No Authentication (Public Registry)'}, | ||||
|                         {key: 'basic', option: 'Basic Auth (Username + Password)'}, | ||||
|                         {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-checkbox  | ||||
|                       .key=${'isDefault'}  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user