Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/go/openshell/v1/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func resolveAuthProvider(cfg *Config) (types.AuthProvider, error) {
// Token loading is deferred to GetRequestMetadata so that
// NewClient succeeds even when the token file is missing.
// The error surfaces on first authentication attempt (FR-007).
return &lazyEdgeAuth{loader: &edgeTokenLoader{dir: cfg.Dir}}, nil
return &lazyEdgeAuth{dir: cfg.Dir}, nil

case AuthModeOIDC:
src := newDiskTokenSource(cfg.Dir)
Expand Down
37 changes: 5 additions & 32 deletions sdk/go/openshell/v1/gateway/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"

"golang.org/x/oauth2"
Expand All @@ -28,32 +27,6 @@ const (
oidcTokenFile = "oidc_token.json"
)

// edgeTokenLoader provides lazy, thread-safe loading of the edge token
// from disk. Successful loads are cached; failures are retried so a token
// created after client construction can be picked up without a restart.
type edgeTokenLoader struct {
dir string
mu sync.Mutex
token string
}

// load returns the edge token string, reading it from disk on the first
// call. It tries edge_token first, falling back to cf_token for legacy
// compatibility. Only a successful result is cached.
func (l *edgeTokenLoader) load() (string, error) {
l.mu.Lock()
defer l.mu.Unlock()
if l.token != "" {
return l.token, nil
}
token, err := readEdgeToken(l.dir)
if err != nil {
return "", err
}
l.token = token
return token, nil
}

// readEdgeToken reads the edge token from the given directory. It tries
// edge_token first, then cf_token as a legacy fallback. The file content
// is trimmed of surrounding whitespace.
Expand Down Expand Up @@ -158,14 +131,14 @@ func (d *diskTokenSource) Token() (*oauth2.Token, error) {
// NewClient succeeds even when the token file is missing on disk (FR-007).
// The error surfaces on the first authentication attempt.
type lazyEdgeAuth struct {
loader *edgeTokenLoader
dir string
}

// GetRequestMetadata loads the edge token lazily and returns it as a
// Bearer authorization header. The first load is cached by the
// underlying edgeTokenLoader.
// GetRequestMetadata reads the edge token from disk on every call and
// returns it as a Bearer authorization header. Reading each time picks up
// tokens rewritten by the CLI without a process restart.
func (a *lazyEdgeAuth) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
token, err := a.loader.load()
token, err := readEdgeToken(a.dir)
if err != nil {
return nil, err
}
Expand Down
35 changes: 16 additions & 19 deletions sdk/go/openshell/v1/gateway/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package gateway

import (
"context"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -83,41 +84,37 @@ func TestReadEdgeToken_EmptyFallbackFile(t *testing.T) {
assert.Contains(t, err.Error(), "empty")
}

func TestEdgeTokenLoader_Lazy(t *testing.T) {
func TestLazyEdgeAuth_ReloadsRewrittenToken(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, edgeTokenFile, "lazy-token")
writeFile(t, dir, edgeTokenFile, "old-token")

loader := &edgeTokenLoader{dir: dir}
auth := &lazyEdgeAuth{dir: dir}

// First call reads from disk.
token1, err := loader.load()
md, err := auth.GetRequestMetadata(context.Background())
require.NoError(t, err)
assert.Equal(t, "lazy-token", token1)
assert.Equal(t, "Bearer old-token", md["authorization"])

// Remove the file. Second call should return cached value.
require.NoError(t, os.Remove(filepath.Join(dir, edgeTokenFile)))
writeFile(t, dir, edgeTokenFile, "new-token")

token2, err := loader.load()
md, err = auth.GetRequestMetadata(context.Background())
require.NoError(t, err)
assert.Equal(t, "lazy-token", token2, "should return cached token")
assert.Equal(t, "Bearer new-token", md["authorization"])
}

func TestEdgeTokenLoader_LazyError(t *testing.T) {
func TestLazyEdgeAuth_PicksUpLateToken(t *testing.T) {
dir := t.TempDir()
// No token files exist.

loader := &edgeTokenLoader{dir: dir}
auth := &lazyEdgeAuth{dir: dir}

// First call fails.
_, err1 := loader.load()
require.Error(t, err1)
_, err := auth.GetRequestMetadata(context.Background())
require.ErrorIs(t, err, ErrTokenLoad)

// Write the file now. A transient load failure must not be cached.
writeFile(t, dir, edgeTokenFile, "late-token")

token, err2 := loader.load()
require.NoError(t, err2)
assert.Equal(t, "late-token", token)
md, err := auth.GetRequestMetadata(context.Background())
require.NoError(t, err)
assert.Equal(t, "Bearer late-token", md["authorization"])
}

// --- T015: diskTokenSource tests ---
Expand Down
Loading