Skip to content

Commit 542e1c5

Browse files
committed
chore: fix riot integration and error handling
1 parent a9667d2 commit 542e1c5

File tree

5 files changed

+345
-24
lines changed

5 files changed

+345
-24
lines changed

PLAYER_IMPORT_SECURITY.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# 🔒 Player Import Security - Proteção contra Importação Duplicada
2+
3+
## Visão Geral
4+
5+
O sistema implementa uma proteção rigorosa contra a importação de jogadores que já pertencem a outras organizações. Esta é uma medida de segurança importante para:
6+
7+
1. **Prevenir conflitos de dados** - Um jogador só pode estar ativo em uma organização por vez
8+
2. **Proteger a privacidade** - Evitar que organizações vejam/importem jogadores de competidores
9+
3. **Compliance** - Registrar tentativas suspeitas para auditoria
10+
11+
## Como Funciona
12+
13+
### Validação no Import
14+
15+
Quando uma organização tenta importar um jogador:
16+
17+
1. ✅ Sistema busca o jogador na Riot API
18+
2. ✅ Verifica se o `riot_puuid` já existe no banco de dados
19+
3. ✅ Se existir em **outra organização**, bloqueia a importação
20+
4. ✅ Registra a tentativa no `AuditLog` para auditoria
21+
5. ✅ Retorna erro **403 Forbidden** com mensagem clara
22+
23+
### Mensagem de Erro
24+
25+
```
26+
This player is already registered in another organization.
27+
Players can only be associated with one organization at a time.
28+
Attempting to import players from other organizations may result
29+
in account restrictions.
30+
```
31+
32+
### Status HTTP
33+
34+
- **403 Forbidden** - Jogador pertence a outra organização
35+
- **404 Not Found** - Jogador não encontrado na Riot API
36+
- **422 Unprocessable Entity** - Formato inválido de Riot ID
37+
38+
## Logs de Auditoria
39+
40+
Cada tentativa bloqueada gera um registro em `audit_logs` com:
41+
42+
```ruby
43+
{
44+
organization: <organização que tentou importar>,
45+
action: 'import_attempt_blocked',
46+
entity_type: 'Player',
47+
entity_id: <id do player existente>,
48+
new_values: {
49+
attempted_summoner_name: "PlayerName#TAG",
50+
actual_summoner_name: "PlayerName#TAG",
51+
owner_organization_id: "uuid",
52+
owner_organization_name: "Org Name",
53+
reason: "Player already belongs to another organization",
54+
puuid: "riot_puuid"
55+
}
56+
}
57+
```
58+
59+
## Logs de Sistema
60+
61+
Tentativas bloqueadas também geram logs de WARNING:
62+
63+
```
64+
⚠️ SECURITY: Attempt to import player <name> (PUUID: <puuid>)
65+
that belongs to organization <org_name> by organization <attempting_org>
66+
```
67+
68+
## Código de Erro
69+
70+
```ruby
71+
code: 'PLAYER_BELONGS_TO_OTHER_ORGANIZATION'
72+
```
73+
74+
## Exemplo de Uso
75+
76+
### Request (Frontend)
77+
```javascript
78+
await playersService.importFromRiot({
79+
summoner_name: "PlayerName#TAG",
80+
role: "mid"
81+
});
82+
```
83+
84+
### Response (Erro - Jogador já existe)
85+
```json
86+
{
87+
"error": {
88+
"code": "PLAYER_BELONGS_TO_OTHER_ORGANIZATION",
89+
"message": "This player is already registered in another organization. Players can only be associated with one organization at a time. Attempting to import players from other organizations may result in account restrictions.",
90+
"status": 403
91+
}
92+
}
93+
```
94+
95+
## Implicações para Compliance
96+
97+
- ✅ Todas as tentativas são registradas com timestamp
98+
- ✅ Informações da organização que tentou são armazenadas
99+
- ✅ PUUID do jogador é registrado para rastreamento
100+
- ✅ Logs podem ser usados para identificar padrões suspeitos
101+
102+
## Notas Importantes
103+
104+
1. **Unicidade Global**: O `riot_puuid` é único globalmente, não por organização
105+
2. **Auditoria Completa**: Todas as tentativas são registradas, mesmo as bloqueadas
106+
3. **Privacidade**: O sistema NÃO revela qual organização possui o jogador (apenas registra internamente)
107+
4. **Ações Futuras**: Tentativas repetidas podem resultar em bloqueio de conta (a implementar)
108+
109+
## Implementação Técnica
110+
111+
### Arquivos Modificados
112+
113+
1. `app/modules/players/services/riot_sync_service.rb:73-97`
114+
- Validação antes de criar o player
115+
- Log de segurança
116+
- Criação de audit log
117+
118+
2. `app/modules/players/controllers/players_controller.rb:357-358`
119+
- Mapeamento de código de erro para status HTTP 403
120+
121+
## Próximos Passos Sugeridos
122+
123+
- [ ] Implementar rate limiting para tentativas de import
124+
- [ ] Alertas para administradores após X tentativas bloqueadas
125+
- [ ] Dashboard de segurança mostrando tentativas suspeitas
126+
- [ ] Bloqueio temporário de conta após múltiplas tentativas
127+
128+
---
129+
130+
**Última atualização**: 2025-10-25
131+
**Versão**: 1.0.0

app/modules/players/controllers/players_controller.rb

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,10 +350,23 @@ def handle_import_success(result)
350350

351351
# Handle import error
352352
def handle_import_error(result)
353+
# Determine appropriate HTTP status based on error code
354+
status = case result[:code]
355+
when 'PLAYER_NOT_FOUND', 'INVALID_FORMAT'
356+
:not_found
357+
when 'PLAYER_BELONGS_TO_OTHER_ORGANIZATION'
358+
:forbidden
359+
when 'RIOT_API_ERROR'
360+
# Check if it's a server error (5xx) or rate limit
361+
result[:status_code] && result[:status_code] >= 500 ? :bad_gateway : :service_unavailable
362+
else
363+
:service_unavailable
364+
end
365+
353366
render_error(
354-
message: "Failed to import from Riot API: #{result[:error]}",
367+
message: result[:error] || "Failed to import from Riot API",
355368
code: result[:code] || 'IMPORT_ERROR',
356-
status: :service_unavailable
369+
status: status
357370
)
358371
end
359372
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# frozen_string_literal: true
2+
3+
module Players
4+
module Services
5+
# Custom exception for Riot API errors with status code tracking
6+
class RiotApiError < StandardError
7+
attr_accessor :status_code, :response_body
8+
9+
def initialize(message = nil)
10+
super(message)
11+
@status_code = nil
12+
@response_body = nil
13+
end
14+
15+
def not_found?
16+
status_code == 404
17+
end
18+
19+
def rate_limited?
20+
status_code == 429
21+
end
22+
23+
def server_error?
24+
status_code >= 500
25+
end
26+
end
27+
end
28+
end

0 commit comments

Comments
 (0)