mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
fix(auth): preserve remember-me when password change re-issues the session (#1927)
This commit is contained in:
@@ -45,7 +45,10 @@ import { MailerService } from '../../src/nest/notifications/mailer/mailer.servic
|
||||
import { createTables } from '../../src/db/schema';
|
||||
import { runMigrations } from '../../src/db/migrations';
|
||||
import { createUser } from '../helpers/factories';
|
||||
import { resetRateLimits } from '../helpers/test-db';
|
||||
import { AuthModule } from '../../src/nest/auth/auth.module';
|
||||
import { AuthService } from '../../src/nest/auth/auth.service';
|
||||
import { SessionRenewalInterceptor } from '../../src/nest/auth/session-renewal.interceptor';
|
||||
import { DatabaseModule } from '../../src/nest/database/database.module';
|
||||
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
|
||||
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
|
||||
@@ -70,6 +73,9 @@ describe('Auth e2e (real auth guard + real service + real cookie service + temp
|
||||
// Mirror the production APP_PIPE (app.module.ts): DTO-typed bodies validate
|
||||
// by metatype, exactly as they do under buildApp().
|
||||
nest.useGlobalPipes(new ZodValidationPipe());
|
||||
// Mirror the production APP_INTERCEPTOR: sliding session renewal (#1927).
|
||||
// Inert for the harness's exp-less tokens and freshly issued logins.
|
||||
nest.useGlobalInterceptors(new SessionRenewalInterceptor(moduleRef.get(AuthService)));
|
||||
await nest.init();
|
||||
return nest;
|
||||
}
|
||||
@@ -154,6 +160,75 @@ describe('Auth e2e (real auth guard + real service + real cookie service + temp
|
||||
expect(cookie).not.toMatch(/Expires/i);
|
||||
}, 10000);
|
||||
|
||||
it('renews the session cookie when the token is past half its lifetime (#1927)', async () => {
|
||||
// 70% of a 24h token consumed → the interceptor re-issues the cookie.
|
||||
const res = await request(server)
|
||||
.get('/api/auth/me')
|
||||
.set('Cookie', sessionCookie(userId, 0, { lifetime: 86400, consumed: 60000 }));
|
||||
expect(res.status).toBe(200);
|
||||
const setCookie = (res.headers['set-cookie'] ?? []) as unknown as string[];
|
||||
const renewed = setCookie.find((c) => c.startsWith('trek_session='))!;
|
||||
expect(renewed).toBeTruthy();
|
||||
expect(renewed).toMatch(/Max-Age=86400/i);
|
||||
// The renewed token is a fresh full-lifetime session for the same user.
|
||||
const jwt = require('jsonwebtoken');
|
||||
const decoded = jwt.decode(/trek_session=([^;]+)/.exec(renewed)![1]) as { id: number; exp: number; iat: number };
|
||||
expect(decoded.id).toBe(userId);
|
||||
expect(decoded.exp - decoded.iat).toBe(86400);
|
||||
}, 10000);
|
||||
|
||||
it('renews a remembered session with the long window and a remember:false one as a session cookie (#1927)', async () => {
|
||||
const long = await request(server)
|
||||
.get('/api/auth/me')
|
||||
.set('Cookie', sessionCookie(userId, 0, { lifetime: 2592000, consumed: 1600000, remember: true }));
|
||||
expect(long.status).toBe(200);
|
||||
const longCookie = ((long.headers['set-cookie'] ?? []) as unknown as string[]).find((c) => c.startsWith('trek_session='))!;
|
||||
expect(longCookie).toMatch(/Max-Age=2592000/i);
|
||||
|
||||
const sess = await request(server)
|
||||
.get('/api/auth/me')
|
||||
.set('Cookie', sessionCookie(userId, 0, { lifetime: 86400, consumed: 60000, remember: false }));
|
||||
expect(sess.status).toBe(200);
|
||||
const sessCookie = ((sess.headers['set-cookie'] ?? []) as unknown as string[]).find((c) => c.startsWith('trek_session='))!;
|
||||
expect(sessCookie).not.toMatch(/Max-Age/i);
|
||||
expect(sessCookie).not.toMatch(/Expires/i);
|
||||
}, 10000);
|
||||
|
||||
it('does not renew a young session cookie (#1927)', async () => {
|
||||
const res = await request(server)
|
||||
.get('/api/auth/me')
|
||||
.set('Cookie', sessionCookie(userId, 0, { lifetime: 86400, consumed: 1000 }));
|
||||
expect(res.status).toBe(200);
|
||||
const setCookie = (res.headers['set-cookie'] ?? []) as unknown as string[];
|
||||
expect(setCookie.some((c) => c.startsWith('trek_session='))).toBe(false);
|
||||
}, 10000);
|
||||
|
||||
it('PUT /me/password preserves the remember choice on the re-issued cookie (#1927)', async () => {
|
||||
resetRateLimits(app); // earlier login cases share the same per-ip 'login' bucket
|
||||
const seeded = createUser(db as never, { username: 'pw-remember', email: 'pw-remember@example.test' });
|
||||
const login = await request(server)
|
||||
.post('/api/auth/login')
|
||||
.send({ email: seeded.user.email, password: seeded.password, remember_me: true });
|
||||
expect(login.status).toBe(200);
|
||||
const loginCookie = ((login.headers['set-cookie'] as unknown as string[]).find((c) => c.startsWith('trek_session=')))!;
|
||||
const sessionValue = /trek_session=([^;]+)/.exec(loginCookie)![1];
|
||||
|
||||
const change = await request(server)
|
||||
.put('/api/auth/me/password')
|
||||
.set('Cookie', `trek_session=${sessionValue}`)
|
||||
.send({ current_password: seeded.password, new_password: 'New1234!x' });
|
||||
expect(change.status).toBe(200);
|
||||
// The handler's re-issue is appended after any interceptor renewal — the
|
||||
// browser keeps the last trek_session cookie, so assert on that one.
|
||||
const setCookie = (change.headers['set-cookie'] ?? []) as unknown as string[];
|
||||
const finalCookie = setCookie.filter((c) => c.startsWith('trek_session=')).pop()!;
|
||||
expect(finalCookie).toMatch(/Max-Age=2592000/i);
|
||||
const jwt = require('jsonwebtoken');
|
||||
const decoded = jwt.decode(/trek_session=([^;]+)/.exec(finalCookie)![1]) as { remember?: boolean; exp: number; iat: number };
|
||||
expect(decoded.remember).toBe(true);
|
||||
expect(decoded.exp - decoded.iat).toBe(2592000);
|
||||
}, 10000);
|
||||
|
||||
it('POST /register creates the user, sets the cookie and audits user.register', async () => {
|
||||
const before = auditRows('user.register');
|
||||
const res = await request(server)
|
||||
|
||||
Reference in New Issue
Block a user