Project

General

Profile

proposal-issue-minor-functional-2.txt

Sergey Ivanovskiy, 08/12/2026 04:15 PM

Download (8.55 KB)

 
1
================================================================================================
2
PROPOSAL - [MINOR] functional - p2j.sso_reauth.js handleSsoReauth
3
The expiry reload terminates the session it claims to re-attach to
4
================================================================================================
5

    
6
REVIEW FINDING (abridged)
7
   "The countdown-expiry window.location.reload() cannot 're-attach to a session the server
8
   re-authenticated' in the GUI web client [...] window.onpagehide [...] writes
9
   {exitTheApplication:true, date:now} to sessionStorage; on the reloaded page handleOpenEvent calls
10
   isRequiredToRedirect(), true for the whole watchdogTimeout window [...] and runs
11
   sendNotification(types.MSG_QUIT); doRedirectToLogoutPage();."
12

    
13
FILES
14
   src/com/goldencode/p2j/ui/client/driver/web/res/p2j.sso_reauth.js
15
   src/com/goldencode/p2j/ui/client/driver/web/res/p2j.socket.js
16

    
17
------------------------------------------------------------------------------------------------
18
1. ROOT CAUSE
19
------------------------------------------------------------------------------------------------
20

    
21
p2j.socket.js maintains an invariant that predates this change: in the GUI client, a page unload
22
that is not an explicit logout means "the user is closing or reloading the application", and the
23
next page to open within watchdogTimeout must quit the session rather than adopt it. That is
24
implemented by window.onpagehide (p2j.socket.js:6861) writing the exitTheApplication object to
25
sessionStorage, and isRequiredToRedirect() (:6419) reading it back:
26

    
27
   if (exit && (date < (exit.date + watchdogTimeout)))   // 120000 ms configured, 40 min in debug
28
   {
29
      return true;
30
   }
31

    
32
handleOpenEvent then sends MSG_QUIT and calls doRedirectToLogoutPage().
33

    
34
The new expiry branch calls window.location.reload() without opting out of that invariant, so it
35
inherits it: the reload is indistinguishable from a user-initiated one. The guard added at the top
36
of handleOpenEvent does not help, because exitTheApplication is a plain module variable that is a
37
fresh false on the reloaded document - the surviving state is the sessionStorage object, not the
38
variable.
39

    
40
The state this exists to rescue is genuinely reachable: SsoTokenManager.processCallbackReauthentication
41
clears reauthInProgress and cancels reauthTimeoutFuture *before* the callback page is emitted, and
42
VirtualDesktopWebHandler's notification is best-effort -
43
try{window.opener.postMessage(...)}catch(e){} followed by window.close() - so a COOP header or an
44
early popup close loses it while the session is alive. The grace period does not cover that case
45
either: the callback page's window.close() makes reauthPopup.closed true and
46
popupCloseCheckInterval nulls reauthMessageListener, so reauthInFlight() is false and the reload
47
fires immediately.
48

    
49
Severity is MINOR because the deleted showSessionExpiredPage was no better here - it wiped the DOM
50
but left the socket open, orphaning the re-authenticated session. The user lost it either way. What
51
is new is a comment and javadoc asserting a recovery the code cannot perform.
52

    
53
------------------------------------------------------------------------------------------------
54
2. PROPOSED FIX
55
------------------------------------------------------------------------------------------------
56

    
57
Two options. 2.1 makes the code match its comment and is the smaller change; 2.2 is the honest
58
long-term design. Doing neither and only correcting the javadoc (2.3) is an acceptable minimum.
59

    
60
2.1 Opt this one reload out of the quit-on-reload invariant
61
-------------------------------------------------------------
62

    
63
Add an explicit, narrowly scoped signal to p2j.socket, beside the other exported helpers:
64

    
65
   +  /**
66
   +   * Mark the imminent page unload as an internal reload rather than the user leaving, so the
67
   +   * reloaded page adopts the existing session instead of quitting it.
68
   +   * <p>
69
   +   * Used by the SSO re-auth overlay, whose expiry reload exists precisely to re-attach to a
70
   +   * session the server may have re-authenticated.  Also suppresses the leave-confirmation, which
71
   +   * would otherwise prompt the user about a reload they did not ask for.
72
   +   */
73
   +  me.beginInternalReload = function()
