fix: sign out (#14376)

#### PR Dependency Tree


* **PR #14376** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Sign-out functionality now works in more scenarios, including when
headers are absent or duplicated.

* **Tests**
* Added test coverage for sign-out behavior across different header
configurations.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-02-05 23:39:26 +08:00
committed by GitHub
parent 8d201cd1ad
commit b2a495e885
2 changed files with 66 additions and 1 deletions

View File

@@ -202,6 +202,68 @@ test('should be able to sign out', async t => {
t.falsy(session); t.falsy(session);
}); });
test('should be able to sign out when csrf header is missing (compat)', async t => {
const { app } = t.context;
const u1 = await app.createUser('u1@affine.pro');
const signInRes = await supertest(app.getHttpServer())
.post('/api/auth/sign-in')
.send({ email: u1.email, password: u1.password })
.expect(200);
const cookies = parseCookies(signInRes);
const cookieHeader = Object.entries(cookies)
.map(([k, v]) => `${k}=${v}`)
.join('; ');
await supertest(app.getHttpServer())
.post('/api/auth/sign-out')
.set('Cookie', cookieHeader)
.expect(200);
const sessionRes = await supertest(app.getHttpServer())
.get('/api/auth/session')
.set('Cookie', cookieHeader)
.expect(200);
t.falsy(sessionRes.body.user);
});
test('should be able to sign out when duplicated csrf cookies exist', async t => {
const { app } = t.context;
const u1 = await app.createUser('u1@affine.pro');
const signInRes = await supertest(app.getHttpServer())
.post('/api/auth/sign-in')
.send({ email: u1.email, password: u1.password })
.expect(200);
const cookies = parseCookies(signInRes);
const csrf = cookies[AuthService.csrfCookieName];
const cookieHeader = [
`${AuthService.sessionCookieName}=${cookies[AuthService.sessionCookieName]}`,
`${AuthService.userCookieName}=${cookies[AuthService.userCookieName]}`,
`${AuthService.csrfCookieName}=${csrf}`,
`${AuthService.csrfCookieName}=${randomUUID()}`,
].join('; ');
await supertest(app.getHttpServer())
.post('/api/auth/sign-out')
.set('Cookie', cookieHeader)
.set('x-affine-csrf-token', csrf)
.expect(200);
const sessionRes = await supertest(app.getHttpServer())
.get('/api/auth/session')
.set('Cookie', cookieHeader)
.expect(200);
t.falsy(sessionRes.body.user);
});
test('should reject sign out when csrf token mismatched', async t => { test('should reject sign out when csrf token mismatched', async t => {
const { app } = t.context; const { app } = t.context;

View File

@@ -245,7 +245,10 @@ export class AuthController {
| string | string
| undefined; | undefined;
const csrfHeader = req.get('x-affine-csrf-token'); const csrfHeader = req.get('x-affine-csrf-token');
if (!csrfCookie || !csrfHeader || csrfCookie !== csrfHeader) { if (
csrfHeader && // optional for backward compatibility, drop after 0.25.0 outdated
(!csrfCookie || csrfCookie !== csrfHeader)
) {
throw new ActionForbidden(); throw new ActionForbidden();
} }