Skip to content

Commit 9328ab3

Browse files
committed
add tests
1 parent 0330e17 commit 9328ab3

2 files changed

Lines changed: 341 additions & 0 deletions

File tree

internal/controller/user_controller_test.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,150 @@ func TestUserController(t *testing.T) {
353353
require.NoError(t, err)
354354
})
355355
}
356+
357+
func TestUserControllerAttributes(t *testing.T) {
358+
tlog.NewTestLogger().Init()
359+
tempDir := t.TempDir()
360+
361+
authServiceCfg := service.AuthServiceConfig{
362+
Users: []config.User{
363+
{
364+
Username: "attruser",
365+
Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password
366+
Attributes: config.UserAttributes{
367+
Name: "Alice Smith",
368+
Email: "alice@example.com",
369+
},
370+
},
371+
{
372+
Username: "attrtotpuser",
373+
Password: "$2a$10$ZwVYQH07JX2zq7Fjkt3gU.BjwvvwPeli4OqOno04RQIv0P7usBrXa", // password
374+
TotpSecret: "JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK",
375+
Attributes: config.UserAttributes{
376+
Name: "Bob Jones",
377+
Email: "bob@example.com",
378+
},
379+
},
380+
},
381+
SessionExpiry: 10,
382+
CookieDomain: "example.com",
383+
LoginTimeout: 10,
384+
LoginMaxRetries: 3,
385+
SessionCookieName: "tinyauth-session",
386+
}
387+
388+
userControllerCfg := controller.UserControllerConfig{
389+
CookieDomain: "example.com",
390+
}
391+
392+
app := bootstrap.NewBootstrapApp(config.Config{})
393+
db, err := app.SetupDatabase(path.Join(tempDir, "tinyauth_attrs.db"))
394+
require.NoError(t, err)
395+
396+
queries := repository.New(db)
397+
398+
docker := service.NewDockerService()
399+
err = docker.Init()
400+
require.NoError(t, err)
401+
402+
ldap := service.NewLdapService(service.LdapServiceConfig{})
403+
err = ldap.Init()
404+
require.NoError(t, err)
405+
406+
broker := service.NewOAuthBrokerService(make(map[string]config.OAuthServiceConfig))
407+
err = broker.Init()
408+
require.NoError(t, err)
409+
410+
authService := service.NewAuthService(authServiceCfg, docker, ldap, queries, broker)
411+
err = authService.Init()
412+
require.NoError(t, err)
413+
414+
makeRouter := func(extraMiddlewares ...gin.HandlerFunc) *gin.Engine {
415+
router := gin.Default()
416+
for _, m := range extraMiddlewares {
417+
router.Use(m)
418+
}
419+
gin.SetMode(gin.TestMode)
420+
group := router.Group("/api")
421+
ctrl := controller.NewUserController(userControllerCfg, group, authService)
422+
ctrl.SetupRoutes()
423+
return router
424+
}
425+
426+
t.Run("Login uses name and email from user attributes", func(t *testing.T) {
427+
authService.ClearRateLimitsTestingOnly()
428+
router := makeRouter()
429+
430+
loginReq := controller.LoginRequest{Username: "attruser", Password: "password"}
431+
body, err := json.Marshal(loginReq)
432+
require.NoError(t, err)
433+
434+
req := httptest.NewRequest("POST", "/api/user/login", strings.NewReader(string(body)))
435+
req.Header.Set("Content-Type", "application/json")
436+
rec := httptest.NewRecorder()
437+
router.ServeHTTP(rec, req)
438+
439+
require.Equal(t, 200, rec.Code)
440+
cookies := rec.Result().Cookies()
441+
require.Len(t, cookies, 1)
442+
assert.Equal(t, "tinyauth-session", cookies[0].Name)
443+
})
444+
445+
t.Run("Login with TOTP uses name and email from user attributes in pending session", func(t *testing.T) {
446+
authService.ClearRateLimitsTestingOnly()
447+
router := makeRouter()
448+
449+
loginReq := controller.LoginRequest{Username: "attrtotpuser", Password: "password"}
450+
body, err := json.Marshal(loginReq)
451+
require.NoError(t, err)
452+
453+
req := httptest.NewRequest("POST", "/api/user/login", strings.NewReader(string(body)))
454+
req.Header.Set("Content-Type", "application/json")
455+
rec := httptest.NewRecorder()
456+
router.ServeHTTP(rec, req)
457+
458+
require.Equal(t, 200, rec.Code)
459+
var res map[string]any
460+
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &res))
461+
assert.Equal(t, true, res["totpPending"])
462+
require.Len(t, rec.Result().Cookies(), 1)
463+
})
464+
465+
t.Run("TOTP completion uses name and email from user attributes", func(t *testing.T) {
466+
authService.ClearRateLimitsTestingOnly()
467+
468+
// First: login to get TOTP-pending session
469+
router := makeRouter(func(c *gin.Context) {
470+
c.Set("context", &config.UserContext{
471+
Username: "attrtotpuser",
472+
Name: "Bob Jones",
473+
Email: "bob@example.com",
474+
Provider: "local",
475+
TotpPending: true,
476+
TotpEnabled: true,
477+
})
478+
})
479+
480+
code, err := totp.GenerateCode("JPIEBDKJH6UGWJMX66RR3S55UFP2SGKK", time.Now())
481+
require.NoError(t, err)
482+
483+
totpReq := controller.TotpRequest{Code: code}
484+
body, err := json.Marshal(totpReq)
485+
require.NoError(t, err)
486+
487+
req := httptest.NewRequest("POST", "/api/user/totp", strings.NewReader(string(body)))
488+
req.Header.Set("Content-Type", "application/json")
489+
rec := httptest.NewRecorder()
490+
router.ServeHTTP(rec, req)
491+
492+
require.Equal(t, 200, rec.Code)
493+
cookies := rec.Result().Cookies()
494+
require.Len(t, cookies, 1)
495+
assert.Equal(t, "tinyauth-session", cookies[0].Name)
496+
})
497+
498+
t.Cleanup(func() {
499+
err = db.Close()
500+
require.NoError(t, err)
501+
})
502+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package service_test
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/steveiliop56/tinyauth/internal/config"
11+
"github.com/steveiliop56/tinyauth/internal/repository"
12+
"github.com/steveiliop56/tinyauth/internal/service"
13+
)
14+
15+
func newTestUser() repository.OidcUserinfo {
16+
addr := config.AddressClaim{
17+
Formatted: "123 Main St",
18+
StreetAddress: "123 Main St",
19+
Locality: "Springfield",
20+
Region: "IL",
21+
PostalCode: "62701",
22+
Country: "US",
23+
}
24+
addrJSON, _ := json.Marshal(addr)
25+
26+
return repository.OidcUserinfo{
27+
Sub: "test-sub",
28+
Name: "Test User",
29+
PreferredUsername: "testuser",
30+
Email: "test@example.com",
31+
Groups: "admins,users",
32+
UpdatedAt: 1234567890,
33+
GivenName: "Test",
34+
FamilyName: "User",
35+
MiddleName: "M",
36+
Nickname: "testy",
37+
Profile: "https://example.com/testuser",
38+
Picture: "https://example.com/testuser.jpg",
39+
Website: "https://testuser.example.com",
40+
Gender: "male",
41+
Birthdate: "1990-01-01",
42+
Zoneinfo: "America/Chicago",
43+
Locale: "en-US",
44+
PhoneNumber: "+15555550100",
45+
PhoneNumberVerified: 1,
46+
Address: string(addrJSON),
47+
}
48+
}
49+
50+
func newOIDCService(t *testing.T) *service.OIDCService {
51+
t.Helper()
52+
dir := t.TempDir()
53+
svc := service.NewOIDCService(service.OIDCServiceConfig{
54+
PrivateKeyPath: dir + "/key.pem",
55+
PublicKeyPath: dir + "/key.pub",
56+
Issuer: "https://tinyauth.example.com",
57+
SessionExpiry: 3600,
58+
}, nil)
59+
require.NoError(t, svc.Init())
60+
return svc
61+
}
62+
63+
func TestCompileUserinfo_OpenidOnly(t *testing.T) {
64+
svc := newOIDCService(t)
65+
user := newTestUser()
66+
67+
info := svc.CompileUserinfo(user, "openid")
68+
69+
assert.Equal(t, "test-sub", info.Sub)
70+
assert.Equal(t, int64(1234567890), info.UpdatedAt)
71+
// profile fields not requested
72+
assert.Empty(t, info.Name)
73+
assert.Empty(t, info.Email)
74+
assert.Nil(t, info.Groups)
75+
assert.Nil(t, info.PhoneNumberVerified)
76+
assert.Nil(t, info.Address)
77+
}
78+
79+
func TestCompileUserinfo_ProfileScope(t *testing.T) {
80+
svc := newOIDCService(t)
81+
user := newTestUser()
82+
83+
info := svc.CompileUserinfo(user, "openid,profile")
84+
85+
assert.Equal(t, "Test User", info.Name)
86+
assert.Equal(t, "testuser", info.PreferredUsername)
87+
assert.Equal(t, "Test", info.GivenName)
88+
assert.Equal(t, "User", info.FamilyName)
89+
assert.Equal(t, "M", info.MiddleName)
90+
assert.Equal(t, "testy", info.Nickname)
91+
assert.Equal(t, "https://example.com/testuser", info.Profile)
92+
assert.Equal(t, "https://example.com/testuser.jpg", info.Picture)
93+
assert.Equal(t, "https://testuser.example.com", info.Website)
94+
assert.Equal(t, "male", info.Gender)
95+
assert.Equal(t, "1990-01-01", info.Birthdate)
96+
assert.Equal(t, "America/Chicago", info.Zoneinfo)
97+
assert.Equal(t, "en-US", info.Locale)
98+
// non-profile fields still absent
99+
assert.Empty(t, info.Email)
100+
}
101+
102+
func TestCompileUserinfo_EmailScope(t *testing.T) {
103+
svc := newOIDCService(t)
104+
user := newTestUser()
105+
106+
info := svc.CompileUserinfo(user, "openid,email")
107+
108+
assert.Equal(t, "test@example.com", info.Email)
109+
assert.True(t, info.EmailVerified)
110+
assert.Empty(t, info.Name) // profile not requested
111+
}
112+
113+
func TestCompileUserinfo_PhoneScope(t *testing.T) {
114+
svc := newOIDCService(t)
115+
user := newTestUser()
116+
117+
info := svc.CompileUserinfo(user, "openid,phone")
118+
119+
assert.Equal(t, "+15555550100", info.PhoneNumber)
120+
require.NotNil(t, info.PhoneNumberVerified)
121+
assert.True(t, *info.PhoneNumberVerified)
122+
}
123+
124+
func TestCompileUserinfo_PhoneScope_Unverified(t *testing.T) {
125+
svc := newOIDCService(t)
126+
user := newTestUser()
127+
user.PhoneNumberVerified = 0
128+
129+
info := svc.CompileUserinfo(user, "openid,phone")
130+
131+
require.NotNil(t, info.PhoneNumberVerified)
132+
assert.False(t, *info.PhoneNumberVerified)
133+
}
134+
135+
func TestCompileUserinfo_AddressScope(t *testing.T) {
136+
svc := newOIDCService(t)
137+
user := newTestUser()
138+
139+
info := svc.CompileUserinfo(user, "openid,address")
140+
141+
require.NotNil(t, info.Address)
142+
assert.Equal(t, "123 Main St", info.Address.Formatted)
143+
assert.Equal(t, "123 Main St", info.Address.StreetAddress)
144+
assert.Equal(t, "Springfield", info.Address.Locality)
145+
assert.Equal(t, "IL", info.Address.Region)
146+
assert.Equal(t, "62701", info.Address.PostalCode)
147+
assert.Equal(t, "US", info.Address.Country)
148+
}
149+
150+
func TestCompileUserinfo_AddressScope_InvalidJSON(t *testing.T) {
151+
svc := newOIDCService(t)
152+
user := newTestUser()
153+
user.Address = "not-valid-json"
154+
155+
info := svc.CompileUserinfo(user, "openid,address")
156+
157+
// invalid JSON silently skipped, address omitted
158+
assert.Nil(t, info.Address)
159+
}
160+
161+
func TestCompileUserinfo_GroupsScope(t *testing.T) {
162+
svc := newOIDCService(t)
163+
user := newTestUser()
164+
165+
info := svc.CompileUserinfo(user, "openid,groups")
166+
167+
assert.Equal(t, []string{"admins", "users"}, info.Groups)
168+
}
169+
170+
func TestCompileUserinfo_GroupsScope_Empty(t *testing.T) {
171+
svc := newOIDCService(t)
172+
user := newTestUser()
173+
user.Groups = ""
174+
175+
info := svc.CompileUserinfo(user, "openid,groups")
176+
177+
assert.Equal(t, []string{}, info.Groups)
178+
}
179+
180+
func TestCompileUserinfo_AllScopes(t *testing.T) {
181+
svc := newOIDCService(t)
182+
user := newTestUser()
183+
184+
info := svc.CompileUserinfo(user, "openid,profile,email,phone,address,groups")
185+
186+
assert.Equal(t, "Test User", info.Name)
187+
assert.Equal(t, "test@example.com", info.Email)
188+
assert.Equal(t, "+15555550100", info.PhoneNumber)
189+
require.NotNil(t, info.PhoneNumberVerified)
190+
assert.True(t, *info.PhoneNumberVerified)
191+
require.NotNil(t, info.Address)
192+
assert.Equal(t, "Springfield", info.Address.Locality)
193+
assert.Equal(t, []string{"admins", "users"}, info.Groups)
194+
}

0 commit comments

Comments
 (0)