diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-19 14:48:31 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-19 14:48:31 +0300 |
| commit | 0d737e7585a7b6876679b266e677675f7787ff24 (patch) | |
| tree | 0d1c1210d1072afb7b691bf68df54f8d90a7b889 | |
| parent | 622827ccb4c23915aeccaa39d3e8152dd2955afb (diff) | |
Return error from tokenManager.Generate instead of panicking
A crypto/rand.Read failure is a process-level emergency, but the previous
implementation aborted the entire server. Propagate the error through
the TokenManager interface so callers (CreateAPIToken) can surface it
via the normal HTTP 500 path instead of crashing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| -rw-r--r-- | player-server/internal/auth/interfaces.go | 4 | ||||
| -rw-r--r-- | player-server/internal/auth/token.go | 9 | ||||
| -rw-r--r-- | player-server/internal/auth/token_test.go | 5 | ||||
| -rw-r--r-- | player-server/internal/service/auth.go | 7 | ||||
| -rw-r--r-- | player-server/internal/service/auth_test.go | 4 |
5 files changed, 21 insertions, 8 deletions
diff --git a/player-server/internal/auth/interfaces.go b/player-server/internal/auth/interfaces.go index 790c534..e167a60 100644 --- a/player-server/internal/auth/interfaces.go +++ b/player-server/internal/auth/interfaces.go @@ -15,7 +15,9 @@ type SessionManager interface { } // TokenManager is the abstraction for API token generation and hashing. +// Generate returns an error when the system random source fails so callers +// can surface the failure (e.g. via HTTP 500) instead of panicking. type TokenManager interface { - Generate() (plaintext, hash string) + Generate() (plaintext, hash string, err error) Hash(plaintext string) string } diff --git a/player-server/internal/auth/token.go b/player-server/internal/auth/token.go index bdb7e6b..bcb696f 100644 --- a/player-server/internal/auth/token.go +++ b/player-server/internal/auth/token.go @@ -20,14 +20,17 @@ func NewTokenManager() TokenManager { } // Generate creates a plaintext API token and its stored hash. -func (m *tokenManager) Generate() (plaintext, hash string) { +// Returns an error if the system random source fails. A crypto/rand.Read +// failure indicates a process-level emergency, so callers should propagate +// the error upward rather than masking it. +func (m *tokenManager) Generate() (plaintext, hash string, err error) { b := make([]byte, tokenByteLength) if _, err := rand.Read(b); err != nil { - panic(fmt.Sprintf("generate token: %v", err)) + return "", "", fmt.Errorf("generate token: %w", err) } plaintext = hex.EncodeToString(b) - return plaintext, m.Hash(plaintext) + return plaintext, m.Hash(plaintext), nil } // Hash returns the SHA-256 hash of a plaintext API token. diff --git a/player-server/internal/auth/token_test.go b/player-server/internal/auth/token_test.go index bf18aa5..a9bddf8 100644 --- a/player-server/internal/auth/token_test.go +++ b/player-server/internal/auth/token_test.go @@ -11,7 +11,10 @@ func TestTokenManager_Generate(t *testing.T) { seen := make(map[string]string) for i := 0; i < 1000; i++ { - plaintext, hash := tm.Generate() + plaintext, hash, err := tm.Generate() + if err != nil { + t.Fatalf("generate token: %v", err) + } if len(plaintext) != tokenByteLength*2 { t.Fatalf("expected plaintext length %d, got %d", tokenByteLength*2, len(plaintext)) } diff --git a/player-server/internal/service/auth.go b/player-server/internal/service/auth.go index 6bd2055..1fe468b 100644 --- a/player-server/internal/service/auth.go +++ b/player-server/internal/service/auth.go @@ -92,8 +92,13 @@ func (s *authService) Login(ctx context.Context, username, password string) (*Au } // CreateAPIToken creates a hashed API token and returns the one-time plaintext value. +// A token generation failure (e.g. crypto/rand.Read returning an error) is +// surfaced to the caller; the HTTP layer translates it into a 500 response. func (s *authService) CreateAPIToken(ctx context.Context, userID int64, name string, expiresAt *time.Time) (*CreateAPITokenResult, error) { - plaintext, hash := s.tm.Generate() + plaintext, hash, err := s.tm.Generate() + if err != nil { + return nil, fmt.Errorf("generate api token: %w", err) + } now := s.clock.Now() token := &model.APIToken{ UserID: userID, diff --git a/player-server/internal/service/auth_test.go b/player-server/internal/service/auth_test.go index 213f6e5..b01a2b3 100644 --- a/player-server/internal/service/auth_test.go +++ b/player-server/internal/service/auth_test.go @@ -16,8 +16,8 @@ type fixedTokenManager struct { hash string } -func (m fixedTokenManager) Generate() (string, string) { - return m.plaintext, m.hash +func (m fixedTokenManager) Generate() (string, string, error) { + return m.plaintext, m.hash, nil } func (m fixedTokenManager) Hash(plaintext string) string { |
