refactor(auth): use remote.Resolve(), drop resolveRemoteEntry
All auth commands now use the same resolution path as every other command. Local -r flags removed; inherited from root persistent flag.
- commit
- 979c2f96e0731fe15d2c05ab8e05db6c91684834
- author
- brice · · 2026-04-04 11:27 UTC
- parent
- 688083d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +79 -106
M internal/adapters/in/cli/auth.go +71 -104
| @@ -203,10 +203,7 @@ available while Gordon is running.`, | |||
| 203 | 203 | ||
| 204 | 204 | // newAuthLoginCmd creates the login command for remote authentication. | |
| 205 | 205 | func newAuthLoginCmd() *cobra.Command { | |
| 206 | - | var ( | |
| 207 | - | remoteName string | |
| 208 | - | token string | |
| 209 | - | ) | |
| 206 | + | var token string | |
| 210 | 207 | ||
| 211 | 208 | cmd := &cobra.Command{ | |
| 212 | 209 | Use: "login", | |
| @@ -224,31 +221,30 @@ Examples: | |||
| 224 | 221 | gordon auth login Verify existing token`, | |
| 225 | 222 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 226 | 223 | if token != "" { | |
| 227 | - | return runAuthLoginWithToken(cmd.Context(), remoteName, token, cmd.OutOrStdout()) | |
| 224 | + | return runAuthLoginWithToken(cmd.Context(), token, cmd.OutOrStdout()) | |
| 228 | 225 | } | |
| 229 | - | return runAuthLoginVerify(cmd.Context(), remoteName, cmd.OutOrStdout()) | |
| 226 | + | return runAuthLoginVerify(cmd.Context(), cmd.OutOrStdout()) | |
| 230 | 227 | }, | |
| 231 | 228 | } | |
| 232 | 229 | ||
| 233 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to authenticate with (defaults to active remote)") | |
| 234 | 230 | cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote") | |
| 235 | 231 | ||
| 236 | 232 | return cmd | |
| 237 | 233 | } | |
| 238 | 234 | ||
| 239 | - | func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io.Writer) error { | |
| 235 | + | func runAuthLoginWithToken(ctx context.Context, token string, out io.Writer) error { | |
| 240 | 236 | token = strings.TrimSpace(token) | |
| 241 | 237 | if token == "" { | |
| 242 | 238 | return fmt.Errorf("token cannot be empty") | |
| 243 | 239 | } | |
| 244 | 240 | ||
| 245 | - | resolvedName, remoteConfig, err := resolveRemoteEntry(remoteName) | |
| 246 | - | if err != nil { | |
| 247 | - | return err | |
| 241 | + | // Resolve remote — but use the new token (not the stored one) for verification. | |
| 242 | + | resolved, isRemote := remote.Resolve(remoteFlag, "", insecureTLSFlag) | |
| 243 | + | if !isRemote { | |
| 244 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") | |
| 248 | 245 | } | |
| 249 | 246 | ||
| 250 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName) | |
| 251 | - | client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...) | |
| 247 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(token, resolved.InsecureTLS)...) | |
| 252 | 248 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| 253 | 249 | defer cancel() | |
| 254 | 250 | verified := true | |
| @@ -257,47 +253,59 @@ func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io | |||
| 257 | 253 | verified = false | |
| 258 | 254 | } | |
| 259 | 255 | ||
| 260 | - | if err := remote.UpdateRemoteToken(resolvedName, token); err != nil { | |
| 256 | + | // Store token — requires a named remote. | |
| 257 | + | if resolved.Name == "" { | |
| 258 | + | _ = cliWriteLine(out, styles.RenderWarning("Ad-hoc URL: token verified but cannot be stored. Use 'gordon remotes add' to save this remote.")) | |
| 259 | + | return nil | |
| 260 | + | } | |
| 261 | + | if err := remote.UpdateRemoteToken(resolved.Name, token); err != nil { | |
| 261 | 262 | return fmt.Errorf("failed to save token: %w", err) | |
| 262 | 263 | } | |
| 263 | 264 | ||
| 264 | 265 | _ = cliWriteLine(out, "") | |
| 265 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolvedName))) | |
| 266 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolved.Name))) | |
| 266 | 267 | if verified { | |
| 267 | 268 | _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint")) | |
| 268 | 269 | } | |
| 269 | 270 | return nil | |
| 270 | 271 | } | |
| 271 | 272 | ||
| 272 | - | func runAuthLoginVerify(ctx context.Context, remoteName string, out io.Writer) error { | |
| 273 | - | resolvedName, entry, err := resolveRemoteEntry(remoteName) | |
| 274 | - | if err != nil { | |
| 275 | - | return err | |
| 273 | + | func runAuthLoginVerify(ctx context.Context, out io.Writer) error { | |
| 274 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) | |
| 275 | + | if !isRemote { | |
| 276 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") | |
| 276 | 277 | } | |
| 277 | - | ||
| 278 | - | token := remote.ResolveTokenForRemote(resolvedName, entry) | |
| 279 | - | if token == "" { | |
| 280 | - | return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", resolvedName) | |
| 278 | + | if resolved.Token == "" { | |
| 279 | + | name := resolved.Name | |
| 280 | + | if name == "" { | |
| 281 | + | name = resolved.URL | |
| 282 | + | } | |
| 283 | + | return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", name) | |
| 281 | 284 | } | |
| 282 | 285 | ||
| 283 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName) | |
| 284 | - | client := remote.NewClient(entry.URL, remoteClientOptions(token, insecureTLS)...) | |
| 286 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...) | |
| 285 | 287 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | |
| 286 | 288 | defer cancel() | |
| 287 | 289 | ||
| 288 | 290 | if _, err := client.GetStatus(verifyCtx); err != nil { | |
| 289 | - | _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", resolvedName, err))) | |
| 291 | + | name := resolved.Name | |
| 292 | + | if name == "" { | |
| 293 | + | name = resolved.URL | |
| 294 | + | } | |
| 295 | + | _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", name, err))) | |
| 290 | 296 | return fmt.Errorf("authentication failed") | |
| 291 | 297 | } | |
| 292 | 298 | ||
| 293 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", resolvedName))) | |
| 299 | + | name := resolved.Name | |
| 300 | + | if name == "" { | |
| 301 | + | name = resolved.URL | |
| 302 | + | } | |
| 303 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", name))) | |
| 294 | 304 | return nil | |
| 295 | 305 | } | |
| 296 | 306 | ||
| 297 | 307 | // newAuthShowTokenCmd creates the show-token command. | |
| 298 | 308 | func newAuthShowTokenCmd() *cobra.Command { | |
| 299 | - | var remoteName string | |
| 300 | - | ||
| 301 | 309 | cmd := &cobra.Command{ | |
| 302 | 310 | Use: "show-token", | |
| 303 | 311 | Short: "Print the stored token for a remote", | |
| @@ -313,38 +321,36 @@ Examples: | |||
| 313 | 321 | gordon auth show-token --remote prod Show token for specific remote | |
| 314 | 322 | gordon auth show-token | pbcopy Copy token to clipboard`, | |
| 315 | 323 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 316 | - | return runAuthShowToken(remoteName, cmd.OutOrStdout()) | |
| 324 | + | return runAuthShowToken(cmd.OutOrStdout()) | |
| 317 | 325 | }, | |
| 318 | 326 | } | |
| 319 | 327 | ||
| 320 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to show token for (defaults to active remote)") | |
| 321 | - | ||
| 322 | 328 | return cmd | |
| 323 | 329 | } | |
| 324 | 330 | ||
| 325 | - | func runAuthShowToken(remoteName string, out io.Writer) error { | |
| 326 | - | resolvedName, entry, err := resolveRemoteEntry(remoteName) | |
| 327 | - | if err != nil { | |
| 328 | - | return err | |
| 331 | + | func runAuthShowToken(out io.Writer) error { | |
| 332 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) | |
| 333 | + | if !isRemote { | |
| 334 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") | |
| 329 | 335 | } | |
| 330 | - | ||
| 331 | - | token := remote.ResolveTokenForRemote(resolvedName, entry) | |
| 332 | - | if token == "" { | |
| 333 | - | return fmt.Errorf("no token found for remote '%s'", resolvedName) | |
| 336 | + | if resolved.Token == "" { | |
| 337 | + | name := resolved.Name | |
| 338 | + | if name == "" { | |
| 339 | + | name = resolved.URL | |
| 340 | + | } | |
| 341 | + | return fmt.Errorf("no token found for remote '%s'", name) | |
| 334 | 342 | } | |
| 335 | 343 | ||
| 336 | 344 | if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) { | |
| 337 | 345 | _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback")) | |
| 338 | 346 | } | |
| 339 | 347 | ||
| 340 | - | _ = cliWriteLine(out, token) | |
| 348 | + | _ = cliWriteLine(out, resolved.Token) | |
| 341 | 349 | return nil | |
| 342 | 350 | } | |
| 343 | 351 | ||
| 344 | 352 | // newAuthLogoutCmd creates the logout command. | |
| 345 | 353 | func newAuthLogoutCmd() *cobra.Command { | |
| 346 | - | var remoteName string | |
| 347 | - | ||
| 348 | 354 | cmd := &cobra.Command{ | |
| 349 | 355 | Use: "logout", | |
| 350 | 356 | Short: "Remove stored authentication for a remote", | |
| @@ -357,49 +363,31 @@ Examples: | |||
| 357 | 363 | gordon auth logout Logout from active remote | |
| 358 | 364 | gordon auth logout --remote prod Logout from specific remote`, | |
| 359 | 365 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 360 | - | return runAuthLogout(remoteName, cmd.OutOrStdout()) | |
| 366 | + | return runAuthLogout(cmd.OutOrStdout()) | |
| 361 | 367 | }, | |
| 362 | 368 | } | |
| 363 | 369 | ||
| 364 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to logout from (defaults to active remote)") | |
| 365 | - | ||
| 366 | 370 | return cmd | |
| 367 | 371 | } | |
| 368 | 372 | ||
| 369 | - | func runAuthLogout(remoteName string, out io.Writer) error { | |
| 370 | - | resolvedName, _, err := resolveRemoteEntry(remoteName) | |
| 371 | - | if err != nil { | |
| 372 | - | return err | |
| 373 | + | func runAuthLogout(out io.Writer) error { | |
| 374 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) | |
| 375 | + | if !isRemote { | |
| 376 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") | |
| 377 | + | } | |
| 378 | + | if resolved.Name == "" { | |
| 379 | + | return fmt.Errorf("cannot logout from ad-hoc URL. Use 'gordon remotes add' to save this remote first") | |
| 373 | 380 | } | |
| 374 | 381 | ||
| 375 | - | if err := remote.ClearRemoteToken(resolvedName); err != nil { | |
| 382 | + | if err := remote.ClearRemoteToken(resolved.Name); err != nil { | |
| 376 | 383 | return fmt.Errorf("failed to clear token: %w", err) | |
| 377 | 384 | } | |
| 378 | 385 | ||
| 379 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolvedName))) | |
| 386 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolved.Name))) | |
| 380 | 387 | return nil | |
| 381 | 388 | } | |
| 382 | 389 | ||
| 383 | - | func resolveRemoteEntry(remoteName string) (string, remote.RemoteEntry, error) { | |
| 384 | - | config, err := remote.LoadRemotes("") | |
| 385 | - | if err != nil { | |
| 386 | - | return "", remote.RemoteEntry{}, fmt.Errorf("failed to load remotes: %w", err) | |
| 387 | - | } | |
| 388 | 390 | ||
| 389 | - | if remoteName == "" { | |
| 390 | - | remoteName = config.Active | |
| 391 | - | } | |
| 392 | - | if remoteName == "" { | |
| 393 | - | return "", remote.RemoteEntry{}, fmt.Errorf("no remote specified and no active remote set. Use --remote or 'gordon remote use <name>'") | |
| 394 | - | } | |
| 395 | - | ||
| 396 | - | remoteConfig, ok := config.Remotes[remoteName] | |
| 397 | - | if !ok { | |
| 398 | - | return "", remote.RemoteEntry{}, fmt.Errorf("remote '%s' not found", remoteName) | |
| 399 | - | } | |
| 400 | - | ||
| 401 | - | return remoteName, remoteConfig, nil | |
| 402 | - | } | |
| 403 | 391 | ||
| 404 | 392 | // runShowInternalAuth displays the internal registry credentials. | |
| 405 | 393 | func runShowInternalAuth() error { | |
| @@ -711,7 +699,6 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service | |||
| 711 | 699 | ||
| 712 | 700 | // newAuthStatusCmd creates the auth status command. | |
| 713 | 701 | func newAuthStatusCmd() *cobra.Command { | |
| 714 | - | var remoteNameFlag string | |
| 715 | 702 | cmd := &cobra.Command{ | |
| 716 | 703 | Use: "status", | |
| 717 | 704 | Short: "Check authentication session status", | |
| @@ -723,54 +710,34 @@ Examples: | |||
| 723 | 710 | gordon auth status Check status of active remote | |
| 724 | 711 | gordon auth status --remote prod Check specific remote`, | |
| 725 | 712 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 726 | - | return runAuthStatus(remoteNameFlag) | |
| 713 | + | return runAuthStatus() | |
| 727 | 714 | }, | |
| 728 | 715 | } | |
| 729 | - | cmd.Flags().StringVarP(&remoteNameFlag, "remote", "r", "", "Check specific remote") | |
| 730 | 716 | return cmd | |
| 731 | 717 | } | |
| 732 | 718 | ||
| 733 | 719 | // runAuthStatus checks authentication status for the active remote. | |
| 734 | - | func runAuthStatus(remoteNameArg string) error { | |
| 735 | - | // Load remotes config | |
| 736 | - | config, err := remote.LoadRemotes("") | |
| 737 | - | if err != nil { | |
| 738 | - | return fmt.Errorf("failed to load remotes: %w", err) | |
| 720 | + | func runAuthStatus() error { | |
| 721 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) | |
| 722 | + | if !isRemote { | |
| 723 | + | return fmt.Errorf("no active remote configured. Use 'gordon remotes use <name>' or --remote flag") | |
| 739 | 724 | } | |
| 740 | 725 | ||
| 741 | - | // Determine remote name | |
| 742 | - | remoteName := remoteNameArg | |
| 743 | - | if remoteName == "" { | |
| 744 | - | remoteName = config.Active | |
| 745 | - | } | |
| 746 | - | if remoteName == "" { | |
| 747 | - | return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag") | |
| 748 | - | } | |
| 726 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...) | |
| 749 | 727 | ||
| 750 | - | remoteConfig, ok := config.Remotes[remoteName] | |
| 751 | - | if !ok { | |
| 752 | - | if remoteNameArg != "" { | |
| 753 | - | return fmt.Errorf("remote '%s' not found", remoteName) | |
| 754 | - | } | |
| 755 | - | return fmt.Errorf("active remote '%s' not found", remoteName) | |
| 728 | + | name := resolved.Name | |
| 729 | + | if name == "" { | |
| 730 | + | name = resolved.URL | |
| 756 | 731 | } | |
| 732 | + | fmt.Printf("Checking authentication status for '%s'...\n", name) | |
| 757 | 733 | ||
| 758 | - | // Create client | |
| 759 | - | token := remote.ResolveTokenForRemote(remoteName, remoteConfig) | |
| 760 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, remoteName) | |
| 761 | - | client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...) | |
| 762 | - | ||
| 763 | - | // Verify auth | |
| 764 | 734 | ctx := context.Background() | |
| 765 | - | fmt.Printf("Checking authentication status for '%s'...\n", remoteName) | |
| 766 | - | ||
| 767 | 735 | status, err := client.VerifyAuth(ctx) | |
| 768 | 736 | if err != nil { | |
| 769 | - | return handleAuthVerifyError(err, remoteName, remoteConfig.URL) | |
| 737 | + | return handleAuthVerifyError(err, name, resolved.URL) | |
| 770 | 738 | } | |
| 771 | 739 | ||
| 772 | - | // Display status | |
| 773 | - | displayAuthStatus(remoteName, remoteConfig.URL, status) | |
| 740 | + | displayAuthStatus(name, resolved.URL, status) | |
| 774 | 741 | return nil | |
| 775 | 742 | } | |
| 776 | 743 | ||
| @@ -203,10 +203,7 @@ available while Gordon is running.`, | |||||
| 203 | 203 | ||||
| 204 | // newAuthLoginCmd creates the login command for remote authentication. | 204 | // newAuthLoginCmd creates the login command for remote authentication. | ||
| 205 | func newAuthLoginCmd() *cobra.Command { | 205 | func newAuthLoginCmd() *cobra.Command { | ||
| 206 | - | var ( | 206 | + | var token string |
| 207 | - | remoteName string | |||
| 208 | - | token string | |||
| 209 | - | ) | |||
| 210 | 207 | ||||
| 211 | cmd := &cobra.Command{ | 208 | cmd := &cobra.Command{ | ||
| 212 | Use: "login", | 209 | Use: "login", | ||
| @@ -224,31 +221,30 @@ Examples: | |||||
| 224 | gordon auth login Verify existing token`, | 221 | gordon auth login Verify existing token`, | ||
| 225 | RunE: func(cmd *cobra.Command, args []string) error { | 222 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 226 | if token != "" { | 223 | if token != "" { | ||
| 227 | - | return runAuthLoginWithToken(cmd.Context(), remoteName, token, cmd.OutOrStdout()) | 224 | + | return runAuthLoginWithToken(cmd.Context(), token, cmd.OutOrStdout()) |
| 228 | } | 225 | } | ||
| 229 | - | return runAuthLoginVerify(cmd.Context(), remoteName, cmd.OutOrStdout()) | 226 | + | return runAuthLoginVerify(cmd.Context(), cmd.OutOrStdout()) |
| 230 | }, | 227 | }, | ||
| 231 | } | 228 | } | ||
| 232 | 229 | ||||
| 233 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to authenticate with (defaults to active remote)") | |||
| 234 | cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote") | 230 | cmd.Flags().StringVarP(&token, "token", "t", "", "Authentication token to store for the remote") | ||
| 235 | 231 | ||||
| 236 | return cmd | 232 | return cmd | ||
| 237 | } | 233 | } | ||
| 238 | 234 | ||||
| 239 | - | func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io.Writer) error { | 235 | + | func runAuthLoginWithToken(ctx context.Context, token string, out io.Writer) error { |
| 240 | token = strings.TrimSpace(token) | 236 | token = strings.TrimSpace(token) | ||
| 241 | if token == "" { | 237 | if token == "" { | ||
| 242 | return fmt.Errorf("token cannot be empty") | 238 | return fmt.Errorf("token cannot be empty") | ||
| 243 | } | 239 | } | ||
| 244 | 240 | ||||
| 245 | - | resolvedName, remoteConfig, err := resolveRemoteEntry(remoteName) | 241 | + | // Resolve remote — but use the new token (not the stored one) for verification. |
| 246 | - | if err != nil { | 242 | + | resolved, isRemote := remote.Resolve(remoteFlag, "", insecureTLSFlag) |
| 247 | - | return err | 243 | + | if !isRemote { |
| 244 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") | |||
| 248 | } | 245 | } | ||
| 249 | 246 | ||||
| 250 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName) | 247 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(token, resolved.InsecureTLS)...) |
| 251 | - | client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...) | |||
| 252 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | 248 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | ||
| 253 | defer cancel() | 249 | defer cancel() | ||
| 254 | verified := true | 250 | verified := true | ||
| @@ -257,47 +253,59 @@ func runAuthLoginWithToken(ctx context.Context, remoteName, token string, out io | |||||
| 257 | verified = false | 253 | verified = false | ||
| 258 | } | 254 | } | ||
| 259 | 255 | ||||
| 260 | - | if err := remote.UpdateRemoteToken(resolvedName, token); err != nil { | 256 | + | // Store token — requires a named remote. |
| 257 | + | if resolved.Name == "" { | |||
| 258 | + | _ = cliWriteLine(out, styles.RenderWarning("Ad-hoc URL: token verified but cannot be stored. Use 'gordon remotes add' to save this remote.")) | |||
| 259 | + | return nil | |||
| 260 | + | } | |||
| 261 | + | if err := remote.UpdateRemoteToken(resolved.Name, token); err != nil { | |||
| 261 | return fmt.Errorf("failed to save token: %w", err) | 262 | return fmt.Errorf("failed to save token: %w", err) | ||
| 262 | } | 263 | } | ||
| 263 | 264 | ||||
| 264 | _ = cliWriteLine(out, "") | 265 | _ = cliWriteLine(out, "") | ||
| 265 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolvedName))) | 266 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Token stored for remote '%s'", resolved.Name))) |
| 266 | if verified { | 267 | if verified { | ||
| 267 | _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint")) | 268 | _ = cliWriteLine(out, styles.RenderSuccess("Token verified with remote status endpoint")) | ||
| 268 | } | 269 | } | ||
| 269 | return nil | 270 | return nil | ||
| 270 | } | 271 | } | ||
| 271 | 272 | ||||
| 272 | - | func runAuthLoginVerify(ctx context.Context, remoteName string, out io.Writer) error { | 273 | + | func runAuthLoginVerify(ctx context.Context, out io.Writer) error { |
| 273 | - | resolvedName, entry, err := resolveRemoteEntry(remoteName) | 274 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) |
| 274 | - | if err != nil { | 275 | + | if !isRemote { |
| 275 | - | return err | 276 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") |
| 276 | } | 277 | } | ||
| 277 | - | 278 | + | if resolved.Token == "" { | |
| 278 | - | token := remote.ResolveTokenForRemote(resolvedName, entry) | 279 | + | name := resolved.Name |
| 279 | - | if token == "" { | 280 | + | if name == "" { |
| 280 | - | return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", resolvedName) | 281 | + | name = resolved.URL |
| 282 | + | } | |||
| 283 | + | return fmt.Errorf("no token stored for remote '%s'; use: gordon auth login --token <token>", name) | |||
| 281 | } | 284 | } | ||
| 282 | 285 | ||||
| 283 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, resolvedName) | 286 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...) |
| 284 | - | client := remote.NewClient(entry.URL, remoteClientOptions(token, insecureTLS)...) | |||
| 285 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | 287 | verifyCtx, cancel := context.WithTimeout(ctx, 10*time.Second) | ||
| 286 | defer cancel() | 288 | defer cancel() | ||
| 287 | 289 | ||||
| 288 | if _, err := client.GetStatus(verifyCtx); err != nil { | 290 | if _, err := client.GetStatus(verifyCtx); err != nil { | ||
| 289 | - | _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", resolvedName, err))) | 291 | + | name := resolved.Name |
| 292 | + | if name == "" { | |||
| 293 | + | name = resolved.URL | |||
| 294 | + | } | |||
| 295 | + | _ = cliWriteLine(out, styles.RenderWarning(fmt.Sprintf("Token verification failed for '%s': %v", name, err))) | |||
| 290 | return fmt.Errorf("authentication failed") | 296 | return fmt.Errorf("authentication failed") | ||
| 291 | } | 297 | } | ||
| 292 | 298 | ||||
| 293 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", resolvedName))) | 299 | + | name := resolved.Name |
| 300 | + | if name == "" { | |||
| 301 | + | name = resolved.URL | |||
| 302 | + | } | |||
| 303 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Authenticated to '%s'", name))) | |||
| 294 | return nil | 304 | return nil | ||
| 295 | } | 305 | } | ||
| 296 | 306 | ||||
| 297 | // newAuthShowTokenCmd creates the show-token command. | 307 | // newAuthShowTokenCmd creates the show-token command. | ||
| 298 | func newAuthShowTokenCmd() *cobra.Command { | 308 | func newAuthShowTokenCmd() *cobra.Command { | ||
| 299 | - | var remoteName string | |||
| 300 | - | ||||
| 301 | cmd := &cobra.Command{ | 309 | cmd := &cobra.Command{ | ||
| 302 | Use: "show-token", | 310 | Use: "show-token", | ||
| 303 | Short: "Print the stored token for a remote", | 311 | Short: "Print the stored token for a remote", | ||
| @@ -313,38 +321,36 @@ Examples: | |||||
| 313 | gordon auth show-token --remote prod Show token for specific remote | 321 | gordon auth show-token --remote prod Show token for specific remote | ||
| 314 | gordon auth show-token | pbcopy Copy token to clipboard`, | 322 | gordon auth show-token | pbcopy Copy token to clipboard`, | ||
| 315 | RunE: func(cmd *cobra.Command, args []string) error { | 323 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 316 | - | return runAuthShowToken(remoteName, cmd.OutOrStdout()) | 324 | + | return runAuthShowToken(cmd.OutOrStdout()) |
| 317 | }, | 325 | }, | ||
| 318 | } | 326 | } | ||
| 319 | 327 | ||||
| 320 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to show token for (defaults to active remote)") | |||
| 321 | - | ||||
| 322 | return cmd | 328 | return cmd | ||
| 323 | } | 329 | } | ||
| 324 | 330 | ||||
| 325 | - | func runAuthShowToken(remoteName string, out io.Writer) error { | 331 | + | func runAuthShowToken(out io.Writer) error { |
| 326 | - | resolvedName, entry, err := resolveRemoteEntry(remoteName) | 332 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) |
| 327 | - | if err != nil { | 333 | + | if !isRemote { |
| 328 | - | return err | 334 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") |
| 329 | } | 335 | } | ||
| 330 | - | 336 | + | if resolved.Token == "" { | |
| 331 | - | token := remote.ResolveTokenForRemote(resolvedName, entry) | 337 | + | name := resolved.Name |
| 332 | - | if token == "" { | 338 | + | if name == "" { |
| 333 | - | return fmt.Errorf("no token found for remote '%s'", resolvedName) | 339 | + | name = resolved.URL |
| 340 | + | } | |||
| 341 | + | return fmt.Errorf("no token found for remote '%s'", name) | |||
| 334 | } | 342 | } | ||
| 335 | 343 | ||||
| 336 | if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) { | 344 | if f, ok := out.(*os.File); ok && isatty.IsTerminal(f.Fd()) { | ||
| 337 | _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback")) | 345 | _ = cliWriteLine(os.Stderr, styles.RenderWarning("Warning: token will be visible in terminal scrollback")) | ||
| 338 | } | 346 | } | ||
| 339 | 347 | ||||
| 340 | - | _ = cliWriteLine(out, token) | 348 | + | _ = cliWriteLine(out, resolved.Token) |
| 341 | return nil | 349 | return nil | ||
| 342 | } | 350 | } | ||
| 343 | 351 | ||||
| 344 | // newAuthLogoutCmd creates the logout command. | 352 | // newAuthLogoutCmd creates the logout command. | ||
| 345 | func newAuthLogoutCmd() *cobra.Command { | 353 | func newAuthLogoutCmd() *cobra.Command { | ||
| 346 | - | var remoteName string | |||
| 347 | - | ||||
| 348 | cmd := &cobra.Command{ | 354 | cmd := &cobra.Command{ | ||
| 349 | Use: "logout", | 355 | Use: "logout", | ||
| 350 | Short: "Remove stored authentication for a remote", | 356 | Short: "Remove stored authentication for a remote", | ||
| @@ -357,49 +363,31 @@ Examples: | |||||
| 357 | gordon auth logout Logout from active remote | 363 | gordon auth logout Logout from active remote | ||
| 358 | gordon auth logout --remote prod Logout from specific remote`, | 364 | gordon auth logout --remote prod Logout from specific remote`, | ||
| 359 | RunE: func(cmd *cobra.Command, args []string) error { | 365 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 360 | - | return runAuthLogout(remoteName, cmd.OutOrStdout()) | 366 | + | return runAuthLogout(cmd.OutOrStdout()) |
| 361 | }, | 367 | }, | ||
| 362 | } | 368 | } | ||
| 363 | 369 | ||||
| 364 | - | cmd.Flags().StringVarP(&remoteName, "remote", "r", "", "Remote to logout from (defaults to active remote)") | |||
| 365 | - | ||||
| 366 | return cmd | 370 | return cmd | ||
| 367 | } | 371 | } | ||
| 368 | 372 | ||||
| 369 | - | func runAuthLogout(remoteName string, out io.Writer) error { | 373 | + | func runAuthLogout(out io.Writer) error { |
| 370 | - | resolvedName, _, err := resolveRemoteEntry(remoteName) | 374 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) |
| 371 | - | if err != nil { | 375 | + | if !isRemote { |
| 372 | - | return err | 376 | + | return fmt.Errorf("no remote specified. Use --remote or 'gordon remotes use <name>'") |
| 377 | + | } | |||
| 378 | + | if resolved.Name == "" { | |||
| 379 | + | return fmt.Errorf("cannot logout from ad-hoc URL. Use 'gordon remotes add' to save this remote first") | |||
| 373 | } | 380 | } | ||
| 374 | 381 | ||||
| 375 | - | if err := remote.ClearRemoteToken(resolvedName); err != nil { | 382 | + | if err := remote.ClearRemoteToken(resolved.Name); err != nil { |
| 376 | return fmt.Errorf("failed to clear token: %w", err) | 383 | return fmt.Errorf("failed to clear token: %w", err) | ||
| 377 | } | 384 | } | ||
| 378 | 385 | ||||
| 379 | - | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolvedName))) | 386 | + | _ = cliWriteLine(out, styles.RenderSuccess(fmt.Sprintf("Logged out from remote '%s'", resolved.Name))) |
| 380 | return nil | 387 | return nil | ||
| 381 | } | 388 | } | ||
| 382 | 389 | ||||
| 383 | - | func resolveRemoteEntry(remoteName string) (string, remote.RemoteEntry, error) { | |||
| 384 | - | config, err := remote.LoadRemotes("") | |||
| 385 | - | if err != nil { | |||
| 386 | - | return "", remote.RemoteEntry{}, fmt.Errorf("failed to load remotes: %w", err) | |||
| 387 | - | } | |||
| 388 | 390 | ||||
| 389 | - | if remoteName == "" { | |||
| 390 | - | remoteName = config.Active | |||
| 391 | - | } | |||
| 392 | - | if remoteName == "" { | |||
| 393 | - | return "", remote.RemoteEntry{}, fmt.Errorf("no remote specified and no active remote set. Use --remote or 'gordon remote use <name>'") | |||
| 394 | - | } | |||
| 395 | - | ||||
| 396 | - | remoteConfig, ok := config.Remotes[remoteName] | |||
| 397 | - | if !ok { | |||
| 398 | - | return "", remote.RemoteEntry{}, fmt.Errorf("remote '%s' not found", remoteName) | |||
| 399 | - | } | |||
| 400 | - | ||||
| 401 | - | return remoteName, remoteConfig, nil | |||
| 402 | - | } | |||
| 403 | 391 | ||||
| 404 | // runShowInternalAuth displays the internal registry credentials. | 392 | // runShowInternalAuth displays the internal registry credentials. | ||
| 405 | func runShowInternalAuth() error { | 393 | func runShowInternalAuth() error { | ||
| @@ -711,7 +699,6 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service | |||||
| 711 | 699 | ||||
| 712 | // newAuthStatusCmd creates the auth status command. | 700 | // newAuthStatusCmd creates the auth status command. | ||
| 713 | func newAuthStatusCmd() *cobra.Command { | 701 | func newAuthStatusCmd() *cobra.Command { | ||
| 714 | - | var remoteNameFlag string | |||
| 715 | cmd := &cobra.Command{ | 702 | cmd := &cobra.Command{ | ||
| 716 | Use: "status", | 703 | Use: "status", | ||
| 717 | Short: "Check authentication session status", | 704 | Short: "Check authentication session status", | ||
| @@ -723,54 +710,34 @@ Examples: | |||||
| 723 | gordon auth status Check status of active remote | 710 | gordon auth status Check status of active remote | ||
| 724 | gordon auth status --remote prod Check specific remote`, | 711 | gordon auth status --remote prod Check specific remote`, | ||
| 725 | RunE: func(cmd *cobra.Command, args []string) error { | 712 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 726 | - | return runAuthStatus(remoteNameFlag) | 713 | + | return runAuthStatus() |
| 727 | }, | 714 | }, | ||
| 728 | } | 715 | } | ||
| 729 | - | cmd.Flags().StringVarP(&remoteNameFlag, "remote", "r", "", "Check specific remote") | |||
| 730 | return cmd | 716 | return cmd | ||
| 731 | } | 717 | } | ||
| 732 | 718 | ||||
| 733 | // runAuthStatus checks authentication status for the active remote. | 719 | // runAuthStatus checks authentication status for the active remote. | ||
| 734 | - | func runAuthStatus(remoteNameArg string) error { | 720 | + | func runAuthStatus() error { |
| 735 | - | // Load remotes config | 721 | + | resolved, isRemote := remote.Resolve(remoteFlag, tokenFlag, insecureTLSFlag) |
| 736 | - | config, err := remote.LoadRemotes("") | 722 | + | if !isRemote { |
| 737 | - | if err != nil { | 723 | + | return fmt.Errorf("no active remote configured. Use 'gordon remotes use <name>' or --remote flag") |
| 738 | - | return fmt.Errorf("failed to load remotes: %w", err) | |||
| 739 | } | 724 | } | ||
| 740 | 725 | ||||
| 741 | - | // Determine remote name | 726 | + | client := remote.NewClient(resolved.URL, remoteClientOptions(resolved.Token, resolved.InsecureTLS)...) |
| 742 | - | remoteName := remoteNameArg | |||
| 743 | - | if remoteName == "" { | |||
| 744 | - | remoteName = config.Active | |||
| 745 | - | } | |||
| 746 | - | if remoteName == "" { | |||
| 747 | - | return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag") | |||
| 748 | - | } | |||
| 749 | 727 | ||||
| 750 | - | remoteConfig, ok := config.Remotes[remoteName] | 728 | + | name := resolved.Name |
| 751 | - | if !ok { | 729 | + | if name == "" { |
| 752 | - | if remoteNameArg != "" { | 730 | + | name = resolved.URL |
| 753 | - | return fmt.Errorf("remote '%s' not found", remoteName) | |||
| 754 | - | } | |||
| 755 | - | return fmt.Errorf("active remote '%s' not found", remoteName) | |||
| 756 | } | 731 | } | ||
| 732 | + | fmt.Printf("Checking authentication status for '%s'...\n", name) | |||
| 757 | 733 | ||||
| 758 | - | // Create client | |||
| 759 | - | token := remote.ResolveTokenForRemote(remoteName, remoteConfig) | |||
| 760 | - | insecureTLS := remote.ResolveInsecureTLSForRemote(insecureTLSFlag, remoteName) | |||
| 761 | - | client := remote.NewClient(remoteConfig.URL, remoteClientOptions(token, insecureTLS)...) | |||
| 762 | - | ||||
| 763 | - | // Verify auth | |||
| 764 | ctx := context.Background() | 734 | ctx := context.Background() | ||
| 765 | - | fmt.Printf("Checking authentication status for '%s'...\n", remoteName) | |||
| 766 | - | ||||
| 767 | status, err := client.VerifyAuth(ctx) | 735 | status, err := client.VerifyAuth(ctx) | ||
| 768 | if err != nil { | 736 | if err != nil { | ||
| 769 | - | return handleAuthVerifyError(err, remoteName, remoteConfig.URL) | 737 | + | return handleAuthVerifyError(err, name, resolved.URL) |
| 770 | } | 738 | } | ||
| 771 | 739 | ||||
| 772 | - | // Display status | 740 | + | displayAuthStatus(name, resolved.URL, status) |
| 773 | - | displayAuthStatus(remoteName, remoteConfig.URL, status) | |||
| 774 | return nil | 741 | return nil | ||
| 775 | } | 742 | } | ||
| 776 | 743 | ||||
M internal/adapters/in/cli/auth_test.go +8 -2
| @@ -16,8 +16,9 @@ import ( | |||
| 16 | 16 | func TestRunAuthLoginWithToken_StoresToken(t *testing.T) { | |
| 17 | 17 | t.Setenv("XDG_CONFIG_HOME", t.TempDir()) | |
| 18 | 18 | t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled")) | |
| 19 | + | t.Setenv("GORDON_REMOTE", "") | |
| 20 | + | t.Setenv("GORDON_TOKEN", "") | |
| 19 | 21 | ||
| 20 | - | // Mock server — verification may fail (token exchange not mocked), but token should still be stored. | |
| 21 | 22 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 22 | 23 | w.WriteHeader(http.StatusUnauthorized) | |
| 23 | 24 | _, _ = w.Write([]byte(`{"error":"unauthorized"}`)) | |
| @@ -32,8 +33,13 @@ func TestRunAuthLoginWithToken_StoresToken(t *testing.T) { | |||
| 32 | 33 | } | |
| 33 | 34 | assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config)) | |
| 34 | 35 | ||
| 36 | + | // Set global flag to target "prod" remote | |
| 37 | + | origRemote := remoteFlag | |
| 38 | + | remoteFlag = "prod" | |
| 39 | + | t.Cleanup(func() { remoteFlag = origRemote }) | |
| 40 | + | ||
| 35 | 41 | var out bytes.Buffer | |
| 36 | - | err := runAuthLoginWithToken(context.Background(), "", "token123", &out) | |
| 42 | + | err := runAuthLoginWithToken(context.Background(), "token123", &out) | |
| 37 | 43 | assert.NoError(t, err) | |
| 38 | 44 | ||
| 39 | 45 | loaded, err := remote.LoadRemotes("") | |
| @@ -16,8 +16,9 @@ import ( | |||||
| 16 | func TestRunAuthLoginWithToken_StoresToken(t *testing.T) { | 16 | func TestRunAuthLoginWithToken_StoresToken(t *testing.T) { | ||
| 17 | t.Setenv("XDG_CONFIG_HOME", t.TempDir()) | 17 | t.Setenv("XDG_CONFIG_HOME", t.TempDir()) | ||
| 18 | t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled")) | 18 | t.Setenv("PATH", filepath.Join("/nonexistent", "pass-disabled")) | ||
| 19 | + | t.Setenv("GORDON_REMOTE", "") | |||
| 20 | + | t.Setenv("GORDON_TOKEN", "") | |||
| 19 | 21 | ||||
| 20 | - | // Mock server — verification may fail (token exchange not mocked), but token should still be stored. | |||
| 21 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 22 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 22 | w.WriteHeader(http.StatusUnauthorized) | 23 | w.WriteHeader(http.StatusUnauthorized) | ||
| 23 | _, _ = w.Write([]byte(`{"error":"unauthorized"}`)) | 24 | _, _ = w.Write([]byte(`{"error":"unauthorized"}`)) | ||
| @@ -32,8 +33,13 @@ func TestRunAuthLoginWithToken_StoresToken(t *testing.T) { | |||||
| 32 | } | 33 | } | ||
| 33 | assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config)) | 34 | assert.NoError(t, remote.SaveRemotes(remote.DefaultRemotesPath(), config)) | ||
| 34 | 35 | ||||
| 36 | + | // Set global flag to target "prod" remote | |||
| 37 | + | origRemote := remoteFlag | |||
| 38 | + | remoteFlag = "prod" | |||
| 39 | + | t.Cleanup(func() { remoteFlag = origRemote }) | |||
| 40 | + | ||||
| 35 | var out bytes.Buffer | 41 | var out bytes.Buffer | ||
| 36 | - | err := runAuthLoginWithToken(context.Background(), "", "token123", &out) | 42 | + | err := runAuthLoginWithToken(context.Background(), "token123", &out) |
| 37 | assert.NoError(t, err) | 43 | assert.NoError(t, err) | ||
| 38 | 44 | ||||
| 39 | loaded, err := remote.LoadRemotes("") | 45 | loaded, err := remote.LoadRemotes("") | ||