mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-08-24 21:17:31 +00:00
89 lines
3.8 KiB
Go
89 lines
3.8 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
|
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
|
|
)
|
|
|
|
// versionBeforeMultipleClientSecrets is the migration version right before client secrets moved into the credentials document
|
|
const versionBeforeMultipleClientSecrets = 20260802120000
|
|
|
|
// TestMigrateClientSecretsToCredentials checks that the secret of every existing client is preserved as an entry of the credentials document
|
|
func TestMigrateClientSecretsToCredentials(t *testing.T) {
|
|
const legacySecret = "legacy-client-secret"
|
|
|
|
legacyHash, err := bcrypt.GenerateFromPassword([]byte(legacySecret), bcrypt.MinCost)
|
|
require.NoError(t, err)
|
|
|
|
createdAt := time.Now().Add(-72 * time.Hour).Truncate(time.Second)
|
|
|
|
db := testutils.NewDatabaseForTestWithMigrationSeed(t, versionBeforeMultipleClientSecrets, func(t *testing.T, db *gorm.DB) {
|
|
// A client with a secret and no other credentials
|
|
err := db.Exec(
|
|
`INSERT INTO oidc_clients (id, created_at, name, secret, callback_urls, is_public, pkce_enabled) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
"client-with-secret", createdAt.Unix(), "With Secret", string(legacyHash), `["https://example.com/callback"]`, false, false,
|
|
).Error
|
|
require.NoError(t, err)
|
|
|
|
// A client whose credentials document already holds a federated identity, which the migration must preserve
|
|
err = db.Exec(
|
|
`INSERT INTO oidc_clients (id, created_at, name, secret, callback_urls, is_public, pkce_enabled, credentials) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
"client-with-federated-identity", createdAt.Unix(), "With Federated Identity", string(legacyHash), `["https://example.com/callback"]`, false, false,
|
|
`{"federatedIdentities":[{"issuer":"https://issuer.example.com"}]}`,
|
|
).Error
|
|
require.NoError(t, err)
|
|
|
|
// A public client, which never had a secret
|
|
err = db.Exec(
|
|
`INSERT INTO oidc_clients (id, created_at, name, secret, callback_urls, is_public, pkce_enabled) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
"public-client", createdAt.Unix(), "Public", "", `["https://example.com/callback"]`, true, true,
|
|
).Error
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
// The legacy column is gone once the migration has run
|
|
ok := db.Migrator().HasColumn(&model.OidcClient{}, "secret")
|
|
assert.False(t, ok)
|
|
|
|
var clients []model.OidcClient
|
|
err = db.Find(&clients).Error
|
|
require.NoError(t, err)
|
|
byID := make(map[string]model.OidcClient, len(clients))
|
|
for _, client := range clients {
|
|
byID[client.ID] = client
|
|
}
|
|
require.Len(t, byID, 3)
|
|
|
|
// The migrated secret keeps its bcrypt hash, has no expiration, and carries no prefix because the value was never stored
|
|
migrated := byID["client-with-secret"].Credentials.Secrets
|
|
require.Len(t, migrated, 1)
|
|
assert.NotEmpty(t, migrated[0].ID)
|
|
assert.Equal(t, model.OidcClientSecretHashBcrypt, migrated[0].Algorithm)
|
|
assert.Equal(t, string(legacyHash), migrated[0].Hash)
|
|
assert.Empty(t, migrated[0].Prefix)
|
|
assert.Nil(t, migrated[0].ExpiresAt)
|
|
assert.True(t, migrated[0].IsActive())
|
|
assert.Equal(t, createdAt.UTC(), migrated[0].CreatedAt.UTC())
|
|
|
|
// Existing federated identities survive the migration alongside the new secret
|
|
withFederated := byID["client-with-federated-identity"].Credentials
|
|
require.Len(t, withFederated.Secrets, 1)
|
|
assert.Equal(t, string(legacyHash), withFederated.Secrets[0].Hash)
|
|
require.Len(t, withFederated.FederatedIdentities, 1)
|
|
assert.Equal(t, "https://issuer.example.com", withFederated.FederatedIdentities[0].Issuer)
|
|
|
|
// Clients that never had a secret do not get an empty one
|
|
assert.Empty(t, byID["public-client"].Credentials.Secrets)
|
|
|
|
// Every migrated secret gets its own identifier
|
|
assert.NotEqual(t, migrated[0].ID, withFederated.Secrets[0].ID)
|
|
}
|