fix: preserve multi-instance session data on login/logout with tests …#1394
fix: preserve multi-instance session data on login/logout with tests …#1394Tony133 wants to merge 5 commits into
Conversation
…and improvements type safe in test file
b387ac4 to
ca88732
Compare
…ype-safe-test Signed-off-by: Antonio Tripodi <Tony133@users.noreply.github.com>
| request.session.set(this.key, undefined) | ||
| if (request.session.regenerate) { | ||
| await request.session.regenerate() | ||
| if (this.clearSessionOnLogin) { |
There was a problem hiding this comment.
I would still keep the regenerate as it was, considering that it is invoked in both the branches, and then transform the else in if(!this.clearSessionOnLogin)
There was a problem hiding this comment.
I would still keep the regenerate as it was, considering that it is invoked in both the branches, and then transform the else in if(!this.clearSessionOnLogin)
I tried it but unfortunately it breaks the tests "logging out with one instance should not log out the other instance" and "multiple registered instances (clearSessionOnLogin: false)"
The issue is that data() must be called before of regenerate(), because after regeneration the session is brand new and the existing data is already lost. Moving regenerate() before the condition makes it impossible to restore the other instance's session data.
So the if/else structure is actually necessary here to ensure we read existingData before calling regenerate().
We could refactor the plugin a bit, perhaps in another pull request
There was a problem hiding this comment.
Pull request overview
Fixes a session-isolation bug when multiple Authenticator instances share the same @fastify/session session and clearSessionOnLogin: false, where session.regenerate() was wiping unrelated passport namespaces during login/logout.
Changes:
- Preserve and restore existing session data around
session.regenerate()inSecureSessionManager.logIn()/logOut()whenclearSessionOnLoginisfalse. - Update many tests to satisfy stricter Fastify/TS typing for
preValidationhooks. - Bump
@fastify/secure-sessiondevDependency to^8.3.0.
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/session-managers/SecureSessionManager.ts |
Preserve existing session key/value pairs across regeneration to avoid cross-instance session data loss. |
test/multi-instance.test.ts |
Exercises the multi-authenticator scenario (login/logout isolation) and updates hook typing. |
test/session-isolation.test.ts |
Tightens session-isolation tests and updates hook typing. |
test/secure-session-manager.test.ts |
Improves type safety in unit tests for SecureSessionManager. |
test/passport.test.ts |
Updates route declarations to satisfy hook typing; continues broad passport behavior coverage. |
test/strategy.test.ts |
Updates preValidation typing for authentication hook usage. |
test/strategies-integration.test.ts |
Updates preValidation typing for OAuth integration strategy tests. |
test/session-serialization.test.ts |
Updates preValidation typing in serialization flow tests. |
test/multi-strategy-callback.test.ts |
Updates typing around multi-strategy authenticate callbacks used in preValidation. |
test/independent-strategy-instances.test.ts |
Updates preValidation typing for independent strategy instance tests. |
test/decorators.test.ts |
Updates preValidation typing for decorator behavior tests. |
test/csrf-fixation.test.ts |
Updates preValidation typing in CSRF fixation regression test. |
test/base-strategy.test.ts |
Updates preValidation typing in base-strategy coverage test. |
test/authorize.test.ts |
Updates preValidation typing for authorize() hook usage. |
test/authenticator.test.ts |
Updates preValidation typing in authenticator edge-case tests. |
test/authentication-route.test.ts |
Updates preValidation typing in authentication-route edge-case tests. |
package.json |
Bumps @fastify/secure-session to ^8.3.0. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| keepSessionInfoKeys.push(...Object.keys(request.session)) | ||
| } | ||
| await request.session.regenerate(keepSessionInfoKeys) | ||
| } else { | ||
| const existingData: Record<string, unknown> = (request.session as any).data?.() ?? {} |
| const existingData: Record<string, unknown> = (request.session as any).data?.() ?? {} | ||
| await request.session.regenerate() | ||
| for (const [key, value] of Object.entries(existingData)) { | ||
| request.session.set(key, value) | ||
| } |
| { | ||
| preValidation: fastifyPassport.authenticate('test', { | ||
| authInfo: false | ||
| }) as unknown as preValidationHookHandler | ||
| }, |
Proposal:
When using multiple
Authenticatorinstances withclearSessionOnLogin: false, logging in or out with one instance would inadvertently destroy the session data of other instances.This happened because
request.session.regenerate()was called without arguments in bothlogInandlogOut, which wipes the entire session — including passport keys belonging to other instances (e.g.passport-abeing lost when logging in viapassport-b).Changes Made:
In
SecureSessionManager:logIn: theelsebranch (triggered whenclearSessionOnLogin: false) calledrequest.session.regenerate()with no arguments, destroying all existing session data.logOut: similarly calledrequest.session.regenerate()unconditionally, logging out all instances instead of just the current one.When
clearSessionOnLoginisfalse, instead of callingregenerate()blindly:(request.session as any).data?.()before regeneratingrequest.session.regenerate()to get a new secure session IDrequest.session.set(), omitting only the key being logged out (inlogOut)Updated tests with better type checking
Update
@fastify/secure-sessiondependency in thepackage.jsonNote:
session.data()is used becauseObject.keys(request.session)only returns internal metadata fields (changed,deleted), not the actual session data. The method is available on both@fastify/sessionand@fastify/secure-session, though only declared in the types of the latter.This PR should close this PRs: