Files
tinyauth/internal/service/oidc_service_test.go
T
Scott McKendry 5d95123dcb feat(oidc): support for all in-spec attributes and scopes (#777)
* feat(oidc): support for all in-spec attributes and scopes

* add tests

* assert phone/email verified when either is set

* update tests

* add claims back to userinfo

* remove redundant column drop in migration

* fix duplicate migration id

* fix clobbered imports post-rebase
2026-04-27 19:25:52 +03:00

199 lines
6.5 KiB
Go

package service_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/service"
)
func newTestUser() repository.OidcUserinfo {
addr := config.AddressClaim{
Formatted: "123 Main St",
StreetAddress: "123 Main St",
Locality: "Springfield",
Region: "IL",
PostalCode: "62701",
Country: "US",
}
addrJSON, _ := json.Marshal(addr)
return repository.OidcUserinfo{
Sub: "test-sub",
Name: "Test User",
PreferredUsername: "testuser",
Email: "test@example.com",
Groups: "admins,users",
UpdatedAt: 1234567890,
GivenName: "Test",
FamilyName: "User",
MiddleName: "M",
Nickname: "testy",
Profile: "https://example.com/testuser",
Picture: "https://example.com/testuser.jpg",
Website: "https://testuser.example.com",
Gender: "male",
Birthdate: "1990-01-01",
Zoneinfo: "America/Chicago",
Locale: "en-US",
PhoneNumber: "+15555550100",
Address: string(addrJSON),
}
}
func TestCompileUserinfo(t *testing.T) {
dir := t.TempDir()
svc := service.NewOIDCService(service.OIDCServiceConfig{
PrivateKeyPath: dir + "/key.pem",
PublicKeyPath: dir + "/key.pub",
Issuer: "https://tinyauth.example.com",
SessionExpiry: 3600,
}, nil)
require.NoError(t, svc.Init())
type testCase struct {
description string
mutate func(u *repository.OidcUserinfo)
scope string
run func(t *testing.T, info service.UserinfoResponse)
}
tests := []testCase{
{
description: "openid scope only returns sub and updated_at",
scope: "openid",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, "test-sub", info.Sub)
assert.Equal(t, int64(1234567890), info.UpdatedAt)
assert.Empty(t, info.Name)
assert.Empty(t, info.Email)
assert.Nil(t, info.Groups)
assert.Nil(t, info.PhoneNumberVerified)
assert.Nil(t, info.Address)
},
},
{
description: "profile scope returns all profile fields",
scope: "openid,profile",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, "Test User", info.Name)
assert.Equal(t, "testuser", info.PreferredUsername)
assert.Equal(t, "Test", info.GivenName)
assert.Equal(t, "User", info.FamilyName)
assert.Equal(t, "M", info.MiddleName)
assert.Equal(t, "testy", info.Nickname)
assert.Equal(t, "https://example.com/testuser", info.Profile)
assert.Equal(t, "https://example.com/testuser.jpg", info.Picture)
assert.Equal(t, "https://testuser.example.com", info.Website)
assert.Equal(t, "male", info.Gender)
assert.Equal(t, "1990-01-01", info.Birthdate)
assert.Equal(t, "America/Chicago", info.Zoneinfo)
assert.Equal(t, "en-US", info.Locale)
assert.Empty(t, info.Email)
},
},
{
description: "email scope sets email and email_verified true when email present",
scope: "openid,email",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, "test@example.com", info.Email)
assert.True(t, info.EmailVerified)
assert.Empty(t, info.Name)
},
},
{
description: "email scope sets email_verified false when email absent",
scope: "openid,email",
mutate: func(u *repository.OidcUserinfo) { u.Email = "" },
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Empty(t, info.Email)
assert.False(t, info.EmailVerified)
},
},
{
description: "phone scope sets phone_number_verified true when phone present",
scope: "openid,phone",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, "+15555550100", info.PhoneNumber)
require.NotNil(t, info.PhoneNumberVerified)
assert.True(t, *info.PhoneNumberVerified)
},
},
{
description: "phone scope sets phone_number_verified false when phone absent",
scope: "openid,phone",
mutate: func(u *repository.OidcUserinfo) { u.PhoneNumber = "" },
run: func(t *testing.T, info service.UserinfoResponse) {
require.NotNil(t, info.PhoneNumberVerified)
assert.False(t, *info.PhoneNumberVerified)
},
},
{
description: "address scope returns parsed address",
scope: "openid,address",
run: func(t *testing.T, info service.UserinfoResponse) {
require.NotNil(t, info.Address)
assert.Equal(t, "123 Main St", info.Address.Formatted)
assert.Equal(t, "123 Main St", info.Address.StreetAddress)
assert.Equal(t, "Springfield", info.Address.Locality)
assert.Equal(t, "IL", info.Address.Region)
assert.Equal(t, "62701", info.Address.PostalCode)
assert.Equal(t, "US", info.Address.Country)
},
},
{
description: "address scope with invalid JSON omits address",
scope: "openid,address",
mutate: func(u *repository.OidcUserinfo) { u.Address = "not-valid-json" },
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Nil(t, info.Address)
},
},
{
description: "groups scope returns split groups",
scope: "openid,groups",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, []string{"admins", "users"}, info.Groups)
},
},
{
description: "groups scope returns empty slice when no groups",
scope: "openid,groups",
mutate: func(u *repository.OidcUserinfo) { u.Groups = "" },
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, []string{}, info.Groups)
},
},
{
description: "all scopes return all fields",
scope: "openid,profile,email,phone,address,groups",
run: func(t *testing.T, info service.UserinfoResponse) {
assert.Equal(t, "Test User", info.Name)
assert.Equal(t, "test@example.com", info.Email)
assert.Equal(t, "+15555550100", info.PhoneNumber)
require.NotNil(t, info.PhoneNumberVerified)
assert.True(t, *info.PhoneNumberVerified)
require.NotNil(t, info.Address)
assert.Equal(t, "Springfield", info.Address.Locality)
assert.Equal(t, []string{"admins", "users"}, info.Groups)
},
},
}
for _, test := range tests {
t.Run(test.description, func(t *testing.T) {
user := newTestUser()
if test.mutate != nil {
test.mutate(&user)
}
info := svc.CompileUserinfo(user, test.scope)
test.run(t, info)
})
}
}