Set SESSION_COOKIE_AGE to 399 days so logins survive longer than two weeks - #3587
Open
mjradwin wants to merge 1 commit into
Open
Set SESSION_COOKIE_AGE to 399 days so logins survive longer than two weeks#3587mjradwin wants to merge 1 commit into
mjradwin wants to merge 1 commit into
Conversation
SESSION_COOKIE_AGE was never set anywhere in the codebase, so Django's default of 1209600 seconds (2 weeks) governed how long a login lasted. Users were signed out roughly a fortnight after their last visit, well short of the ~400-day cookie lifetime browsers permit. Because SESSION_SAVE_EVERY_REQUEST is already True the window is rolling, so the logout lands two weeks after a user's *last* visit rather than two weeks after login -- which is why the symptom gets reported as "about a month" rather than "exactly 14 days". 399 days leaves a day of headroom under the 400-day cap that Chrome, Firefox and Safari apply to cookie lifetimes; a value at or above the cap would be silently truncated by the browser and put the cookie back out of sync with the server-side record. One setting covers both halves of the problem: Django feeds SESSION_COOKIE_AGE to the sessionid cookie's Max-Age *and* to django_session.expire_date, so the cookie cannot outlive the session record it points at. Tests are named session_settings_test.py, not test_session_settings.py: pytest.ini collects sefaria/system/tests/*_test.py, so a test_*.py name in this directory is silently never run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rt3gvV73jEpFCnuvU2hm1c
Contributor
There was a problem hiding this comment.
Pull request overview
This PR extends Django session lifetime by explicitly setting SESSION_COOKIE_AGE to 399 days (a rolling window due to SESSION_SAVE_EVERY_REQUEST = True) so users remain logged in longer, and adds tests to pin the intended behavior.
Changes:
- Set
SESSION_COOKIE_AGE = 399 * 24 * 60 * 60in the main Django settings to extend session lifetime. - Document the same setting in
local_settings_example.pyfor local/dev configuration parity. - Add a new pytest module asserting the configured session age, browser-cap constraint, rolling-session behavior, and cookie/server expiry derivation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
sefaria/settings.py |
Sets SESSION_COOKIE_AGE to 399 days and documents rationale/rolling behavior. |
sefaria/local_settings_example.py |
Adds the same SESSION_COOKIE_AGE example value and explanatory comments. |
sefaria/system/tests/session_settings_test.py |
Introduces tests that pin the session lifetime and its coupling to cookie/server-side expiry behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+75
to
+81
| from django.contrib.sessions.backends.db import SessionStore | ||
|
|
||
| assert SessionStore().get_expiry_age() == settings.SESSION_COOKIE_AGE | ||
|
|
||
| expected = timezone.now() + timedelta(seconds=settings.SESSION_COOKIE_AGE) | ||
| drift = abs((SessionStore().get_expiry_date() - expected).total_seconds()) | ||
| assert drift < 60 |
Comment on lines
+216
to
+220
| # the window rolls: every request pushes both out another 399 days. | ||
| # | ||
| # Django's own default is 1209600 (2 weeks), which is what we were silently | ||
| # running on before. | ||
| SESSION_COOKIE_AGE = 399 * 24 * 60 * 60 # 34,473,600 seconds |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
SESSION_COOKIE_AGEis not set anywhere in the codebase, so Django's default of1209600seconds (2 weeks) has been governing how long a login lasts. Users getsigned out roughly a fortnight after their last visit, far short of the ~400-day
cookie lifetime browsers permit.
Because
SESSION_SAVE_EVERY_REQUESTis alreadyTrue(settings.py), the windowis rolling: the logout lands two weeks after a user's last visit, not two
weeks after login. That's why the symptom tends to get reported as "about a
month" rather than "exactly 14 days" — the interval a user perceives is always
longer than the setting itself.
This sets the session lifetime to 399 days, leaving a day of headroom under the
400-day cap Chrome, Firefox and Safari apply to cookie lifetimes. A value at or
above the cap gets silently truncated by the browser, which would put the cookie
back out of sync with the server-side record.
One setting covers both halves of the problem: Django feeds
SESSION_COOKIE_AGEto the
sessionidcookie'sMax-Ageand todjango_session.expire_date, sothe cookie cannot outlive the session record it points at. A long-lived cookie
riding on a short-lived server record would log the user out anyway.
Code Changes
sefaria/settings.py— addsSESSION_COOKIE_AGE = 399 * 24 * 60 * 60(34,473,600 seconds) alongside the existing
SESSION_SAVE_EVERY_REQUEST.sefaria/local_settings_example.py— same value, documented for local dev.sefaria/system/tests/session_settings_test.py— new. Pins the value, pinsthat it stays under the 400-day browser cap, pins
SESSION_SAVE_EVERY_REQUEST,and pins the invariant that matters: the cookie's
Max-AgeandSessionStore.get_expiry_age()(what becomesdjango_session.expire_date)both derive from the same setting. The cookie test uses the
signed_cookiesengine via
override_settings, so no database is required.Notes
SESSION_SAVE_EVERY_REQUEST = Truemeans each logged-in user's next request rewrites both the cookie and the DB
row to +399 days. No data migration needed. Users already past their two weeks
are gone regardless.
django_sessionwill grow. Rows now live 399 days instead of 14, and thereis no
clearsessionsjob today, so expired rows are never purged. A companionPR adds that cronjob to the helm chart. The two PRs are independent and can be
accepted or rejected separately — this one is correct on its own, and the helm
one is correct on its own.
SESSION_ENGINEiscached_db, so sessions are alsowritten to Redis DB 0, now with a 399-day TTL instead of 14 days. Eviction is
safe (
cached_dbfalls back to the database), but memory use is worthwatching.
a long time on shared or public computers, and a stolen
sessionidstays validmuch longer. Django rotates the session key on login and on password change, so
those paths are covered, but the longer window is a real policy decision, not
just a config tweak.
defaultRedis cache in the helmchart has
"TIMEOUT": 60 * 60 * 24 * 30. That is not the session expiry —cached_db.save()passes an explicit per-key timeout that overrides the cachedefault, and on a cache miss
load()falls back to thedjango_sessionrow.It cannot log anyone out.
session_settings_test.py, nottest_session_settings.py.pytest.inicollectssefaria/system/tests/*_test.py, so atest_*.pyname inthat directory is never collected. Worth a separate look: this means
test_middleware.py,test_database.py,test_decorators.pyandtest_language_module_switching.pyin that same directory are currently notrunning in CI. Out of scope here, left untouched.
🤖 Co-authored with Claude Code