@@ -1497,6 +1497,105 @@ func TestAuditLogE2E_MutationPushPausedThenListRendered(t *testing.T) {
14971497 }
14981498}
14991499
1500+ // TestValidateLoginTokenAdminComparisonGuard is a correctness guard for the
1501+ // constant-time admin token comparison inside validateLoginToken (cloudserver.go:160).
1502+ // A correct admin token must be accepted and a wrong one rejected, preserving
1503+ // behavior after the timing-safe hmac.Equal replacement (security issue #350).
1504+ func TestValidateLoginTokenAdminComparisonGuard (t * testing.T ) {
1505+ authSvc , err := cloudauth .NewService (& cloudstore.CloudStore {}, strings .Repeat ("x" , 32 ))
1506+ if err != nil {
1507+ t .Fatalf ("new auth service: %v" , err )
1508+ }
1509+ authSvc .SetBearerToken ("sync-token" )
1510+ authSvc .SetDashboardSessionTokens ([]string {"admin-token" })
1511+ srv := New (& fakeStore {}, authSvc , 0 , WithDashboardAdminToken ("admin-token" ))
1512+
1513+ // Correct admin token must authenticate and redirect to dashboard.
1514+ goodLogin := httptest .NewRecorder ()
1515+ goodReq := httptest .NewRequest (http .MethodPost , "/dashboard/login" , strings .NewReader ("token=admin-token" ))
1516+ goodReq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
1517+ srv .Handler ().ServeHTTP (goodLogin , goodReq )
1518+ if goodLogin .Code != http .StatusSeeOther {
1519+ t .Fatalf ("correct admin token must authenticate, got %d body=%q" , goodLogin .Code , goodLogin .Body .String ())
1520+ }
1521+
1522+ // Wrong token must be rejected (re-render login form).
1523+ badLogin := httptest .NewRecorder ()
1524+ badReq := httptest .NewRequest (http .MethodPost , "/dashboard/login" , strings .NewReader ("token=wrong-token" ))
1525+ badReq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
1526+ srv .Handler ().ServeHTTP (badLogin , badReq )
1527+ if badLogin .Code == http .StatusSeeOther {
1528+ t .Fatalf ("wrong admin token must be rejected, got %d" , badLogin .Code )
1529+ }
1530+ for _ , cookie := range badLogin .Result ().Cookies () {
1531+ if cookie .Name == dashboardSessionCookieName {
1532+ t .Fatal ("wrong admin token must not set session cookie" )
1533+ }
1534+ }
1535+ }
1536+
1537+ // TestIsDashboardAdminComparisonGuard is a correctness guard for the
1538+ // constant-time admin token comparison inside isDashboardAdmin (cloudserver.go:320).
1539+ // A valid admin session cookie must grant admin access and a wrong one must not,
1540+ // preserving behavior after the timing-safe hmac.Equal replacement (security issue #350).
1541+ func TestIsDashboardAdminComparisonGuard (t * testing.T ) {
1542+ authSvc , err := cloudauth .NewService (& cloudstore.CloudStore {}, strings .Repeat ("x" , 32 ))
1543+ if err != nil {
1544+ t .Fatalf ("new auth service: %v" , err )
1545+ }
1546+ authSvc .SetBearerToken ("sync-token" )
1547+ authSvc .SetDashboardSessionTokens ([]string {"admin-token" })
1548+ srv := New (& fakeStore {}, authSvc , 0 , WithDashboardAdminToken ("admin-token" ))
1549+
1550+ // Establish a valid admin session cookie via login.
1551+ loginRec := httptest .NewRecorder ()
1552+ loginReq := httptest .NewRequest (http .MethodPost , "/dashboard/login" , strings .NewReader ("token=admin-token" ))
1553+ loginReq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
1554+ srv .Handler ().ServeHTTP (loginRec , loginReq )
1555+ if loginRec .Code != http .StatusSeeOther {
1556+ t .Fatalf ("setup: expected admin login to succeed, got %d body=%q" , loginRec .Code , loginRec .Body .String ())
1557+ }
1558+ adminCookies := loginRec .Result ().Cookies ()
1559+
1560+ // Admin session must grant access to /dashboard/admin.
1561+ adminRec := httptest .NewRecorder ()
1562+ adminReq := httptest .NewRequest (http .MethodGet , "/dashboard/admin" , nil )
1563+ for _ , c := range adminCookies {
1564+ adminReq .AddCookie (c )
1565+ }
1566+ srv .Handler ().ServeHTTP (adminRec , adminReq )
1567+ if adminRec .Code != http .StatusOK {
1568+ t .Fatalf ("valid admin session must access /dashboard/admin, got %d body=%q" , adminRec .Code , adminRec .Body .String ())
1569+ }
1570+
1571+ // Non-admin session (sync-token login) must not access /dashboard/admin.
1572+ authSvc2 , err := cloudauth .NewService (& cloudstore.CloudStore {}, strings .Repeat ("x" , 32 ))
1573+ if err != nil {
1574+ t .Fatalf ("new auth service 2: %v" , err )
1575+ }
1576+ authSvc2 .SetBearerToken ("sync-token" )
1577+ authSvc2 .SetDashboardSessionTokens ([]string {"admin-token" })
1578+ srv2 := New (& fakeStore {}, authSvc2 , 0 , WithDashboardAdminToken ("admin-token" ))
1579+
1580+ syncLoginRec := httptest .NewRecorder ()
1581+ syncLoginReq := httptest .NewRequest (http .MethodPost , "/dashboard/login" , strings .NewReader ("token=sync-token" ))
1582+ syncLoginReq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
1583+ srv2 .Handler ().ServeHTTP (syncLoginRec , syncLoginReq )
1584+ if syncLoginRec .Code != http .StatusSeeOther {
1585+ t .Fatalf ("setup: sync-token login must succeed, got %d body=%q" , syncLoginRec .Code , syncLoginRec .Body .String ())
1586+ }
1587+
1588+ nonAdminRec := httptest .NewRecorder ()
1589+ nonAdminReq := httptest .NewRequest (http .MethodGet , "/dashboard/admin" , nil )
1590+ for _ , c := range syncLoginRec .Result ().Cookies () {
1591+ nonAdminReq .AddCookie (c )
1592+ }
1593+ srv2 .Handler ().ServeHTTP (nonAdminRec , nonAdminReq )
1594+ if nonAdminRec .Code == http .StatusOK {
1595+ t .Fatalf ("non-admin session must not access /dashboard/admin, got %d" , nonAdminRec .Code )
1596+ }
1597+ }
1598+
15001599// TestInsecureModeLoginRedirects asserts that GET /dashboard/login with auth==nil
15011600// returns 303 to /dashboard/ (login is a no-op in insecure mode). Satisfies REQ-110.
15021601func TestInsecureModeLoginRedirects (t * testing.T ) {
0 commit comments