-
Notifications
You must be signed in to change notification settings - Fork 371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reverter ganhos de TabCoins em conteúdos pegos pelo firewall interno e outros ajustes #1638
base: main
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Importante contribuição, @Rafatcb! 💪💪💪
Com a adição de undoContentsTabcoins
, talvez seja melhor executar tudo dentro de uma transação, mas é preciso pensar melhor se isso faz sentido, pois, se der algum erro, não seria interessante reverter toda a transação.
Fiz comentários no código
infra/stored-procedures/firewall-create-content-text-child.pgsql
Outdated
Show resolved
Hide resolved
Acho que criar uma transação para cada iteração do Edit: Vendo melhor, a condição em que entrará na função |
2ca3788
to
c51bbff
Compare
@Rafatcb, aquele E a questão de diferentes usuários me fez pensar na possível situação de usuários legítimos que publicarem algo pela mesma rede pública, ao mesmo tempo, e somente o terceiro saberá que alguma coisa errada aconteceu. Os dois primeiros não saberão o que aconteceu com suas publicações. Será que é interessante verificar se alguma das publicações é de um usuário diferente do terceiro e enviar uma notificação por email? Um efeito colateral da implementação é que ficou mais complicado de reverter o efeito do firewall em casos de falso positivo. Antes, apesar de ter que fazer diretamente no banco, era só voltar as publicações de |
Criarei o teste 👍
Acho que isso pode ser interessante sim.
Ainda não pensei na melhor forma em fazer isso. Você já tem alguma ideia? Se seria criando uma função SQL ou uma |
Também não pensei. Acho que o ponto mais importante é como ficaria no banco de dados, já que não podemos excluir os balanços criados pelo evento de firewall. Provavelmente teria que criar novos balanços revertendo os do evento de reversão 😅 Talvez uma |
c51bbff
to
ea0bc53
Compare
FirewallPercebi que a alteração que fiz removia o conteúdo, mas não atualizava o A parte do e-mail acabou ficando bem "personalizada" de acordo com singular/plural e publicação/comentário. Para as publicações, consegui enviar o título, mas para os comentários não sei o que seria melhor para quem receber o e-mail ter uma noção, então passei apenas os IDs. Se você ter alguma sugestão de melhoria, pode falar. Não sei se o texto "Identificamos que você está tentando..." é adequado, se deveria mencionar "Identificamos muitas novas publicações no seu IP" ou algo parecido. Desfazer o efeito do firewallModifiquei o Algumas dúvidas:
|
ea0bc53
to
25cc2b8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Rafatcb, está muito bom, mas como ainda não consegui ver tudo, já vou deixar alguns comentários.
Sobre a refatoração em models/transacional
, aprovo todas as mudanças. Até por isso acho que talvez compense separar em um PR prévio a esse e já matar essa etapa.
Sobre a parte de email dentro de models/firewall
, será que não são coisas que deveriam estar no models/notification
?
Tem mais comentários no código 👍
Ok, vou fazer isso. Eu fiz aqui porque estava precisando copiar os estilos e percebi a repetição.
Eu havia começado a fazer no |
25cc2b8
to
5f29106
Compare
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enquanto estou criando o endpoint GET
, percebi algo que acho que está errado (filterOutput
no model
), então decidi aproveitar para comentar também outros pontos que poderiam ser diferentes.
tests/integration/api/v1/moderation/undo_firewall_effect/[event_id]/post.test.js
Outdated
Show resolved
Hide resolved
5f29106
to
af24608
Compare
Fiz o
|
Realizei as alterações propostas. Separei o |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fiz sugestões no código, e para facilitar eu já enviei um commit com as mudanças que sugeri. Se acatar, pode reorganizar os commits do PR antes do merge, se achar necessário.
Estando tudo certo, me avise para rodarmos as migrations e fazermos os testes finais em homologação 🤝
infra/stored-procedures/firewall-create-content-text-child.pgsql
Outdated
Show resolved
Hide resolved
Podemos deixar os commits assim 👍 Parece tudo certo, pode rodar as migrações. |
f405596
to
48162f4
Compare
Precisei alterar a migration para remover a versão antiga das funções antes de criar as novas, pois não foi possível atualizar as funções alterando o tipo de retorno delas. Agora foi tudo normalmente e já podemos testar em homologação. Inclusive já adicionei as features para os moderadores 👍 |
Criei alguns comentários em homologação, caí no rate limit duas vezes, mas nenhuma no firewall. Então, fui tentar criar contas, e consegui cair no firewall. Só não consegui continuar o teste porque não temos um |
Vi aqui que se alguém é pego pelo firewall e continua realizando a ação, novos eventos do firewall são criados. Eventos com test('With two "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).toEqual(429);
let allEvents = await event.findAll();
const firstFirewallEvent = allEvents.at(-1);
const { response: user4Response } = await usersRequestBuilder.post({
username: 'fourthUser',
email: '[email protected]',
password: 'password',
});
expect(user4Response.status).toEqual(429);
allEvents = await event.findAll();
const secondFirewallEvent = allEvents.at(-1);
expect(firstFirewallEvent.id).not.toEqual(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(firstFirewallResponse.status).toEqual(200);
expect(secondFirewallResponse.status).toEqual(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).toEqual({
affected: {
users: [user1AfterFirewall, user2AfterFirewall],
},
events: [
{
created_at: firstFirewallResponseBody.events[0].created_at,
id: firstFirewallEvent.id,
metadata: {
from_rule: 'create:user',
users: [user1AfterFirewall.id, user2AfterFirewall.id],
},
originator_user_id: null,
type: 'firewall:block_users',
},
],
});
expect(Date.parse(firstFirewallResponseBody.events[0].created_at)).not.toEqual(NaN);
expect(secondFirewallResponseBody).toEqual({
affected: {
users: [user1AfterFirewall, user2AfterFirewall],
},
events: [
{
created_at: secondFirewallResponseBody.events[0].created_at,
id: secondFirewallEvent.id,
metadata: {
from_rule: 'create:user',
users: [user1AfterFirewall.id, user2AfterFirewall.id],
},
originator_user_id: null,
type: 'firewall:block_users',
},
],
});
expect(Date.parse(secondFirewallResponseBody.events[0].created_at)).not.toEqual(NaN);
}); Esse não me parece o comportamento ideal, já que na hora de confirmar ou desfazer o evento, após um deles ser analisado, o outro não terá mais impacto. Faz sentido mudar as funções |
Pode ser que eu não tenha entendido sua dúvida, mas acredito que não faça sentido procurar outros eventos no momento que alguém cair no firewall. Mas na ação de desfazer ou confirmar o evento de firewall, aí sim, acho que é necessário buscar todos. Quando eu sugeri de não colocar o limite de 2 eventos no validador, foi justamente pensando nisso. 👍 |
|
||
function buildWhereClause(where, nextArgumentIndex = 1) { | ||
const columnMap = { | ||
types: 'type', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Não temos um padrão para isso, e acho que fui eu quem começou a adicionar o nome da propriedade no plural nos models para permitir buscar passando um array, mas como tanto aqui quanto no models/user
eu faço a verificação Array.isArray(value)
, pensei em tirar a possibilidade de passar o nome da propriedade no plural nesses dois models. Faz sentido?
Se sim, posso realizar essa modificação junto de outras (se surgirem na revisão).
Isso também pode ser tratado posteriormente no contents
, já que ele não está relacionado à este PR.
Criei a função |
INNER JOIN | ||
searched_event ON true | ||
WHERE | ||
events.metadata @> searched_event.metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do jeito que está testando, também passaria se fizesse a comparação simplesmente com =
:
WHERE
events.metadata = searched_event.metadata
Mas acredito que a intenção seja lidar com os casos em que nem todos os afetados serão os mesmos, por exemplo quando um conteúdo (ou usuário) for pego no primeiro evento, mas, por já ser mais antigo, não for pego pelo segundo. Nesse caso, para obter o mesmo resultado independentemente de qual dos eventos enviar na requisição, a comparação precisa ser com algo assim:
WHERE
events.metadata @> searched_event.metadata OR
events.metadata <@ searched_event.metadata
Obs.: Tem outras formas de comparar, mas teria que ter algo específico para conteúdos e outra comparação para usuários.
Acho que, para testar isso corretamente, teria que ser possível criar os eventos com data retroativa. Uma alternativa é modificar a query de criação do evento para usar a data e hora da lambda ao invés do banco de dados (como no published_at
) e usar vi.useFakeTimers
no teste.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fiquei em dúvida se vale a pena tantas alterações para testar isso. Veja o que fui percebendo:
- Ao passar um
new Date()
noINSERT
domodels/event
, não será possível testar com oRequestBuilder
porque o Next e o Vitest são processos diferentes, então mesmo modificando o tempo no teste, o registro usará o horário do computador. - Então eu poderia usar o
orchestrator
para criar os conteúdos, já que ele cria também o evento. Nessa situação, o horário salvo é o modificado no teste comuseFakeTimers()
, mas ocanRequest
não é chamado. - Seguindo, eu precisaria modificar o
orchestrator.createContent
para simular a chamada à regra adequada do firewall. - Por fim (imagino), precisaria modificar a função
firewall_create_content_text_child
e relacionadas para aceitar ocreatedAt
, que seria usado para a comparação no lugar deAND created_at > NOW() - INTERVAL '5 seconds'
.
Faz sentido? Compliquei alguma parte que não precisaria?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Ao passar um
new Date()
noINSERT
domodels/event
, não será possível testar com oRequestBuilder
porque o Next e o Vitest são processos diferentes, então mesmo modificando o tempo no teste, o registro usará o horário do computador.- Então eu poderia usar o
orchestrator
para criar os conteúdos, já que ele cria também o evento. Nessa situação, o horário salvo é o modificado no teste comuseFakeTimers()
, mas ocanRequest
não é chamado.
Para usar tempos falsos, de fato precisa criar o conteúdo pelo orchestrator.createContent
, mas só precisa falsificar a hora do primeiro (ou dos primeiros) evento(s). A requisição que vai cair no firewall deve ser direto pela API, onde pode ser usado o RequestBuilder
.
- Seguindo, eu precisaria modificar o
orchestrator.createContent
para simular a chamada à regra adequada do firewall.
Acho que não precisa (nem deveria fazer isso), mas entendo que isso seria para a criação do primeiro evento de firewall do teste. Deve ser suficiente adicionar um orchestrator.createFirewallEvent
para permitir criar um evento qualquer diretamente por ele. Com isso será possível simular um evento de firewall com mais (e com menos) conteúdos (ou usuários) do que o evento de firewall que será criado pela API, e daí é esperado que esse evento criado manualmente seja incluído nos eventos de firewall relacionados que são retornados. Acho que compensa fazer isso, pois a alternativa seria criar os dois eventos pela API, mas adicionando um intervalo de tempo entre as duas chamadas, o que aos poucos vai atrasando a execução da bateria de testes
Sobre o orchestrator.createFirewallEvent
, ele pode chamar um firewall.createEvent
, que pode ser criado para ser usado dentro de firewall/rules
, o que também parece ser uma refatoração simples.
- Por fim (imagino), precisaria modificar a função
firewall_create_content_text_child
e relacionadas para aceitar ocreatedAt
, que seria usado para a comparação no lugar deAND created_at > NOW() - INTERVAL '5 seconds'
.
Pensando rapidamente, acho que não precisa modificar essas funções.
Faz sentido? Compliquei alguma parte que não precisaria?
Talvez tenha complicado ao pensar em usar apenas o orchestrator.createContent
ou apenas o RequestBuilder
, mas usando os dois, cada um no momento adequado, acho que é uma mudança bem simples. No Triple A
dos testes (Arrange
, Act
, Assert
), o orchestrator.createContent
e orchestrator.createFirewallEvent
seriam usados no Arrange
e o RequestBuilder
no Act
.
No código atual em produção seria uma mudança apenas na query
de event.create
, por exemplo adicionando created_at
, $5
e new Date()
assim:
const query = {
text: `INSERT INTO events (type, originator_user_id, originator_ip, metadata, created_at)
VALUES($1, $2, $3, $4, $5) RETURNING *;`,
values: [
cleanObject.type,
cleanObject.originator_user_id,
cleanObject.originator_ip,
cleanObject.metadata,
new Date(),
],
};
Não testei o que estou sugerindo, então posso estar deixando escapar algo. Se preferir, posso implementar para descobrir se é tão simples como pensei. 🤝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aprendendofelipe Eu estava tentando implementar mas comecei a ficar confuso sobre o que você realmente propôs, então se puder implementar, agradeço.
A alteração que subi agora foi apenas para definir o status: published
de alguns testes que criavam publicações pelo orchestrator
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pode deixar que amanhã eu implemento 🤝
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As alterações que subi agora foram apenas um rebase
.
2614e19
to
2f0f13b
Compare
Improves firewall error message; changes the content status to `deleted` instead of `draft`; reverses the user's TabCoins earnings from content that was caught by the firewall; only updates content that has the status `published`.
Send an email explaining what happened to each affected user that did not trigger the event. If it is the root content firewall, the titles are sent in the body of the email, and if it is the comments firewall, the IDs are sent.
Returns the firewall `events` objects, along with the data affected by the event (`contents` and/or `users`)
…rewall Instead of saving the `status` of content affected by the firewall as `deleted`, save it as `firewall`. When undoing, returns the contents to their original status (not necessarily `published`).
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.
2f0f13b
to
a02b300
Compare
Mudanças realizadas
Mudança nas mensagens de erro
As mensagens de erro passaram a informar que algumas ações podem ter sido desfeitas.
Mudança no
status
dos conteúdos (firewall
ao invés dedraft
)Conforme mencionado em #1638 (comment), foi criado um novo
status
chamadofirewall
para representar um conteúdo que foi afetado pelo firewall, mesmo que o conteúdo estivesse com o statusdeleted
. Assim, é possível analisar pelos endpoints quais ações o usuário realizou que fizeram com que o firewall fosse ativado. O evento do firewall poderá ser analisado para confirmar (confirm
) ou reverter (undo
) o efeito, então o status do conteúdo seria respectivamentedeleted
ou o status anterior.Reversão de ganhos de TabCoins
A função
creditOrDebitTabCoins
foi chamada para reverter possíveis ganhos de TabCoins que o usuário teve com os conteúdos publicados que foram pegos pelo firewall interno.Endpoints
GET /api/v1/events/firewall/[id]
: obter dados sobre o evento do firewall e quais foram os conteúdos e usuários afetados.POST /api/v1/moderations/review_firewall/[id]
: confirmar ou desfazer um evento específico de firewall. A confirmação (confirm
) adiciona a featurenuked
ao usuário ou o statusdeleted
aos conteúdos envolvidos. A reversão (undo
) retorna os TabCoins removidos, restaura ostatus
dos conteúdos efeatures
dos usuários, a depender do tipo de firewall. Este endpoint retornará os dados atualizados contendo o evento original do firewall, o novo evento criado ao desfazer o firewall, os dados dos conteúdos e usuários afetados.Resolve #1463
Tipo de mudança
Checklist: