diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8be857b..28a8ae1 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-go@v6 with: # NOTE: Keep this in sync with the version from go.mod - go-version: "1.23.x" + go-version: "1.25.x" - name: Run Benchmark run: set -o pipefail; go test ./... -benchmem -run=^$ -bench . | tee output.txt diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 50a2c61..dff8b36 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/setup-go@v6 with: # NOTE: Keep this in sync with the version from go.mod - go-version: "1.23.x" + go-version: "1.25.x" cache: false - name: golangci-lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b1eb4fa..7d6147d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: Build: strategy: matrix: - go-version: [1.23.x, 1.24.x] + go-version: [1.24.x, 1.25.x] platform: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: @@ -31,7 +31,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Install gotestsum - run: go install gotest.tools/gotestsum@v1.12.0 + run: go install gotest.tools/gotestsum@v1.13.0 - name: Test run: gotestsum -f testname -- ./... -race -count=1 -shuffle=on @@ -40,10 +40,10 @@ jobs: run: gotestsum -f testname -- ./... -race -count=1 -coverprofile=coverage.txt -covermode=atomic -shuffle=on - name: Upload coverage reports to Codecov - if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.24.x' }} - uses: codecov/codecov-action@v5.5.2 + if: ${{ matrix.platform == 'ubuntu-latest' && matrix.go-version == '1.25.x' }} + uses: codecov/codecov-action@v5.5.1 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.txt + files: ./coverage.txt flags: unittests slug: gofiber/utils diff --git a/Makefile b/Makefile index c25913c..5f0b50f 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ benchmark: ## coverage: ☂️ Generate coverage report .PHONY: coverage coverage: - go run gotest.tools/gotestsum@v1.12.0 -f testname -- ./... -race -count=1 -coverprofile=coverage.out -covermode=atomic + go run gotest.tools/gotestsum@v1.13.0 -f testname -- ./... -race -count=1 -coverprofile=coverage.out -covermode=atomic go tool cover -html=coverage.out -o coverage.html open coverage.html & @@ -46,7 +46,7 @@ modernize: ## test: 🚦 Execute all tests .PHONY: test test: - go run gotest.tools/gotestsum@v1.12.0 -f testname -- ./... -race -count=1 -shuffle=on + go run gotest.tools/gotestsum@v1.13.0 -f testname -- ./... -race -count=1 -shuffle=on ## tidy: 📌 Clean and tidy dependencies .PHONY: tidy diff --git a/common.go b/common.go index 47d4540..40ae15a 100644 --- a/common.go +++ b/common.go @@ -99,39 +99,30 @@ func UUIDv4() string { // GenerateSecureToken generates a cryptographically secure random token encoded in base64. // It uses crypto/rand for randomness and base64.RawURLEncoding for URL-safe output. // If length is less than or equal to 0, it defaults to 32 bytes (256 bits of entropy). -// Returns an error if the random source fails. -func GenerateSecureToken(length int) (string, error) { +// Panics if the random source fails. +func GenerateSecureToken(length int) string { if length <= 0 { length = 32 } bytes := make([]byte, length) if _, err := randRead(bytes); err != nil { - return "", fmt.Errorf("utils: failed to read random bytes for token: %w", err) + // On Go 1.24+, crypto/rand.Read panics internally and never returns an error. + // On Go 1.23 and earlier, we panic for the same reasons: RNG failures indicate + // a broken system state (uninitialized entropy pool, misconfigured VM, etc.) + // that is almost certainly permanent rather than transient. + // See: https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/crypto/rand/rand.go + // https://go.dev/issue/66821 + panic(fmt.Errorf("utils: failed to read random bytes for token: %w", err)) } - return base64.RawURLEncoding.EncodeToString(bytes), nil -} - -// GenerateSecureTokenMust is a convenience wrapper that panics on failure. -func GenerateSecureTokenMust(length int) string { - s, err := GenerateSecureToken(length) - if err != nil { - panic(err) - } - return s + return base64.RawURLEncoding.EncodeToString(bytes) } // SecureToken generates a secure token with 32 bytes of entropy. -// Returns an error if the random source fails. -func SecureToken() (string, error) { +// Panics if the random source fails. See GenerateSecureToken for details. +func SecureToken() string { return GenerateSecureToken(32) } -// SecureTokenMust generates a secure token with 32 bytes of entropy. -// It panics if the random source fails. -func SecureTokenMust() string { - return GenerateSecureTokenMust(32) -} - // FunctionName returns function name func FunctionName(fn any) string { if fn == nil { diff --git a/common_test.go b/common_test.go index 2adcf69..58039ea 100644 --- a/common_test.go +++ b/common_test.go @@ -103,32 +103,26 @@ func Test_UUIDv4_Concurrency(t *testing.T) { func Test_GenerateSecureToken(t *testing.T) { t.Parallel() // Test with 32 bytes - token, err := GenerateSecureToken(32) - require.NoError(t, err) + token := GenerateSecureToken(32) require.Len(t, token, 43) // base64 encoding of 32 bytes require.NotEmpty(t, token) // Test custom length - token8, err := GenerateSecureToken(8) - require.NoError(t, err) + token8 := GenerateSecureToken(8) require.Len(t, token8, 11) // base64 of 8 bytes ~11 chars - token16, err := GenerateSecureToken(16) - require.NoError(t, err) + token16 := GenerateSecureToken(16) require.Len(t, token16, 22) // base64 of 16 bytes ~22 chars // Test uniqueness - token2, err := GenerateSecureToken(32) - require.NoError(t, err) + token2 := GenerateSecureToken(32) require.NotEqual(t, token, token2) // Test invalid length defaults to 32 - tokenZero, err := GenerateSecureToken(0) - require.NoError(t, err) + tokenZero := GenerateSecureToken(0) require.Len(t, tokenZero, 43) - tokenNegative, err := GenerateSecureToken(-1) - require.NoError(t, err) + tokenNegative := GenerateSecureToken(-1) require.Len(t, tokenNegative, 43) } @@ -139,7 +133,7 @@ func Test_GenerateSecureToken_Concurrency(t *testing.T) { results := make(map[string]string) for i := 0; i < iterations; i++ { go func() { - ch <- GenerateSecureTokenMust(32) + ch <- GenerateSecureToken(32) }() } for i := 0; i < iterations; i++ { @@ -159,25 +153,18 @@ func Test_GenerateSecureToken_ErrorOnRandFail(t *testing.T) { return 0, errors.New("simulated failure") } - s, err := GenerateSecureToken(16) - require.Error(t, err) - require.Empty(t, s) - require.Contains(t, err.Error(), "simulated failure") - - // Must variant should panic on failure - require.Panics(t, func() { GenerateSecureTokenMust(16) }) + // Should panic on failure + require.Panics(t, func() { GenerateSecureToken(16) }) } func Test_SecureToken(t *testing.T) { t.Parallel() - token, err := SecureToken() - require.NoError(t, err) + token := SecureToken() require.Len(t, token, 43) require.NotEmpty(t, token) // Test uniqueness - token2, err := SecureToken() - require.NoError(t, err) + token2 := SecureToken() require.NotEqual(t, token, token2) } @@ -381,13 +368,13 @@ func Benchmark_GenerateSecureToken(b *testing.B) { var res string b.Run("16_bytes", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = GenerateSecureTokenMust(16) + res = GenerateSecureToken(16) } require.Len(b, res, 22) }) b.Run("32_bytes", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = GenerateSecureTokenMust(32) + res = GenerateSecureToken(32) } require.Len(b, res, 43) }) @@ -404,7 +391,7 @@ func Benchmark_TokenGenerators(b *testing.B) { }) b.Run("SecureToken", func(b *testing.B) { for n := 0; n < b.N; n++ { - res = SecureTokenMust() + res = SecureToken() } require.Len(b, res, 43) }) diff --git a/go.mod b/go.mod index efd6981..edfb9c1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gofiber/utils/v2 -go 1.23.0 +go 1.24.0 require ( github.com/fxamacker/cbor/v2 v2.9.0