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,
|
Value: sessionToken,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
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
|
}), &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) {
|
func TestAPIKey_OK(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user