detached b91fac7

fix(auth): properly handle HTTP scheme and load secrets from pass

- Detect request scheme (HTTP/HTTPS) dynamically for token realm URL
  instead of hardcoding https://
- CLI now loads token_secret from pass backend instead of using path string
commit
b91fac7dae8c43cd08da0f8a3e84c7db1d103591
author
brice · · 2026-01-14 05:38 UTC
parent
ce7d45e
signature
unsigned
2 files · +26 -5
M internal/adapters/in/cli/auth.go +18 -4
@@ -14,6 +14,7 @@ import (
1414 "github.com/spf13/viper"
1515 "golang.org/x/term"
1616
17+ "gordon/internal/adapters/out/secrets"
1718 "gordon/internal/adapters/out/tokenstore"
1819 "gordon/internal/app"
1920 "gordon/internal/domain"
@@ -345,12 +346,25 @@ func loadAuthConfig(configPath string) (*cliConfig, error) {
345346 cfg.Backend = domain.SecretsBackendUnsafe
346347 }
347348
348- // Load token secret if specified
349+ // Load token secret from secrets backend
349350 tokenSecretPath := v.GetString("registry_auth.token_secret")
350351 if tokenSecretPath != "" {
351- // For CLI, we generate a default secret if not available
352- // In production, the secret should be loaded from the secrets backend
353- cfg.TokenSecret = []byte(tokenSecretPath) // Use path as seed for now
352+ // Load actual secret from the configured backend
353+ ctx := context.Background()
354+ log := zerowrap.New(zerowrap.Config{Level: "warn"})
355+
356+ switch cfg.Backend {
357+ case domain.SecretsBackendPass:
358+ provider := secrets.NewPassProvider(log)
359+ secret, err := provider.GetSecret(ctx, tokenSecretPath)
360+ if err != nil {
361+ return nil, fmt.Errorf("failed to load token secret from pass: %w", err)
362+ }
363+ cfg.TokenSecret = []byte(secret)
364+ default:
365+ // For unsafe backend, use the path as the secret (backwards compatible)
366+ cfg.TokenSecret = []byte(tokenSecretPath)
367+ }
354368 } else {
355369 // Generate a default secret for CLI token generation
356370 cfg.TokenSecret = []byte("gordon-cli-default-secret")
@@ -14,6 +14,7 @@ import (
14 "github.com/spf13/viper"14 "github.com/spf13/viper"
15 "golang.org/x/term"15 "golang.org/x/term"
16 16
17+ "gordon/internal/adapters/out/secrets"
17 "gordon/internal/adapters/out/tokenstore"18 "gordon/internal/adapters/out/tokenstore"
18 "gordon/internal/app"19 "gordon/internal/app"
19 "gordon/internal/domain"20 "gordon/internal/domain"
@@ -345,12 +346,25 @@ func loadAuthConfig(configPath string) (*cliConfig, error) {
345 cfg.Backend = domain.SecretsBackendUnsafe346 cfg.Backend = domain.SecretsBackendUnsafe
346 }347 }
347 348
348- // Load token secret if specified349+ // Load token secret from secrets backend
349 tokenSecretPath := v.GetString("registry_auth.token_secret")350 tokenSecretPath := v.GetString("registry_auth.token_secret")
350 if tokenSecretPath != "" {351 if tokenSecretPath != "" {
351- // For CLI, we generate a default secret if not available352+ // Load actual secret from the configured backend
352- // In production, the secret should be loaded from the secrets backend353+ ctx := context.Background()
353- cfg.TokenSecret = []byte(tokenSecretPath) // Use path as seed for now354+ log := zerowrap.New(zerowrap.Config{Level: "warn"})
355+
356+ switch cfg.Backend {
357+ case domain.SecretsBackendPass:
358+ provider := secrets.NewPassProvider(log)
359+ secret, err := provider.GetSecret(ctx, tokenSecretPath)
360+ if err != nil {
361+ return nil, fmt.Errorf("failed to load token secret from pass: %w", err)
362+ }
363+ cfg.TokenSecret = []byte(secret)
364+ default:
365+ // For unsafe backend, use the path as the secret (backwards compatible)
366+ cfg.TokenSecret = []byte(tokenSecretPath)
367+ }
354 } else {368 } else {
355 // Generate a default secret for CLI token generation369 // Generate a default secret for CLI token generation
356 cfg.TokenSecret = []byte("gordon-cli-default-secret")370 cfg.TokenSecret = []byte("gordon-cli-default-secret")
M internal/adapters/in/http/middleware/auth.go +8 -1
@@ -278,7 +278,14 @@ func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host stri
278278 switch authType {
279279 case domain.AuthTypeToken:
280280 // For token auth, indicate the token server endpoint
281- realm := "https://" + host + "/v2/token"
281+ // Detect scheme from request (TLS or X-Forwarded-Proto header)
282+ scheme := "http"
283+ if r.TLS != nil {
284+ scheme = "https"
285+ } else if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
286+ scheme = proto
287+ }
288+ realm := scheme + "://" + host + "/v2/token"
282289 w.Header().Set("WWW-Authenticate", `Bearer realm="`+realm+`",service="gordon-registry"`)
283290 default:
284291 w.Header().Set("WWW-Authenticate", `Basic realm="Gordon Registry"`)
@@ -278,7 +278,14 @@ func sendUnauthorized(w http.ResponseWriter, authType domain.AuthType, host stri
278 switch authType {278 switch authType {
279 case domain.AuthTypeToken:279 case domain.AuthTypeToken:
280 // For token auth, indicate the token server endpoint280 // For token auth, indicate the token server endpoint
281- realm := "https://" + host + "/v2/token"281+ // Detect scheme from request (TLS or X-Forwarded-Proto header)
282+ scheme := "http"
283+ if r.TLS != nil {
284+ scheme = "https"
285+ } else if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" {
286+ scheme = proto
287+ }
288+ realm := scheme + "://" + host + "/v2/token"
282 w.Header().Set("WWW-Authenticate", `Bearer realm="`+realm+`",service="gordon-registry"`)289 w.Header().Set("WWW-Authenticate", `Bearer realm="`+realm+`",service="gordon-registry"`)
283 default:290 default:
284 w.Header().Set("WWW-Authenticate", `Basic realm="Gordon Registry"`)291 w.Header().Set("WWW-Authenticate", `Basic realm="Gordon Registry"`)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close