-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathsessionlogin.go
More file actions
146 lines (130 loc) · 5.71 KB
/
Copy pathsessionlogin.go
File metadata and controls
146 lines (130 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package authflow
import (
"context"
"net/http"
"time"
"github.com/dexidp/dex/storage"
)
func (h *Handler) trySessionLogin(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest) bool {
session := h.Sessions.ValidAuthSession(ctx, w, r, authReq)
return h.trySessionLoginWithSession(ctx, r, w, authReq, session)
}
// trySessionLoginWithSession completes the login from an existing session: a
// direct session for the client, or, failing that, an SSO session shared by
// another client. SSO sharing is unidirectional — a source sharing with a target
// does not mean the target shares back. Returns false when no session applies.
func (h *Handler) trySessionLoginWithSession(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest, session *storage.AuthSession) bool {
if session == nil {
return false
}
now := h.Now()
_, directLogin := session.ClientStates[authReq.ClientID]
if !directLogin {
// No direct session for this client — try SSO from a sharing client.
sourceState := h.Sessions.FindSSO(ctx, session, authReq.ClientID)
if sourceState == nil {
return false
}
// Create a new client state for the target client via SSO. It carries the
// source's authentication time: the user did not authenticate again here.
var newState *storage.ClientAuthState
if err := h.Storage.UpdateAuthSession(ctx, session.ID, func(old storage.AuthSession) (storage.AuthSession, error) {
if old.ClientStates == nil {
old.ClientStates = make(map[string]*storage.ClientAuthState)
}
newState = &storage.ClientAuthState{
AuthenticatedAt: sourceState.AuthenticatedAt,
LastActivity: now,
ViaSSO: true,
}
old.ClientStates[authReq.ClientID] = newState
old.LastActivity = now
old.IdleExpiry = h.Sessions.IdleExpiry(now)
return old, nil
}); err != nil {
h.Logger.ErrorContext(ctx, "session: failed to create SSO client state", "err", err)
return false
}
// Keep the caller's session in sync for storage backends that work on a
// deserialized copy (SQL, ent, Kubernetes, etcd) rather than the shared
// in-memory map.
if session.ClientStates == nil {
session.ClientStates = make(map[string]*storage.ClientAuthState)
}
session.ClientStates[authReq.ClientID] = newState
h.Logger.DebugContext(ctx, "session: SSO login from sharing client",
"user_id", session.UserID, "connector_id", session.ConnectorID, "client_id", authReq.ClientID)
}
// Load identity from storage (same path for direct and SSO login).
ui, err := h.Storage.GetUserIdentity(ctx, session.UserID, session.ConnectorID)
if err != nil {
h.Logger.ErrorContext(ctx, "session: failed to get user identity", "err", err)
return false
}
// Check max_age against THIS session's authentication time for this client,
// not the global per-identity LastLogin. ui.LastLogin is a single global row
// rewritten to now() by EVERY interactive login from ANY browser/device, so a
// fresh login on a second device would otherwise satisfy an RP's max_age
// re-authentication demand for a stale session on the first.
//
// Prefer ClientAuthState.AuthenticatedAt (direct login or SSO-carried above).
// If it is missing, force re-authentication rather than falling back to LastLogin.
cs := session.ClientStates[authReq.ClientID]
if cs == nil || cs.AuthenticatedAt.IsZero() {
return false
}
authenticatedAt := cs.AuthenticatedAt
if authReq.MaxAge >= 0 {
if now.Sub(authenticatedAt) > time.Duration(authReq.MaxAge)*time.Second {
return false
}
}
if directLogin {
h.Logger.DebugContext(ctx, "session: re-authenticated from session",
"session_id", session.ID, "user_id", session.UserID)
}
return h.finishSessionLogin(ctx, r, w, authReq, session, &ui, authenticatedAt, now)
}
// finishSessionLogin completes a session-based login (direct or SSO) by updating the auth request
// with the user's identity, refreshing session activity, and returning the appropriate redirect URL.
// authenticatedAt is the per-session, per-client authentication time used for both max_age gating
// and the auth_time claim the RP sees.
func (h *Handler) finishSessionLogin(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest, session *storage.AuthSession, ui *storage.UserIdentity, authenticatedAt time.Time, now time.Time) bool {
claims := storage.Claims{
UserID: ui.Claims.UserID,
Username: ui.Claims.Username,
PreferredUsername: ui.Claims.PreferredUsername,
Email: ui.Claims.Email,
EmailVerified: ui.Claims.EmailVerified,
Groups: ui.Claims.Groups,
}
// Update AuthRequest with stored identity and auth_time from the per-session login.
if err := h.Storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {
a.LoggedIn = true
a.Claims = claims
a.ConnectorID = session.ConnectorID
a.AuthTime = authenticatedAt
return a, nil
}); err != nil {
h.Logger.ErrorContext(ctx, "session: failed to update auth request", "err", err)
return false
}
// Update session activity.
_ = h.Storage.UpdateAuthSession(ctx, session.ID, func(old storage.AuthSession) (storage.AuthSession, error) {
old.LastActivity = now
old.IdleExpiry = h.Sessions.IdleExpiry(now)
if cs, ok := old.ClientStates[authReq.ClientID]; ok {
cs.LastActivity = now
}
return old, nil
})
// Re-read to get the updated AuthRequest (LoggedIn, Claims, ConnectorID set above),
// then let the shared decision pick the next step.
updated, err := h.Storage.GetAuthRequest(ctx, authReq.ID)
if err != nil {
h.Logger.ErrorContext(ctx, "session: failed to get auth request", "err", err)
return false
}
http.Redirect(w, r, h.buildContinueURL(updated), http.StatusSeeOther)
return true
}