Skip to content

Commit 698dca5

Browse files
committed
harden notifications websocket cors
1 parent 03cd3f1 commit 698dca5

3 files changed

Lines changed: 31 additions & 2 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ REDIS_URL="redis://localhost:6379"
1818
# JWT
1919
JWT_SECRET=change-me-in-production
2020

21+
# WebSocket CORS allowlist for /notifications (comma-separated origins)
22+
# JWT auth uses the Socket.IO handshake auth payload, so credentialed CORS is disabled.
23+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
24+
2125
# Admin wallet allowlist (comma-separated Stellar public keys)
2226
# On auth, any wallet in this list will receive role=ADMIN
2327
ADMIN_WALLETS=
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { parseAllowedOrigins } from './notifications.gateway';
2+
3+
describe('parseAllowedOrigins', () => {
4+
it('returns an empty allowlist when the env var is missing', () => {
5+
expect(parseAllowedOrigins(undefined)).toEqual([]);
6+
});
7+
8+
it('splits and trims comma-separated origins', () => {
9+
expect(
10+
parseAllowedOrigins('http://localhost:3000, https://app.example.com ,'),
11+
).toEqual(['http://localhost:3000', 'https://app.example.com']);
12+
});
13+
});

src/notifications/notifications.gateway.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ import { JwtService } from '@nestjs/jwt';
1010
import { ConfigService } from '@nestjs/config';
1111
import type { Server, Socket } from 'socket.io';
1212

13+
export function parseAllowedOrigins(value: string | undefined): string[] {
14+
if (!value) {
15+
return [];
16+
}
17+
18+
return value
19+
.split(',')
20+
.map((origin) => origin.trim())
21+
.filter(Boolean);
22+
}
23+
1324
/**
1425
* WebSocket gateway providing real-time notification events.
1526
*
@@ -29,8 +40,9 @@ import type { Server, Socket } from 'socket.io';
2940
@WebSocketGateway({
3041
namespace: '/notifications',
3142
cors: {
32-
origin: '*',
33-
credentials: true,
43+
// Socket.IO uses JWTs from the handshake auth payload, so credentialed CORS is unnecessary.
44+
origin: parseAllowedOrigins(process.env.ALLOWED_ORIGINS),
45+
credentials: false,
3446
},
3547
})
3648
export class NotificationsGateway

0 commit comments

Comments
 (0)