Files
tinyauth/internal/service/oidc_consent_test.go
T

181 lines
5.7 KiB
Go

package service
import (
"context"
"strings"
"testing"
"github.com/steveiliop56/ding"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/repository/memory"
"github.com/tinyauthapp/tinyauth/internal/test"
"github.com/tinyauthapp/tinyauth/internal/utils/logger"
)
func newTestOIDCService(t *testing.T, cfg *model.Config, runtime *model.RuntimeConfig, store repository.Store) *OIDCService {
t.Helper()
log := logger.NewLogger().WithTestConfig()
log.Init()
ctx := context.Background()
dg := ding.New(ctx)
svc, err := NewOIDCService(OIDCServiceInput{
Log: log,
Config: cfg,
Runtime: runtime,
Queries: store,
Ding: dg,
})
require.NoError(t, err)
require.NotNil(t, svc)
return svc
}
func TestOIDCConsentPerClient(t *testing.T) {
cfg, runtime := test.CreateTestConfigs(t)
store := memory.New()
svc := newTestOIDCService(t, &cfg, &runtime, store)
// Consent for client A
_, err := svc.UpsertOIDCConsent(context.Background(), "testuser", "openid profile", "some-client-id")
require.NoError(t, err)
// Consent for client B
_, err = svc.UpsertOIDCConsent(context.Background(), "testuser", "openid email", "other-client-id")
require.NoError(t, err)
// Two separate rows, one per client
consents, err := store.ListOIDCConsents(context.Background())
require.NoError(t, err)
assert.Len(t, consents, 2)
// Each lookup returns the consent for that specific client
gotA, err := svc.GetOIDCConsent(context.Background(), "testuser", "some-client-id")
require.NoError(t, err)
require.NotNil(t, gotA)
assert.Equal(t, "openid profile", gotA.Scope)
gotB, err := svc.GetOIDCConsent(context.Background(), "testuser", "other-client-id")
require.NoError(t, err)
require.NotNil(t, gotB)
assert.Equal(t, "openid email", gotB.Scope)
}
func TestOIDCConsentUnionKeepsOneRow(t *testing.T) {
cfg, runtime := test.CreateTestConfigs(t)
store := memory.New()
svc := newTestOIDCService(t, &cfg, &runtime, store)
_, err := svc.UpsertOIDCConsent(context.Background(), "testuser", "openid profile", "some-client-id")
require.NoError(t, err)
// Second authorize with an additional scope
_, err = svc.UpsertOIDCConsent(context.Background(), "testuser", "openid email", "some-client-id")
require.NoError(t, err)
// Still exactly one row
consents, err := store.ListOIDCConsents(context.Background())
require.NoError(t, err)
assert.Len(t, consents, 1)
// Stored scope is the union of both requests
got, err := svc.GetOIDCConsent(context.Background(), "testuser", "some-client-id")
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, []string{"email", "openid", "profile"}, splitScopes(got.Scope))
}
func TestOIDCConsentSubsetDoesNotShrink(t *testing.T) {
cfg, runtime := test.CreateTestConfigs(t)
store := memory.New()
svc := newTestOIDCService(t, &cfg, &runtime, store)
_, err := svc.UpsertOIDCConsent(context.Background(), "testuser", "openid profile email", "some-client-id")
require.NoError(t, err)
// Authorize with a subset of already-granted scopes
_, err = svc.UpsertOIDCConsent(context.Background(), "testuser", "openid profile", "some-client-id")
require.NoError(t, err)
// Stored scope is unchanged
got, err := svc.GetOIDCConsent(context.Background(), "testuser", "some-client-id")
require.NoError(t, err)
require.NotNil(t, got)
assert.Equal(t, []string{"email", "openid", "profile"}, splitScopes(got.Scope))
}
func TestOIDCConsentNotFound(t *testing.T) {
cfg, runtime := test.CreateTestConfigs(t)
store := memory.New()
svc := newTestOIDCService(t, &cfg, &runtime, store)
got, err := svc.GetOIDCConsent(context.Background(), "testuser", "some-client-id")
require.NoError(t, err)
assert.Nil(t, got)
}
func TestOIDCConsentReconcileRemovesDeletedClients(t *testing.T) {
cfg, runtime := test.CreateTestConfigs(t)
cfg.OIDC.Clients["client-a"] = model.OIDCClientConfig{
ClientID: "client-a",
ClientSecret: "secret-a",
TrustedRedirectURIs: []string{"https://a.example.com/callback"},
}
cfg.OIDC.Clients["client-b"] = model.OIDCClientConfig{
ClientID: "client-b",
ClientSecret: "secret-b",
TrustedRedirectURIs: []string{"https://b.example.com/callback"},
}
// First startup: both clients configured
store := memory.New()
svc := newTestOIDCService(t, &cfg, &runtime, store)
_, err := svc.UpsertOIDCConsent(context.Background(), "testuser", "openid profile", "client-a")
require.NoError(t, err)
_, err = svc.UpsertOIDCConsent(context.Background(), "testuser", "openid email", "client-b")
require.NoError(t, err)
// Stale consent for a client that was never configured
_, err = svc.UpsertOIDCConsent(context.Background(), "testuser", "openid", "stale-client")
require.NoError(t, err)
// Second startup: client-b removed from configuration
delete(cfg.OIDC.Clients, "client-b")
svc2 := newTestOIDCService(t, &cfg, &runtime, store)
// Consents for the removed and unknown clients are gone
_, err = store.GetOIDCConsentByUsernameAndClientID(context.Background(), repository.GetOIDCConsentByUsernameAndClientIDParams{
Username: "testuser", ClientID: "client-a",
})
require.NoError(t, err)
_, err = store.GetOIDCConsentByUsernameAndClientID(context.Background(), repository.GetOIDCConsentByUsernameAndClientIDParams{
Username: "testuser", ClientID: "client-b",
})
assert.ErrorIs(t, err, repository.ErrNotFound)
_, err = store.GetOIDCConsentByUsernameAndClientID(context.Background(), repository.GetOIDCConsentByUsernameAndClientIDParams{
Username: "testuser", ClientID: "stale-client",
})
assert.ErrorIs(t, err, repository.ErrNotFound)
require.NotNil(t, svc2)
}
func splitScopes(scope string) []string {
return strings.Fields(scope)
}