-
Notifications
You must be signed in to change notification settings - Fork 411
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(firewall): handle firewall with multiple events involved
If the firewall catches some suspicious activity from an IP address and that IP address continues to perform the same action, new events are created for the same firewall side-effect.
- Loading branch information
Showing
7 changed files
with
937 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -249,6 +249,90 @@ describe('GET /api/v1/events/firewall/[id]', () => { | |
}); | ||
}); | ||
|
||
test('With two consecutive "firewall:block_users" events from the same IP', async () => { | ||
const usersRequestBuilder = new RequestBuilder('/api/v1/users'); | ||
const firewallRequestBuilder = new RequestBuilder(`/api/v1/events/firewall`); | ||
await firewallRequestBuilder.buildUser({ with: ['read:firewall'] }); | ||
|
||
// Create users | ||
const { responseBody: user1 } = await usersRequestBuilder.post({ | ||
username: 'firstUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
const { responseBody: user2 } = await usersRequestBuilder.post({ | ||
username: 'secondUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
const { response: user3Response } = await usersRequestBuilder.post({ | ||
username: 'thirdUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
|
||
expect(user3Response.status).toBe(429); | ||
|
||
const firstFirewallEvent = await orchestrator.getLastEvent(); | ||
|
||
const { response: user4Response } = await usersRequestBuilder.post({ | ||
username: 'fourthUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
|
||
expect(user4Response.status).toBe(429); | ||
|
||
const secondFirewallEvent = await orchestrator.getLastEvent(); | ||
|
||
expect(firstFirewallEvent.id).not.toBe(secondFirewallEvent.id); | ||
|
||
// Get firewall side-effects | ||
const { response: firstFirewallResponse, responseBody: firstFirewallResponseBody } = | ||
await firewallRequestBuilder.get(`/${firstFirewallEvent.id}`); | ||
const { response: secondFirewallResponse, responseBody: secondFirewallResponseBody } = | ||
await firewallRequestBuilder.get(`/${secondFirewallEvent.id}`); | ||
|
||
expect.soft(firstFirewallResponse.status).toBe(200); | ||
expect.soft(secondFirewallResponse.status).toBe(200); | ||
|
||
await usersRequestBuilder.setUser(user1); | ||
const { responseBody: user1AfterFirewall } = await usersRequestBuilder.get(`/${user1.username}`); | ||
|
||
await usersRequestBuilder.setUser(user2); | ||
const { responseBody: user2AfterFirewall } = await usersRequestBuilder.get(`/${user2.username}`); | ||
|
||
expect(firstFirewallResponseBody).toStrictEqual({ | ||
affected: { | ||
users: [user1AfterFirewall, user2AfterFirewall], | ||
}, | ||
events: [ | ||
{ | ||
created_at: firstFirewallEvent.created_at.toISOString(), | ||
id: firstFirewallEvent.id, | ||
metadata: { | ||
from_rule: 'create:user', | ||
users: [user1AfterFirewall.id, user2AfterFirewall.id], | ||
}, | ||
originator_user_id: null, | ||
type: 'firewall:block_users', | ||
}, | ||
{ | ||
created_at: secondFirewallEvent.created_at.toISOString(), | ||
id: secondFirewallEvent.id, | ||
metadata: { | ||
from_rule: 'create:user', | ||
users: [user1AfterFirewall.id, user2AfterFirewall.id], | ||
}, | ||
originator_user_id: null, | ||
type: 'firewall:block_users', | ||
}, | ||
], | ||
}); | ||
|
||
expect(firstFirewallResponseBody).toStrictEqual(secondFirewallResponseBody); | ||
}); | ||
|
||
test.each([ | ||
{ | ||
action: 'undo', | ||
|
@@ -335,6 +419,116 @@ describe('GET /api/v1/events/firewall/[id]', () => { | |
}); | ||
}); | ||
|
||
test('With two consecutive "firewall:block_users" events from the same IP reviewed', async () => { | ||
const usersRequestBuilder = new RequestBuilder('/api/v1/users'); | ||
const firewallRequestBuilder = new RequestBuilder(`/api/v1/events/firewall`); | ||
const firewallUser = await firewallRequestBuilder.buildUser({ with: ['read:firewall', 'review:firewall'] }); | ||
|
||
// Create users | ||
const { responseBody: user1 } = await usersRequestBuilder.post({ | ||
username: 'firstUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
const { responseBody: user2 } = await usersRequestBuilder.post({ | ||
username: 'secondUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
const { response: user3Response } = await usersRequestBuilder.post({ | ||
username: 'thirdUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
|
||
expect(user3Response.status).toBe(429); | ||
|
||
const firstFirewallEvent = await orchestrator.getLastEvent(); | ||
|
||
const { response: user4Response } = await usersRequestBuilder.post({ | ||
username: 'fourthUser', | ||
email: '[email protected]', | ||
password: 'password', | ||
}); | ||
|
||
expect(user4Response.status).toBe(429); | ||
|
||
const secondFirewallEvent = await orchestrator.getLastEvent(); | ||
|
||
expect(firstFirewallEvent.id).not.toBe(secondFirewallEvent.id); | ||
|
||
// Check firewall side-effect | ||
expect(firstFirewallEvent.type).toBe('firewall:block_users'); | ||
expect(firstFirewallEvent.metadata.users).toStrictEqual([user1.id, user2.id]); | ||
expect(secondFirewallEvent.type).toBe(firstFirewallEvent.type); | ||
expect(secondFirewallEvent.metadata.users).toStrictEqual(firstFirewallEvent.metadata.users); | ||
|
||
// Review firewall side-effect | ||
const reviewFirewallRequestBuilder = new RequestBuilder( | ||
`/api/v1/moderations/review_firewall/${firstFirewallEvent.id}`, | ||
); | ||
await reviewFirewallRequestBuilder.setUser(firewallUser); | ||
const { response: reviewResponse } = await reviewFirewallRequestBuilder.post({ action: 'confirm' }); | ||
expect(reviewResponse.status).toBe(200); | ||
|
||
// Get reviewed firewall event from first event id | ||
const reviewEvent = await orchestrator.getLastEvent(); | ||
|
||
const { response: responseFromFirstEvent, responseBody: responseBodyFromFirstEvent } = | ||
await firewallRequestBuilder.get(`/${firstFirewallEvent.id}`); | ||
|
||
expect.soft(responseFromFirstEvent.status).toBe(200); | ||
|
||
const user1AfterReview = await user.findOneById(user1.id, { withBalance: true }); | ||
const user2AfterReview = await user.findOneById(user2.id, { withBalance: true }); | ||
|
||
expect(responseBodyFromFirstEvent).toStrictEqual({ | ||
affected: { | ||
users: [mapUserData(user1AfterReview), mapUserData(user2AfterReview)], | ||
}, | ||
events: [ | ||
{ | ||
created_at: firstFirewallEvent.created_at.toISOString(), | ||
id: firstFirewallEvent.id, | ||
metadata: { | ||
from_rule: 'create:user', | ||
users: [user1.id, user2.id], | ||
}, | ||
originator_user_id: null, | ||
type: 'firewall:block_users', | ||
}, | ||
{ | ||
created_at: secondFirewallEvent.created_at.toISOString(), | ||
id: secondFirewallEvent.id, | ||
metadata: { | ||
from_rule: 'create:user', | ||
users: [user1.id, user2.id], | ||
}, | ||
originator_user_id: null, | ||
type: 'firewall:block_users', | ||
}, | ||
{ | ||
created_at: reviewEvent.created_at.toISOString(), | ||
id: reviewEvent.id, | ||
metadata: { | ||
original_event_id: firstFirewallEvent.id, | ||
users: [user1AfterReview.id, user2AfterReview.id], | ||
}, | ||
originator_user_id: firewallUser.id, | ||
type: 'moderation:block_users', | ||
}, | ||
], | ||
}); | ||
|
||
// Get reviewed firewall event from second event id | ||
const { response: responseFromSecondEvent, responseBody: responseBodyFromSecondEvent } = | ||
await firewallRequestBuilder.get(`/${secondFirewallEvent.id}`); | ||
|
||
expect(responseFromSecondEvent.status).toBe(200); | ||
|
||
expect(responseBodyFromSecondEvent).toStrictEqual(responseBodyFromFirstEvent); | ||
}); | ||
|
||
test('With a "firewall:block_contents:text_root" event', async () => { | ||
const usersRequestBuilder = new RequestBuilder(`/api/v1/users`); | ||
const firewallRequestBuilder = new RequestBuilder(`/api/v1/events/firewall`); | ||
|
Oops, something went wrong.