mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
feat: Add more tests for sessions and edge case
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
This commit is contained in:
+2
-2
@@ -449,8 +449,8 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if redirectURL != "" {
|
||||
// Session found but consent required — no UI allowed.
|
||||
s.redirectWithError(w, r, authReq, errInteractionRequired, "Consent required")
|
||||
// Session found but user interaction is needed (consent or MFA) — no UI allowed.
|
||||
s.redirectWithError(w, r, authReq, errInteractionRequired, "User interaction required")
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
@@ -908,6 +908,61 @@ func TestScopesCoveredByConsent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestConsentSurvivesSessionDeletion verifies that UserIdentity.Consents
|
||||
// persists independently from AuthSession lifecycle (logout should not
|
||||
// clear consent decisions).
|
||||
func TestConsentSurvivesSessionDeletion(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
httpServer, s := newTestServerWithSessions(t, nil)
|
||||
defer httpServer.Close()
|
||||
|
||||
userID := "test-user"
|
||||
connectorID := "mock"
|
||||
clientID := "test-client"
|
||||
|
||||
// Create UserIdentity with existing consents.
|
||||
require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{
|
||||
UserID: userID,
|
||||
ConnectorID: connectorID,
|
||||
Claims: storage.Claims{UserID: userID, Username: "testuser"},
|
||||
Consents: map[string][]string{clientID: {"openid", "email", "profile"}},
|
||||
CreatedAt: time.Now(),
|
||||
LastLogin: time.Now(),
|
||||
}))
|
||||
|
||||
// Create and then delete the session (simulating logout).
|
||||
require.NoError(t, s.storage.CreateAuthSession(ctx, storage.AuthSession{
|
||||
UserID: userID, ConnectorID: connectorID, Nonce: "nonce",
|
||||
CreatedAt: time.Now(), LastActivity: time.Now(),
|
||||
}))
|
||||
require.NoError(t, s.storage.DeleteAuthSession(ctx, userID, connectorID))
|
||||
|
||||
// Session is gone.
|
||||
_, err := s.storage.GetAuthSession(ctx, userID, connectorID)
|
||||
require.ErrorIs(t, err, storage.ErrNotFound)
|
||||
|
||||
// Consent survives.
|
||||
ui, err := s.storage.GetUserIdentity(ctx, userID, connectorID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"openid", "email", "profile"}, ui.Consents[clientID],
|
||||
"consent should survive session deletion")
|
||||
}
|
||||
|
||||
// TestConsentIsolatedBetweenClients verifies that consent given for
|
||||
// client-A does not satisfy scope check for client-B.
|
||||
func TestConsentIsolatedBetweenClients(t *testing.T) {
|
||||
approvedForA := map[string][]string{"client-a": {"openid", "email"}}
|
||||
|
||||
// client-b should not have consent.
|
||||
require.False(t, scopesCoveredByConsent(approvedForA["client-b"], []string{"openid", "email"}),
|
||||
"consent for client-a should not cover client-b")
|
||||
|
||||
// client-a should have consent.
|
||||
require.True(t, scopesCoveredByConsent(approvedForA["client-a"], []string{"openid", "email"}),
|
||||
"consent for client-a should cover client-a's requested scopes")
|
||||
}
|
||||
|
||||
func TestHandlePasswordLoginWithSkipApproval(t *testing.T) {
|
||||
ctx := t.Context()
|
||||
|
||||
|
||||
@@ -293,6 +293,62 @@ func TestDiscoveryWithoutSessions(t *testing.T) {
|
||||
require.Empty(t, d.EndSession)
|
||||
}
|
||||
|
||||
// TestHandleLogoutFromCookie tests logout without id_token_hint,
|
||||
// where the user is identified by their session cookie alone.
|
||||
func TestHandleLogoutFromCookie(t *testing.T) {
|
||||
httpServer, server := newTestServerWithSessions(t, nil)
|
||||
defer httpServer.Close()
|
||||
|
||||
ctx := t.Context()
|
||||
userID := "test-user"
|
||||
connectorID := "mock"
|
||||
nonce := "testnonce"
|
||||
|
||||
require.NoError(t, server.storage.CreateAuthSession(ctx, storage.AuthSession{
|
||||
UserID: userID, ConnectorID: connectorID, Nonce: nonce,
|
||||
CreatedAt: time.Now(), LastActivity: time.Now(),
|
||||
}))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/logout", nil)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "dex_session",
|
||||
Value: sessionCookieValue(userID, connectorID, nonce, server.sessionConfig.CookieEncryptionKey),
|
||||
})
|
||||
server.ServeHTTP(rr, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
require.Contains(t, rr.Body.String(), "successfully logged out")
|
||||
|
||||
// Session should be deleted.
|
||||
_, err := server.storage.GetAuthSession(ctx, userID, connectorID)
|
||||
require.ErrorIs(t, err, storage.ErrNotFound)
|
||||
|
||||
// Cookie should be cleared.
|
||||
for _, c := range rr.Result().Cookies() {
|
||||
if c.Name == "dex_session" {
|
||||
require.Equal(t, -1, c.MaxAge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLogoutCallbackWithExpiredSession tests that /logout/callback
|
||||
// returns an error when the session has expired or been deleted.
|
||||
func TestLogoutCallbackWithExpiredSession(t *testing.T) {
|
||||
httpServer, server := newTestServerWithSessions(t, nil)
|
||||
defer httpServer.Close()
|
||||
|
||||
// No session created — cookie points to nonexistent session.
|
||||
rr := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/logout/callback", nil)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "dex_session",
|
||||
Value: sessionCookieValue("user-1", "mock", "nonce", server.sessionConfig.CookieEncryptionKey),
|
||||
})
|
||||
server.ServeHTTP(rr, req)
|
||||
require.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
|
||||
func TestRevokeRefreshTokensReturnsConnectorData(t *testing.T) {
|
||||
httpServer, server := newTestServerWithSessions(t, nil)
|
||||
defer httpServer.Close()
|
||||
|
||||
@@ -336,13 +336,11 @@ func TestRefreshTokenAuthTime(t *testing.T) {
|
||||
"access token should not have auth_time when sessions are disabled")
|
||||
}
|
||||
|
||||
// TODO: newIDToken in handleRefreshToken is currently called with time.Time{},
|
||||
// so the ID token does not include auth_time. Once fixed, uncomment:
|
||||
// if tc.wantAuthTime {
|
||||
// idClaims := decodeJWTClaims(t, resp.IDToken)
|
||||
// assert.Equal(t, float64(loginTime.Unix()), idClaims["auth_time"],
|
||||
// "id token auth_time should match UserIdentity.LastLogin")
|
||||
// }
|
||||
if tc.wantAuthTime {
|
||||
idClaims := decodeJWTClaims(t, resp.IDToken)
|
||||
assert.Equal(t, float64(loginTime.Unix()), idClaims["auth_time"],
|
||||
"id token auth_time should match UserIdentity.LastLogin")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user