// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package credstore import ( "encoding/base64" "os" "path/filepath" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zalando/go-keyring" ) // newLargeTestToken builds a token whose access token is several KB, // mimicking real JWTs with large groups claims that exceed the Windows // Credential Manager 2560-byte blob limit. func newLargeTestToken(clientID string) Token { return Token{ AccessToken: "header." + strings.Repeat("groups-claim-payload-", 300) + ".sig", RefreshToken: "test-refresh-token", TokenType: "Bearer", ExpiresAt: time.Now().Add(1 * time.Hour).Truncate(time.Second), ClientID: clientID, } } func newTestEncryptedStore(t *testing.T) (*EncryptedFileStore[Token], string) { t.Helper() keyring.MockInit() path := filepath.Join(t.TempDir(), "tokens.enc") return NewEncryptedFileStore[Token]("test-service", path, JSONCodec[Token]{}), path } func TestEncryptedFileStoreSaveAndLoad(t *testing.T) { store, _ := newTestEncryptedStore(t) tok := newLargeTestToken("test-client") require.NoError(t, store.Save(tok.ClientID, tok)) loaded, err := store.Load("test-client") require.NoError(t, err) assert.Equal(t, tok.AccessToken, loaded.AccessToken) assert.Equal(t, tok.RefreshToken, loaded.RefreshToken) assert.True(t, tok.ExpiresAt.Equal(loaded.ExpiresAt)) } func TestEncryptedFileStoreFileContainsNoPlaintext(t *testing.T) { store, path := newTestEncryptedStore(t) tok := newLargeTestToken("test-client") require.NoError(t, store.Save(tok.ClientID, tok)) raw, err := os.ReadFile(path) require.NoError(t, err) assert.NotContains(t, string(raw), "groups-claim-payload") assert.NotContains(t, string(raw), tok.RefreshToken) } func TestEncryptedFileStoreKeyringHoldsOnlySmallMasterKey(t *testing.T) { store, _ := newTestEncryptedStore(t) tok := newLargeTestToken("test-client") require.NoError(t, store.Save(tok.ClientID, tok)) // The token itself must not be in the keyring. _, err := keyring.Get("test-service", "test-client") assert.ErrorIs(t, err, keyring.ErrNotFound) // Only the 44-byte base64 master key may live in the keyring — // well under the Windows Credential Manager 2560-byte blob limit. encoded, err := keyring.Get("test-service", masterKeyUser) require.NoError(t, err) assert.Len(t, encoded, 44) key, err := base64.StdEncoding.DecodeString(encoded) require.NoError(t, err) assert.Len(t, key, 32) } func TestEncryptedFileStoreLoadNotFound(t *testing.T) { store, _ := newTestEncryptedStore(t) _, err := store.Load("nonexistent") assert.ErrorIs(t, err, ErrNotFound) } func TestEncryptedFileStoreDelete(t *testing.T) { store, _ := newTestEncryptedStore(t) tok := newLargeTestToken("test-client") require.NoError(t, store.Save(tok.ClientID, tok)) require.NoError(t, store.Delete("test-client")) _, err := store.Load("test-client") assert.ErrorIs(t, err, ErrNotFound) } func TestEncryptedFileStoreSaveEmptyClientID(t *testing.T) { store, _ := newTestEncryptedStore(t) err := store.Save("", newLargeTestToken("x")) assert.ErrorIs(t, err, ErrEmptyClientID) } func TestEncryptedFileStoreProbe(t *testing.T) { store, _ := newTestEncryptedStore(t) assert.True(t, store.Probe()) } func TestEncryptedFileStoreCorruptMasterKeyFailsLoad(t *testing.T) { store, _ := newTestEncryptedStore(t) tok := newLargeTestToken("test-client") require.NoError(t, store.Save(tok.ClientID, tok)) // A fresh store whose keyring holds a corrupted key must fail to decrypt // rather than return garbage or mint a new key. require.NoError(t, keyring.Set("test-service", masterKeyUser, "not-base64!")) fresh := NewEncryptedFileStore[Token]("test-service", store.file.filePath, JSONCodec[Token]{}) assert.False(t, fresh.Probe()) _, err := fresh.Load("test-client") require.Error(t, err) assert.NotErrorIs(t, err, ErrNotFound) } func TestEncryptedFileStoreNilCodecPanics(t *testing.T) { assert.Panics(t, func() { NewEncryptedFileStore[string]("svc", "path", nil) }) } func TestEncryptedFileStoreString(t *testing.T) { store, path := newTestEncryptedStore(t) assert.Equal(t, "encrypted-file: "+path, store.String()) }