Skip to content

Commit 6b23f4f

Browse files
[CHAT-1172] Ban by IP feature (#485)
1 parent b74fe45 commit 6b23f4f

File tree

5 files changed

+148
-5
lines changed

5 files changed

+148
-5
lines changed

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1080,7 +1080,7 @@ export class StreamChat<
10801080
tokenManager: client.tokenManager,
10811081
user: this._user,
10821082
authType: this.getAuthType(),
1083-
userAgent: this._userAgent(),
1083+
userAgent: this.getUserAgent(),
10841084
apiKey: this.key,
10851085
recoverCallback: this.recoverState,
10861086
messageCallback: this.handleEvent,

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ export type UserResponse<UserType = UnknownType> = User<UserType> & {
589589
*/
590590

591591
export type BanUserOptions<UserType = UnknownType> = UnBanUserOptions & {
592+
ip_ban?: boolean;
592593
reason?: string;
593594
timeout?: number;
594595
user?: UserResponse<UserType>;

test/integration/ban_by_ip.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
createUsers,
3+
createUserToken,
4+
expectHTTPErrorCode,
5+
getTestClient,
6+
} from './utils';
7+
import chai from 'chai';
8+
import chaiAsPromised from 'chai-as-promised';
9+
import { v4 as uuidv4 } from 'uuid';
10+
11+
chai.use(chaiAsPromised);
12+
13+
function randomIP() {
14+
return (
15+
Math.floor(Math.random() * 255) +
16+
1 +
17+
'.' +
18+
Math.floor(Math.random() * 255) +
19+
'.' +
20+
Math.floor(Math.random() * 255) +
21+
'.' +
22+
Math.floor(Math.random() * 255)
23+
);
24+
}
25+
26+
// mockIP appends X-Forwarded-For to the user agent string
27+
function mockIP(client, ip) {
28+
client.setUserAgent(
29+
client.getUserAgent() + `&X-Forwarded-For=${ip}&X-Forwarded-Port=80`,
30+
);
31+
}
32+
33+
describe('ban user by ip', () => {
34+
const serverClient = getTestClient(true);
35+
const adminUser = {
36+
id: uuidv4(),
37+
};
38+
39+
const ip1 = randomIP();
40+
const ip2 = randomIP();
41+
const ip3 = randomIP();
42+
43+
const tommasoID = `tommaso-${uuidv4()}`;
44+
const thierryID = `thierry-${uuidv4()}`;
45+
const tommasoChannelId = `test-channels-${uuidv4()}`;
46+
const thierryChannelId = `test-channels-${uuidv4()}`;
47+
const tommasoToken = createUserToken(tommasoID);
48+
const thierryToken = createUserToken(thierryID);
49+
const tommasoClient1 = getTestClient();
50+
mockIP(tommasoClient1, ip1);
51+
const tommasoClient2 = getTestClient();
52+
mockIP(tommasoClient2, ip2);
53+
const thierryClient1 = getTestClient();
54+
mockIP(thierryClient1, ip1);
55+
const thierryClient2 = getTestClient();
56+
mockIP(thierryClient2, ip3);
57+
58+
before(async () => {
59+
await createUsers([adminUser.id]);
60+
await tommasoClient1.setUser({ id: tommasoID }, tommasoToken);
61+
await thierryClient1.setUser({ id: thierryID }, thierryToken);
62+
});
63+
64+
it('tommaso and thierry create channels', async () => {
65+
await tommasoClient1.channel('messaging', tommasoChannelId).watch();
66+
await thierryClient1.channel('messaging', thierryChannelId).watch();
67+
});
68+
69+
it('tommasso1 is not banned yet', async () => {
70+
await tommasoClient1
71+
.channel('messaging', tommasoChannelId)
72+
.sendMessage({ text: 'I am not banned yet' });
73+
});
74+
75+
it('ban tommaso by IP', async () => {
76+
await serverClient.banUser(tommasoID, {
77+
user_id: adminUser.id,
78+
ip_ban: true,
79+
});
80+
});
81+
82+
it('tommasso1 is banned', async () => {
83+
await expectHTTPErrorCode(
84+
403,
85+
tommasoClient1
86+
.channel('messaging', tommasoChannelId)
87+
.sendMessage({ text: 'I am banned' }),
88+
);
89+
});
90+
91+
it('thierry1 is banned because he has same ip', async () => {
92+
await expectHTTPErrorCode(
93+
403,
94+
thierryClient1
95+
.channel('messaging', thierryChannelId)
96+
.sendMessage({ text: 'I am banned' }),
97+
);
98+
});
99+
100+
it('tommaso and thierry switch IP addresses', async () => {
101+
await tommasoClient2.setUser({ id: tommasoID }, tommasoToken);
102+
await thierryClient2.setUser({ id: thierryID }, thierryToken);
103+
await tommasoClient2.channel('messaging', tommasoChannelId).watch();
104+
await thierryClient2.channel('messaging', thierryChannelId).watch();
105+
});
106+
107+
it('tommasso2 is banned', async () => {
108+
await expectHTTPErrorCode(
109+
403,
110+
tommasoClient2
111+
.channel('messaging', tommasoChannelId)
112+
.sendMessage({ text: 'I am banned' }),
113+
);
114+
});
115+
116+
it('thierry2 is not banned', async () => {
117+
await thierryClient2
118+
.channel('messaging', thierryChannelId)
119+
.sendMessage({ text: 'I am not banned' });
120+
});
121+
122+
it('unban tommaso', async () => {
123+
await serverClient.unbanUser(tommasoID);
124+
});
125+
126+
it('no one is banned', async () => {
127+
await tommasoClient1
128+
.channel('messaging', tommasoChannelId)
129+
.sendMessage({ text: 'I am not banned' });
130+
await tommasoClient2
131+
.channel('messaging', tommasoChannelId)
132+
.sendMessage({ text: 'I am not banned' });
133+
await thierryClient1
134+
.channel('messaging', thierryChannelId)
135+
.sendMessage({ text: 'I am not banned' });
136+
await thierryClient2
137+
.channel('messaging', thierryChannelId)
138+
.sendMessage({ text: 'I am not banned' });
139+
});
140+
});

test/integration/channels.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,10 @@ describe('Channels - members', function () {
462462
{ state: true },
463463
);
464464
expect(channels.length).to.be.equal(2);
465-
expect(channels[0].data.last_message_at).to.be.equal(last_message);
465+
// parse date to avoid precision issues
466+
expect(Date.parse(channels[0].data.last_message_at)).to.be.equal(
467+
Date.parse(last_message),
468+
);
466469
}
467470
channel1Messages.push(results[0].message);
468471
channel2Messages.push(msg2.message);

test/integration/utils.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ export function getTestClientForUser2(userID, status, options) {
2121

2222
export async function getTestClientForUser(userID, status, options) {
2323
const client = getTestClient(false);
24-
const health = await client.setUser(
24+
client.health = await client.setUser(
2525
{ id: userID, status, ...options },
2626
createUserToken(userID),
2727
);
28-
client.health = health;
2928
return client;
3029
}
3130

@@ -93,7 +92,7 @@ export async function createUsers(userIDs) {
9392
for (const userID of userIDs) {
9493
users.push({ id: userID });
9594
}
96-
return await serverClient.updateUsers(users);
95+
return await serverClient.upsertUsers(users);
9796
}
9897

9998
export function createEventWaiter(clientOrChannel, eventTypes) {

0 commit comments

Comments
 (0)