74
   +  {
75
   +     internalReload = true;
76
   +  };
77

    
78
with a module variable
79

    
80
   +  /** True while an internally requested reload is in flight; see me.beginInternalReload. */
81
   +  var internalReload = false;
82

    
83
and honoured in onpagehide (:6861):
84

    
85
         if (!exitTheApplication)
86
         {
87
   +        if (internalReload)
88
   +        {
89
   +           // an internal reload re-attaches to the same session; do not arm the quit-on-reload
90
   +           // path that a user-initiated reload or a tab close arms
91
   +           return;
92
   +        }
93
            // close or reload cases
94
            exitTheApplication = true;
95
            p2j.saveObject("exitTheApplication", { ... }, false);
96
         }
97

    
98
and in the expiry branch of p2j.sso_reauth.js, immediately before the reload:
99

    
100
   +        p2j.socket.beginInternalReload();
101
            countdownDiv.textContent = 'Confirming sign-in...';
102
            window.location.reload();
103

    
104
The same flag is what suppresses the leave-confirmation - see
105
proposal-issue-minor-functional-3, which owns that half and should land with this one.
106

    
107
With 2.1 the comment at :247-254 becomes true: a live session re-attaches on the reconnect, a killed
108
one is rejected at the websocket upgrade and lands on the login page.
109

    
110
2.2 Preferred alternative: ask the server instead of reloading
111
----------------------------------------------------------------
112

    
113
The socket is still open and the server already knows the answer - the entry's reauthInProgress is
114
false once processCallbackReauthentication has run. A small round trip (reuse the existing
115
notification mechanism, or a dedicated MSG_SSO_REAUTH response) would let the overlay simply
116
disappear on success and go to the login page on failure, with no page reload, no lost canvas state
117
and no dependence on sessionStorage semantics. That is a larger change and touches SsoTokenManager,
118
so it is recorded here as the direction rather than proposed as the patch.
119

    
120
2.3 Correct the documentation either way
121
------------------------------------------
122

    
123
If neither 2.1 nor 2.2 is taken, the comment at p2j.sso_reauth.js:247-254 and the two javadoc blocks
124
at :84-86 and :158-160 must stop claiming a re-attach. Replace with a statement of what actually
125
happens: the reload ends the session and lands on the login page, which is the safe outcome of the
126
two but is not recovery.
127

    
128
------------------------------------------------------------------------------------------------
129
3. RELATIONSHIP TO OTHER PROPOSALS
130
------------------------------------------------------------------------------------------------
131

    
132
   proposal-issue-minor-functional-3   the leave-confirmation prompt on the same reload; shares the
133
                                       internalReload flag introduced in 2.1.  Land together.
134
   proposal-issue-minor-functional-4   closing the orphaned popup before the same reload.
135

    
136
All three touch the same five lines of the expiry branch; apply them as one edit.
137

    
138
------------------------------------------------------------------------------------------------
139
4. RISK AND VERIFICATION
140
------------------------------------------------------------------------------------------------
141

    
142
Risk: moderate for 2.1, because it puts a hole in a long-standing invariant. The hole is narrow -
143
one caller, set only immediately before an unload the page itself requested, and never reset (the
144
document is going away) - but a future caller that sets it and then does not unload would leave a
145
GUI page that no longer arms quit-on-reload. Consider naming it to discourage that, and asserting in
146
onpagehide that it is only ever observed once.
147

    
148
Verification
149
   1. Reproduce the target case: trigger SSO re-auth, click Re-login, complete the IdP flow in the
150
      popup, and kill the postMessage (close the popup manually the instant it lands on /reauthcb,
151
      or serve the callback with a COOP header). Let the countdown expire. Expected with 2.1: the
152
      page reloads and comes back into the *same* live session; pre-fix it logs out.
153
   2. Let the countdown expire with no re-login attempt at all. Expected: the server force-logout
154
      has already fired, the websocket upgrade is rejected, and the page lands on the login page.
155
   3. Confirm the invariant is intact for ordinary use: F5 on a GUI page, and closing the tab, must
156
      both still quit the session (internalReload is false on those paths).
157
   4. Confirm the CHUI client is unaffected - onpagehide returns early on !p2j.isGui, and
158
      ChuiWebPageHandler emits isGui=false.