OpenID Connect (OIDC) identity and OAuth 2.0 provider with pluggable connectors
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

751 lines
22 KiB

package server
import (
"crypto"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dexidp/dex/storage"
"github.com/dexidp/dex/storage/memory"
)
func newTestSessionServer(t *testing.T) *Server {
t.Helper()
now := time.Date(2026, 3, 16, 12, 0, 0, 0, time.UTC)
issuerURL, err := url.Parse("https://example.com/dex")
require.NoError(t, err)
return &Server{
storage: memory.New(nil),
logger: slog.Default(),
now: func() time.Time { return now },
sessionConfig: &SessionConfig{
CookieName: "dex_session",
AbsoluteLifetime: 24 * time.Hour,
ValidIfNotUsedFor: 1 * time.Hour,
},
issuerURL: *issuerURL,
}
}
func TestSetSessionCookie(t *testing.T) {
s := newTestSessionServer(t)
w := httptest.NewRecorder()
s.setSessionCookie(w, "user1", "conn1", "nonce123", false)
cookies := w.Result().Cookies()
require.Len(t, cookies, 1)
c := cookies[0]
assert.Equal(t, "dex_session", c.Name)
assert.Equal(t, sessionCookieValue("user1", "conn1", "nonce123"), c.Value)
assert.Equal(t, "/dex", c.Path)
assert.True(t, c.HttpOnly)
assert.True(t, c.Secure)
assert.Equal(t, http.SameSiteLaxMode, c.SameSite)
}
func TestSetSessionCookie_HTTP(t *testing.T) {
s := newTestSessionServer(t)
u, _ := url.Parse("http://localhost:5556/dex")
s.issuerURL = *u
w := httptest.NewRecorder()
s.setSessionCookie(w, "user1", "conn1", "nonce123", false)
cookies := w.Result().Cookies()
require.Len(t, cookies, 1)
assert.False(t, cookies[0].Secure)
}
func TestClearSessionCookie(t *testing.T) {
s := newTestSessionServer(t)
w := httptest.NewRecorder()
s.clearSessionCookie(w)
cookies := w.Result().Cookies()
require.Len(t, cookies, 1)
assert.Equal(t, -1, cookies[0].MaxAge)
assert.Equal(t, "", cookies[0].Value)
}
func TestSessionCookieValueRoundtrip(t *testing.T) {
tests := []struct {
name string
userID string
connectorID string
nonce string
}{
{"simple", "user1", "ldap", "abc123"},
{"with special chars", "user@example.com", "oidc-provider", "xyz789"},
{"unicode", "юзер", "коннектор", "nonce"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
value := sessionCookieValue(tt.userID, tt.connectorID, tt.nonce)
gotUser, gotConn, gotNonce, err := parseSessionCookie(value)
require.NoError(t, err)
assert.Equal(t, tt.userID, gotUser)
assert.Equal(t, tt.connectorID, gotConn)
assert.Equal(t, tt.nonce, gotNonce)
})
}
}
func TestParseSessionCookie_Invalid(t *testing.T) {
//nolint:dogsled // only for tests
_, _, _, err := parseSessionCookie("invalid")
assert.Error(t, err)
//nolint:dogsled // only for tests
_, _, _, err = parseSessionCookie("a.b")
assert.Error(t, err)
}
func TestGetValidAuthSession(t *testing.T) {
ctx := t.Context()
authReq := &storage.AuthRequest{ConnectorID: "conn1"}
t.Run("no session config", func(t *testing.T) {
s := newTestSessionServer(t)
s.sessionConfig = nil
r := httptest.NewRequest(http.MethodGet, "/", nil)
assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq))
})
t.Run("no cookie", func(t *testing.T) {
s := newTestSessionServer(t)
r := httptest.NewRequest(http.MethodGet, "/", nil)
assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq))
})
t.Run("invalid cookie format", func(t *testing.T) {
s := newTestSessionServer(t)
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: "invalid-format"})
w := httptest.NewRecorder()
assert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq))
// Cookie should be cleared.
assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)
})
t.Run("session not found", func(t *testing.T) {
s := newTestSessionServer(t)
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("nouser", "noconn", "nonce")})
w := httptest.NewRecorder()
assert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq))
// Cookie should be cleared.
assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)
})
t.Run("valid session", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
nonce := "test-nonce"
session := storage.AuthSession{
UserID: "user1",
ConnectorID: "conn1",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-5 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user1", "conn1", nonce)})
result := s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq)
require.NotNil(t, result)
assert.Equal(t, "user1", result.UserID)
assert.Equal(t, "conn1", result.ConnectorID)
})
t.Run("connector mismatch", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
nonce := "test-nonce-conn"
session := storage.AuthSession{
UserID: "user1",
ConnectorID: "ldap",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-5 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user1", "ldap", nonce)})
githubReq := &storage.AuthRequest{ConnectorID: "github"}
assert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, githubReq))
})
t.Run("nonce mismatch", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
session := storage.AuthSession{
UserID: "user2",
ConnectorID: "conn2",
Nonce: "correct-nonce",
ClientStates: map[string]*storage.ClientAuthState{},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-5 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user2", "conn2", "wrong-nonce")})
conn2Req := &storage.AuthRequest{ConnectorID: "conn2"}
w := httptest.NewRecorder()
assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn2Req))
assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)
})
t.Run("expired absolute lifetime", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
nonce := "expired-nonce"
session := storage.AuthSession{
UserID: "user3",
ConnectorID: "conn3",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{},
CreatedAt: now.Add(-25 * time.Hour),
LastActivity: now.Add(-1 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user3", "conn3", nonce)})
conn3Req := &storage.AuthRequest{ConnectorID: "conn3"}
w := httptest.NewRecorder()
assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn3Req))
assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)
// Session should be deleted.
_, err := s.storage.GetAuthSession(ctx, "user3", "conn3")
assert.ErrorIs(t, err, storage.ErrNotFound)
})
t.Run("expired idle timeout", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
nonce := "idle-nonce"
session := storage.AuthSession{
UserID: "user4",
ConnectorID: "conn4",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{},
CreatedAt: now.Add(-2 * time.Hour),
LastActivity: now.Add(-2 * time.Hour),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user4", "conn4", nonce)})
conn4Req := &storage.AuthRequest{ConnectorID: "conn4"}
w := httptest.NewRecorder()
assert.Nil(t, s.getValidAuthSession(ctx, w, r, conn4Req))
assert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)
// Session should be deleted.
_, err := s.storage.GetAuthSession(ctx, "user4", "conn4")
assert.ErrorIs(t, err, storage.ErrNotFound)
})
}
func TestCreateOrUpdateAuthSession(t *testing.T) {
ctx := t.Context()
t.Run("create new session", func(t *testing.T) {
s := newTestSessionServer(t)
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
authReq := storage.AuthRequest{
ID: "auth-1",
ClientID: "client-1",
Claims: storage.Claims{UserID: "user-1"},
ConnectorID: "mock",
}
err := s.createOrUpdateAuthSession(ctx, r, w, authReq, false)
require.NoError(t, err)
// Cookie should be set.
cookies := w.Result().Cookies()
require.Len(t, cookies, 1)
userID, connectorID, nonce, err := parseSessionCookie(cookies[0].Value)
require.NoError(t, err)
assert.Equal(t, "user-1", userID)
assert.Equal(t, "mock", connectorID)
assert.NotEmpty(t, nonce)
// Session should exist in storage.
session, err := s.storage.GetAuthSession(ctx, "user-1", "mock")
require.NoError(t, err)
assert.Equal(t, "user-1", session.UserID)
assert.Equal(t, "mock", session.ConnectorID)
require.Contains(t, session.ClientStates, "client-1")
assert.True(t, session.ClientStates["client-1"].Active)
})
t.Run("update existing session", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
nonce := "existing-nonce"
existingSession := storage.AuthSession{
UserID: "user-1",
ConnectorID: "mock",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{
"client-1": {
Active: true,
ExpiresAt: now.Add(24 * time.Hour),
LastActivity: now.Add(-10 * time.Minute),
},
},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-10 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, existingSession))
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
authReq := storage.AuthRequest{
ID: "auth-2",
ClientID: "client-2",
Claims: storage.Claims{UserID: "user-1"},
ConnectorID: "mock",
}
err := s.createOrUpdateAuthSession(ctx, r, w, authReq, false)
require.NoError(t, err)
// Cookie should be set with existing nonce.
cookies := w.Result().Cookies()
require.Len(t, cookies, 1)
_, _, gotNonce, err := parseSessionCookie(cookies[0].Value)
require.NoError(t, err)
assert.Equal(t, nonce, gotNonce)
// Session should have both clients.
session, err := s.storage.GetAuthSession(ctx, "user-1", "mock")
require.NoError(t, err)
assert.Len(t, session.ClientStates, 2)
assert.Contains(t, session.ClientStates, "client-1")
assert.Contains(t, session.ClientStates, "client-2")
})
t.Run("nil session config", func(t *testing.T) {
s := newTestSessionServer(t)
s.sessionConfig = nil
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
err := s.createOrUpdateAuthSession(ctx, r, w, storage.AuthRequest{}, false)
assert.NoError(t, err)
assert.Empty(t, w.Result().Cookies())
})
}
// setupSessionLoginFixture creates the necessary storage objects for trySessionLogin tests.
func setupSessionLoginFixture(t *testing.T, s *Server) storage.AuthRequest {
t.Helper()
ctx := t.Context()
now := s.now()
require.NoError(t, s.storage.CreateAuthSession(ctx, storage.AuthSession{
UserID: "user-1",
ConnectorID: "mock",
Nonce: "test-nonce",
ClientStates: map[string]*storage.ClientAuthState{
"client-1": {
Active: true,
ExpiresAt: now.Add(24 * time.Hour),
LastActivity: now.Add(-1 * time.Minute),
},
},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-1 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}))
require.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{
UserID: "user-1",
ConnectorID: "mock",
Claims: storage.Claims{
UserID: "user-1",
Username: "testuser",
Email: "test@example.com",
},
Consents: map[string][]string{"client-1": {"openid", "email"}},
CreatedAt: now.Add(-1 * time.Hour),
LastLogin: now.Add(-30 * time.Minute),
}))
authReq := storage.AuthRequest{
ID: storage.NewID(),
ClientID: "client-1",
ConnectorID: "mock",
Scopes: []string{"openid", "email"},
RedirectURI: "http://localhost/callback",
MaxAge: -1,
HMACKey: storage.NewHMACKey(crypto.SHA256),
Expiry: now.Add(10 * time.Minute),
}
require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))
return authReq
}
func sessionCookieRequest(userID, connectorID, nonce string) *http.Request {
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue(userID, connectorID, nonce)})
return r
}
func TestTrySessionLogin(t *testing.T) {
ctx := t.Context()
t.Run("no session", func(t *testing.T) {
s := newTestSessionServer(t)
authReq := storage.AuthRequest{ConnectorID: "mock"}
r := httptest.NewRequest(http.MethodGet, "/", nil)
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok)
})
t.Run("successful login with skipApproval", func(t *testing.T) {
s := newTestSessionServer(t)
s.skipApproval = true
authReq := setupSessionLoginFixture(t, s)
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.True(t, ok)
})
t.Run("successful login redirects to approval", func(t *testing.T) {
s := newTestSessionServer(t)
s.skipApproval = false
authReq := setupSessionLoginFixture(t, s)
authReq.ForceApprovalPrompt = true
require.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {
a.ForceApprovalPrompt = true
return a, nil
}))
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
redirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.True(t, ok)
assert.Contains(t, redirectURL, "/approval")
assert.Contains(t, redirectURL, "req="+authReq.ID)
})
t.Run("skips approval when consent already given", func(t *testing.T) {
s := newTestSessionServer(t)
s.skipApproval = false
authReq := setupSessionLoginFixture(t, s)
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.True(t, ok)
})
t.Run("connector mismatch returns false", func(t *testing.T) {
s := newTestSessionServer(t)
authReq := setupSessionLoginFixture(t, s)
authReq.ConnectorID = "github"
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok)
})
t.Run("no client state for requested client", func(t *testing.T) {
s := newTestSessionServer(t)
authReq := setupSessionLoginFixture(t, s)
authReq.ClientID = "unknown-client"
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok)
})
t.Run("expired client state returns false", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
require.NoError(t, s.storage.CreateAuthSession(t.Context(), storage.AuthSession{
UserID: "user-exp",
ConnectorID: "mock",
Nonce: "nonce-exp",
ClientStates: map[string]*storage.ClientAuthState{
"client-1": {
Active: true,
ExpiresAt: now.Add(-1 * time.Hour),
},
},
CreatedAt: now.Add(-2 * time.Hour),
LastActivity: now.Add(-1 * time.Minute),
}))
require.NoError(t, s.storage.CreateUserIdentity(t.Context(), storage.UserIdentity{
UserID: "user-exp",
ConnectorID: "mock",
Claims: storage.Claims{UserID: "user-exp"},
Consents: make(map[string][]string),
CreatedAt: now,
LastLogin: now,
}))
authReq := storage.AuthRequest{
ID: storage.NewID(),
ClientID: "client-1",
ConnectorID: "mock",
MaxAge: -1,
HMACKey: storage.NewHMACKey(crypto.SHA256),
Expiry: now.Add(10 * time.Minute),
}
require.NoError(t, s.storage.CreateAuthRequest(t.Context(), authReq))
r := sessionCookieRequest("user-exp", "mock", "nonce-exp")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok)
})
t.Run("updates session activity", func(t *testing.T) {
s := newTestSessionServer(t)
s.skipApproval = true
authReq := setupSessionLoginFixture(t, s)
r := sessionCookieRequest("user-1", "mock", "test-nonce")
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
require.True(t, ok)
session, err := s.storage.GetAuthSession(ctx, "user-1", "mock")
require.NoError(t, err)
assert.Equal(t, s.now(), session.LastActivity)
})
}
// setupSessionWithIdentity creates an AuthSession, UserIdentity, and AuthRequest in storage
// for use in trySessionLogin tests. Returns the authReq.
func setupSessionWithIdentity(t *testing.T, s *Server, now time.Time, lastLogin time.Time) storage.AuthRequest {
t.Helper()
ctx := t.Context()
nonce := "test-nonce"
session := storage.AuthSession{
UserID: "user-1",
ConnectorID: "mock",
Nonce: nonce,
ClientStates: map[string]*storage.ClientAuthState{
"client-1": {
Active: true,
ExpiresAt: now.Add(24 * time.Hour),
LastActivity: now.Add(-1 * time.Minute),
},
},
CreatedAt: now.Add(-30 * time.Minute),
LastActivity: now.Add(-1 * time.Minute),
IPAddress: "127.0.0.1",
UserAgent: "test",
}
require.NoError(t, s.storage.CreateAuthSession(ctx, session))
ui := storage.UserIdentity{
UserID: "user-1",
ConnectorID: "mock",
Claims: storage.Claims{
UserID: "user-1",
Username: "testuser",
Email: "test@example.com",
},
Consents: make(map[string][]string),
CreatedAt: now.Add(-1 * time.Hour),
LastLogin: lastLogin,
}
require.NoError(t, s.storage.CreateUserIdentity(ctx, ui))
authReq := storage.AuthRequest{
ID: storage.NewID(),
ClientID: "client-1",
ConnectorID: "mock",
Scopes: []string{"openid"},
RedirectURI: "http://localhost/callback",
MaxAge: -1,
HMACKey: storage.NewHMACKey(crypto.SHA256),
Expiry: now.Add(10 * time.Minute),
}
require.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))
return authReq
}
func TestTrySessionLogin_MaxAge(t *testing.T) {
ctx := t.Context()
t.Run("max_age not specified, session reused", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
authReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour))
authReq.MaxAge = -1 // not specified
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")})
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.True(t, ok, "session should be reused when max_age is not specified")
})
t.Run("max_age satisfied, session reused", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
// User logged in 10 minutes ago, max_age=3600 (1 hour)
authReq := setupSessionWithIdentity(t, s, now, now.Add(-10*time.Minute))
authReq.MaxAge = 3600
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")})
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.True(t, ok, "session should be reused when max_age is satisfied")
})
t.Run("max_age exceeded, force re-auth", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
// User logged in 2 hours ago, max_age=3600 (1 hour)
authReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour))
authReq.MaxAge = 3600
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")})
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok, "session should NOT be reused when max_age is exceeded")
})
t.Run("max_age=0, always force re-auth", func(t *testing.T) {
s := newTestSessionServer(t)
now := s.now()
// User logged in 1 second ago, max_age=0
authReq := setupSessionWithIdentity(t, s, now, now.Add(-1*time.Second))
authReq.MaxAge = 0
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")})
w := httptest.NewRecorder()
_, ok := s.trySessionLogin(ctx, r, w, &authReq)
assert.False(t, ok, "max_age=0 should always force re-authentication")
})
t.Run("auth_time is set from UserIdentity.LastLogin", func(t *testing.T) {
s := newTestSessionServer(t)
s.skipApproval = false
now := s.now()
lastLogin := now.Add(-10 * time.Minute)
authReq := setupSessionWithIdentity(t, s, now, lastLogin)
authReq.ForceApprovalPrompt = true // force approval so AuthRequest is not deleted
require.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {
a.ForceApprovalPrompt = true
return a, nil
}))
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "dex_session", Value: sessionCookieValue("user-1", "mock", "test-nonce")})
w := httptest.NewRecorder()
redirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq)
require.True(t, ok)
assert.Contains(t, redirectURL, "/approval")
// Verify AuthTime was set on the auth request.
updated, err := s.storage.GetAuthRequest(ctx, authReq.ID)
require.NoError(t, err)
assert.Equal(t, lastLogin.Unix(), updated.AuthTime.Unix())
})
}
func TestParseAuthRequest_PromptAndMaxAge(t *testing.T) {
t.Run("prompt=consent sets ForceApprovalPrompt", func(t *testing.T) {
authReq := storage.AuthRequest{
Prompt: "consent",
ForceApprovalPrompt: true,
}
assert.True(t, authReq.ForceApprovalPrompt)
assert.Equal(t, "consent", authReq.Prompt)
})
t.Run("max_age default is -1", func(t *testing.T) {
authReq := storage.AuthRequest{
MaxAge: -1,
}
assert.Equal(t, -1, authReq.MaxAge)
})
}