From bf544d2924fc717f01b9e6c217ffa2ccfb306a4e Mon Sep 17 00:00:00 2001 From: zepatrik Date: Thu, 3 Jul 2025 18:56:17 +0200 Subject: [PATCH] fix(pagination): use hard-coded fallback key instead of panic As the encryption keys are not relevant for security, it is better to fall back to crackable encryption than fail totally. --- pagination/keysetpagination_v2/page_token.go | 9 ++++++--- pagination/keysetpagination_v2/page_token_test.go | 9 ++++++--- pagination/keysetpagination_v2/request_params.go | 7 +++---- pagination/keysetpagination_v2/request_params_test.go | 9 +++++++++ 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pagination/keysetpagination_v2/page_token.go b/pagination/keysetpagination_v2/page_token.go index cf842435..efbe06b1 100644 --- a/pagination/keysetpagination_v2/page_token.go +++ b/pagination/keysetpagination_v2/page_token.go @@ -13,6 +13,8 @@ import ( "github.com/ory/herodot" ) +var fallbackEncryptionKey = &[32]byte{} + type ( PageToken struct { testNow func() time.Time @@ -34,10 +36,11 @@ func (t PageToken) Columns() []Column { return t.cols } // Encrypt encrypts the page token using the first key in the provided keyset. // It panics if no keys are provided. func (t PageToken) Encrypt(keys [][32]byte) string { - if len(keys) == 0 { - panic("keyset pagination: cannot encrypt page token with no keys") + key := fallbackEncryptionKey + if len(keys) > 0 { + key = &keys[0] } - return hyrumtoken.Marshal(&keys[0], t) + return hyrumtoken.Marshal(key, t) } func (t PageToken) MarshalJSON() ([]byte, error) { diff --git a/pagination/keysetpagination_v2/page_token_test.go b/pagination/keysetpagination_v2/page_token_test.go index 7aa2c2e8..8c76b927 100644 --- a/pagination/keysetpagination_v2/page_token_test.go +++ b/pagination/keysetpagination_v2/page_token_test.go @@ -57,8 +57,11 @@ func TestPageToken_Encrypt(t *testing.T) { assert.ErrorContains(t, err, "decrypt token") }) - t.Run("panics with no keys", func(t *testing.T) { - assert.PanicsWithValue(t, "keyset pagination: cannot encrypt page token with no keys", func() { token.Encrypt(nil) }) - assert.PanicsWithValue(t, "keyset pagination: cannot encrypt page token with no keys", func() { token.Encrypt([][32]byte{}) }) + t.Run("uses fallback key", func(t *testing.T) { + for _, encrypted := range []string{token.Encrypt(nil), token.Encrypt([][32]byte{})} { + decrypted, err := ParsePageToken([][32]byte{*fallbackEncryptionKey}, encrypted) + require.NoError(t, err) + assert.Equal(t, token, decrypted) + } }) } diff --git a/pagination/keysetpagination_v2/request_params.go b/pagination/keysetpagination_v2/request_params.go index f10b97ff..98b20ddb 100644 --- a/pagination/keysetpagination_v2/request_params.go +++ b/pagination/keysetpagination_v2/request_params.go @@ -112,14 +112,13 @@ func ParseQueryParams(keys [][32]byte, q url.Values) ([]Option, error) { // ParsePageToken parses a page token from the given raw string using the provided keys. // It panics if no keys are provided. func ParsePageToken(keys [][32]byte, raw string) (t PageToken, err error) { - if len(keys) == 0 { - panic("keysetpagination: cannot parse page token with no keys") - } for i := range keys { err = errors.WithStack(hyrumtoken.Unmarshal(&keys[i], raw, &t)) if err == nil { return } } - return + // as a last resort, try the fallback key + err = hyrumtoken.Unmarshal(fallbackEncryptionKey, raw, &t) + return t, errors.WithStack(err) } diff --git a/pagination/keysetpagination_v2/request_params_test.go b/pagination/keysetpagination_v2/request_params_test.go index 4cbca7c6..65bd7fbb 100644 --- a/pagination/keysetpagination_v2/request_params_test.go +++ b/pagination/keysetpagination_v2/request_params_test.go @@ -93,6 +93,15 @@ func TestParsePageToken(t *testing.T) { require.ErrorContains(t, err, "decrypt token") assert.Zero(t, token) }) + + t.Run("uses fallback key", func(t *testing.T) { + fallbackEncryptedToken := expectedToken.Encrypt(nil) + for _, noKeys := range [][][32]byte{nil, {}} { + token, err := ParsePageToken(noKeys, fallbackEncryptedToken) + require.NoError(t, err) + assert.Equal(t, expectedToken, token) + } + }) } func TestParse(t *testing.T) {