Skip to content

fix(memory): re-index session on Save so RegenerateID persists - #183

Merged
unknwon merged 7 commits into
flamego:mainfrom
unknwon:jc/memory-store-regenerate-id
Sep 17, 2026
Merged

unknwon merged 7 commits into
flamego:mainfrom
unknwon:jc/memory-store-regenerate-id

Conversation

@unknwon

@unknwon unknwon commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Description

The in-memory store keys sessions by the ID seen at Read time, and its Save is a no-op, relying on the session object staying live in the index. This breaks RegenerateID: after a handler rotates the session (e.g. storing OIDC state/nonce before redirecting to the IdP), the session keeps its data in memory but is no longer indexed under its new ID, so the next request's Read(newID) returns a fresh, empty session and the data is silently lost.

Every other store (file/postgres/mysql/redis/…) works because their Save writes keyed by sess.ID(); the memory store's no-op Save is the only inconsistent one.

Fix

memoryStore.Save now reconciles the index with the session's current ID:

  • Fast path: if the session is already indexed under its current ID, there is nothing to persist — this keeps the common no-rotation path a no-op.
  • Otherwise (the rotation case), drop any stale index entry pointing at this session object (pointer-equality scan, O(len(index)), only runs on this path), then re-key the map entry in place if the session is still in the heap, or heap.Push it if not.

LastAccessedAt is left untouched, so GC ordering is unaffected. The fix stays entirely inside memoryStore.Save; Read, Destroy, Touch, and the BaseSession/RegenerateID API are unchanged.

Test plan

  • TestMemoryStore_RegenerateID (in memory_test.go): rotates the ID and sets a value in one request, then reads back with the regenerated cookie in a second request, asserting the data survived and the old ID is no longer indexed. Fails on main, passes with this fix.
  • Full go test ./... passes.

View in Indent
Tag @indent to continue the conversation here.

unknwon and others added 7 commits September 15, 2026 17:06
The memory store keyed sessions by the ID seen at Read time and its Save was a
no-op. After RegenerateID (or Destroy plus RegenerateID, as session rotation
does) the session kept its data in memory but was no longer indexed under its
new ID, so the next Read returned a fresh empty session and the data was lost.

Make Save re-index the session under its current ID, dropping any stale entry
that still points at the same session object. The common no-rotation path stays
a fast no-op. Add a regression test covering RegenerateID across requests.
…nged

- Save now deletes any index entry still pointing at the rotated
  session object, so the old session ID is invalidated instead of
  remaining live in the index, and repeated rotations no longer leak
  index entries.
- BaseSession.RegenerateID marks the session as changed so a
  rotate-only request still hits Save (or the store's equivalent)
  instead of Touch, which silently dropped the rotation for the
  memory store and skipped persisting the new ID for other stores.
- Add TestMemoryStore_RegenerateIDOnly covering a rotate-only request
  and asserting the old ID is no longer indexed.
…ess time; drop added tests

- Save now deletes stale index entries pointing at the rotated session
  object, then re-keys the map entry in place when the session is still
  in the heap, avoiding heap.Remove/Push churn and leaving
  LastAccessedAt untouched.
- Revert the changed flag in BaseSession.RegenerateID; the fix stays
  entirely inside memoryStore.Save.
- Remove TestMemoryStore_RegenerateID and
  TestMemoryStore_RegenerateIDOnly.
@unknwon
unknwon marked this pull request as ready for review September 17, 2026 15:55
@unknwon
unknwon requested a review from a team as a code owner September 17, 2026 15:55
@unknwon
unknwon merged commit 62d1bab into flamego:main Sep 17, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant