fix(api): support shared session cookie domain
Build & Deploy / Build & Push Docker Image (push) Successful in 2m49s
Test / Type Check (all packages) (push) Successful in 55s
Build & Deploy / Deploy to VPS (push) Successful in 3s
Test / API Unit Tests (push) Successful in 48s
Test / Homepage Unit Tests (push) Successful in 46s
Test / Storefront Unit Tests (push) Successful in 47s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Successful in 40s
Test / API Integration Tests (push) Successful in 59s

This commit is contained in:
root
2026-07-02 15:10:40 -04:00
parent a947e48c49
commit ad5f5ebab7
4 changed files with 80 additions and 14 deletions
@@ -0,0 +1,64 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { clearSessionCookie, setSessionCookie } from './sessionCookies'
const originalEnv = { ...process.env }
function mockResponse() {
return {
cookie: vi.fn(),
clearCookie: vi.fn(),
}
}
afterEach(() => {
process.env = { ...originalEnv }
})
describe('session cookies', () => {
it('sets employee session cookies for the configured parent domain', () => {
process.env.NODE_ENV = 'production'
process.env.SESSION_COOKIE_DOMAIN = '.rentaldrivego.ma'
const res = mockResponse()
setSessionCookie(res as never, 'employee', 'token-123', 1000)
expect(res.cookie).toHaveBeenCalledWith('employee_session', 'token-123', {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
domain: '.rentaldrivego.ma',
maxAge: 1000,
})
})
it('clears cookies with the same domain attributes used when setting them', () => {
process.env.NODE_ENV = 'production'
process.env.SESSION_COOKIE_DOMAIN = '.rentaldrivego.ma'
const res = mockResponse()
clearSessionCookie(res as never, 'employee')
expect(res.clearCookie).toHaveBeenCalledWith('employee_session', {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
domain: '.rentaldrivego.ma',
})
})
it('omits the domain option when no shared cookie domain is configured', () => {
delete process.env.SESSION_COOKIE_DOMAIN
const res = mockResponse()
setSessionCookie(res as never, 'employee', 'token-123')
expect(res.cookie).toHaveBeenCalledWith('employee_session', 'token-123', {
httpOnly: true,
secure: false,
sameSite: 'lax',
path: '/',
})
})
})
+13 -11
View File
@@ -1,4 +1,4 @@
import type { Response } from 'express'
import type { CookieOptions, Response } from 'express'
import type { ActorType } from './tokens'
const COOKIE_NAMES: Record<ActorType, string> = {
@@ -11,21 +11,23 @@ export function getSessionCookieName(actorType: ActorType) {
return COOKIE_NAMES[actorType]
}
export function setSessionCookie(res: Response, actorType: ActorType, token: string, maxAgeMs?: number) {
res.cookie(getSessionCookieName(actorType), token, {
function getSessionCookieOptions(actorType: ActorType, maxAgeMs?: number): CookieOptions {
const domain = process.env.SESSION_COOKIE_DOMAIN?.trim()
return {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: actorType === 'admin' ? 'strict' : 'lax',
path: '/',
maxAge: maxAgeMs,
})
...(domain ? { domain } : {}),
...(maxAgeMs !== undefined ? { maxAge: maxAgeMs } : {}),
}
}
export function setSessionCookie(res: Response, actorType: ActorType, token: string, maxAgeMs?: number) {
res.cookie(getSessionCookieName(actorType), token, getSessionCookieOptions(actorType, maxAgeMs))
}
export function clearSessionCookie(res: Response, actorType: ActorType) {
res.clearCookie(getSessionCookieName(actorType), {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: actorType === 'admin' ? 'strict' : 'lax',
path: '/',
})
res.clearCookie(getSessionCookieName(actorType), getSessionCookieOptions(actorType))
}