mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: persist session cookie to disk to prevent PWA logout (#23746)
This commit is contained in:
@@ -582,5 +582,20 @@ func (api *API) createAPIKey(ctx context.Context, params apikey.CreateParams) (*
|
||||
Value: sessionToken,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
// MaxAge is set so the browser persists the cookie to disk rather
|
||||
// than keeping it in memory as a session cookie. Standalone PWAs
|
||||
// (display: standalone) run in their own browser process, and
|
||||
// mobile OSes kill that process when the app is swiped away —
|
||||
// deleting in-memory cookies and forcing an unexpected login.
|
||||
//
|
||||
// We use a long static value (1 year) instead of the key's
|
||||
// LifetimeSeconds because the server refreshes the key's
|
||||
// ExpiresAt on activity but does not re-set the cookie. Tying
|
||||
// MaxAge to the key lifetime would cause the cookie to expire
|
||||
// client-side even when the server-side key is still valid.
|
||||
//
|
||||
// Security is not affected: the server validates ExpiresAt on
|
||||
// every request regardless of the cookie's MaxAge.
|
||||
MaxAge: int((365 * 24 * time.Hour).Seconds()),
|
||||
}), &newkey, nil
|
||||
}
|
||||
|
||||
@@ -394,6 +394,55 @@ func TestSessionExpiry(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestSessionCookieMaxAge verifies that the session cookie is a persistent
|
||||
// cookie (has MaxAge set) rather than a session cookie. Standalone PWAs
|
||||
// run in their own browser process and mobile OSes purge in-memory
|
||||
// (session) cookies when that process is killed, so the cookie must be
|
||||
// persisted to disk.
|
||||
func TestSessionCookieMaxAge(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
client := coderdtest.New(t, nil)
|
||||
|
||||
// Create the first user (password-based login).
|
||||
req := codersdk.CreateFirstUserRequest{
|
||||
Email: "testuser@coder.com",
|
||||
Username: "testuser",
|
||||
Password: "SomeSecurePassword!",
|
||||
}
|
||||
_, err := client.CreateFirstUser(ctx, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Login via the raw HTTP endpoint so we can inspect the Set-Cookie header.
|
||||
loginURL, err := client.URL.Parse("/api/v2/users/login")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodPost, loginURL.String(), codersdk.LoginWithPasswordRequest{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusCreated, res.StatusCode)
|
||||
|
||||
oneYear := int((365 * 24 * time.Hour).Seconds())
|
||||
var found bool
|
||||
for _, cookie := range res.Cookies() {
|
||||
if cookie.Name == codersdk.SessionTokenCookie {
|
||||
// MaxAge should be set to a long value so the browser
|
||||
// persists the cookie to disk. The server handles real
|
||||
// expiry via the API key's ExpiresAt field.
|
||||
require.Equal(t, oneYear, cookie.MaxAge,
|
||||
"Session cookie MaxAge should be set to 1 year for disk persistence")
|
||||
found = true
|
||||
}
|
||||
}
|
||||
require.True(t, found, "session cookie should be present in login response")
|
||||
}
|
||||
|
||||
func TestAPIKey_OK(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user