@hed-hog/core 0.0.87 → 0.0.89
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/challenge/challenge.service.d.ts +3 -0
- package/dist/challenge/challenge.service.d.ts.map +1 -1
- package/dist/challenge/challenge.service.js +52 -6
- package/dist/challenge/challenge.service.js.map +1 -1
- package/dist/profile/profile.controller.d.ts +13 -0
- package/dist/profile/profile.controller.d.ts.map +1 -1
- package/dist/profile/profile.controller.js +24 -0
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/profile/profile.service.d.ts +9 -0
- package/dist/profile/profile.service.d.ts.map +1 -1
- package/dist/profile/profile.service.js +15 -1
- package/dist/profile/profile.service.js.map +1 -1
- package/dist/task/task.service.d.ts +76 -0
- package/dist/task/task.service.d.ts.map +1 -1
- package/dist/task/task.service.js +127 -16
- package/dist/task/task.service.js.map +1 -1
- package/hedhog/data/route.yaml +12 -0
- package/package.json +2 -2
- package/src/challenge/challenge.service.ts +63 -8
- package/src/profile/profile.controller.ts +10 -0
- package/src/profile/profile.service.ts +19 -1
- package/src/task/task.service.ts +159 -21
package/src/task/task.service.ts
CHANGED
|
@@ -6,17 +6,53 @@ import { Cron, CronExpression } from '@nestjs/schedule';
|
|
|
6
6
|
export class TasksService {
|
|
7
7
|
private readonly logger = new Logger(TasksService.name);
|
|
8
8
|
|
|
9
|
-
constructor(private readonly prismaService: PrismaService) {
|
|
10
|
-
this.clear();
|
|
11
|
-
}
|
|
9
|
+
constructor(private readonly prismaService: PrismaService) {}
|
|
12
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Executa todas as tarefas de limpeza do banco de dados diariamente à meia-noite.
|
|
13
|
+
*
|
|
14
|
+
* As tarefas são executadas sequencialmente para evitar contenção excessiva no banco.
|
|
15
|
+
* Cada tarefa possui logging detalhado para monitoramento e troubleshooting.
|
|
16
|
+
*/
|
|
13
17
|
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
|
|
14
18
|
async clear() {
|
|
15
19
|
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
16
20
|
this.logger.verbose('Running scheduled cleanup tasks');
|
|
17
21
|
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
22
|
+
|
|
23
|
+
await this.clearExpiredMfaChallenges();
|
|
24
|
+
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
25
|
+
|
|
26
|
+
await this.clearExpiredSessions();
|
|
27
|
+
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
28
|
+
|
|
29
|
+
await this.clearExpiredUserIdentifierChallenges();
|
|
30
|
+
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
31
|
+
|
|
32
|
+
await this.clearUnverifiedMfaWithExpiredChallenges();
|
|
33
|
+
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
34
|
+
|
|
35
|
+
this.logger.verbose('Finished scheduled cleanup tasks');
|
|
36
|
+
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Limpa challenges de autenticação multifator (MFA) que já expiraram ou foram verificados.
|
|
41
|
+
*
|
|
42
|
+
* Remove registros da tabela user_mfa_challenge que:
|
|
43
|
+
* - Expiraram (expires_at < now())
|
|
44
|
+
* - Já foram verificados (verified_at IS NOT NULL)
|
|
45
|
+
*
|
|
46
|
+
* Por quê: Challenges expirados ou já utilizados não têm mais utilidade e ocupam
|
|
47
|
+
* espaço desnecessário no banco de dados. A remoção periódica mantém a performance
|
|
48
|
+
* das consultas e reduz o tamanho do banco.
|
|
49
|
+
*
|
|
50
|
+
* Limite: 20.000 registros por execução para evitar lock prolongado da tabela.
|
|
51
|
+
*/
|
|
52
|
+
private async clearExpiredMfaChallenges(): Promise<void> {
|
|
18
53
|
this.logger.verbose('Starting cleanup of expired/verified MFA challenges');
|
|
19
|
-
|
|
54
|
+
const startAt = Date.now();
|
|
55
|
+
|
|
20
56
|
await this.prismaService.$queryRaw`
|
|
21
57
|
WITH del AS (
|
|
22
58
|
SELECT user_mfa_id
|
|
@@ -30,27 +66,71 @@ export class TasksService {
|
|
|
30
66
|
USING del
|
|
31
67
|
WHERE c.user_mfa_id = del.user_mfa_id;
|
|
32
68
|
`;
|
|
33
|
-
|
|
34
|
-
this.logger.verbose(
|
|
69
|
+
|
|
70
|
+
this.logger.verbose(
|
|
71
|
+
`Expired/verified MFA challenges cleaned up in ${Date.now() - startAt}ms`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Limpa sessões de usuário expiradas e antigas.
|
|
77
|
+
*
|
|
78
|
+
* Remove registros da tabela user_session que:
|
|
79
|
+
* - Expiraram (expires_at < now())
|
|
80
|
+
* - Foram criadas há mais de 90 dias (created_at < now() - 90 days)
|
|
81
|
+
*
|
|
82
|
+
* Por quê: Sessões expiradas não podem mais ser utilizadas para autenticação.
|
|
83
|
+
* O critério adicional de 90 dias garante retenção de dados para auditoria/análise
|
|
84
|
+
* antes da exclusão definitiva. Isso permite investigar padrões de uso e
|
|
85
|
+
* possíveis problemas de segurança dentro de uma janela razoável.
|
|
86
|
+
*
|
|
87
|
+
* Limite: 20.000 registros por execução para evitar lock prolongado da tabela.
|
|
88
|
+
*/
|
|
89
|
+
private async clearExpiredSessions(): Promise<void> {
|
|
35
90
|
this.logger.verbose('Starting cleanup of expired sessions');
|
|
36
|
-
startAt = Date.now();
|
|
91
|
+
const startAt = Date.now();
|
|
92
|
+
|
|
37
93
|
await this.prismaService.$queryRaw`
|
|
38
94
|
WITH del AS (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
95
|
+
SELECT id
|
|
96
|
+
FROM user_session
|
|
97
|
+
WHERE expires_at < now()
|
|
98
|
+
AND created_at < (now() - interval '90 days')
|
|
99
|
+
ORDER BY id
|
|
100
|
+
LIMIT 20000
|
|
45
101
|
)
|
|
46
102
|
DELETE FROM user_session s
|
|
47
103
|
USING del
|
|
48
104
|
WHERE s.id = del.id;
|
|
49
105
|
`;
|
|
50
|
-
|
|
51
|
-
this.logger.verbose(
|
|
106
|
+
|
|
107
|
+
this.logger.verbose(
|
|
108
|
+
`Expired sessions cleaned up in ${Date.now() - startAt}ms`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Limpa challenges de verificação de identificadores de usuário (email, telefone, etc.)
|
|
114
|
+
* que expiraram e não foram verificados, removendo também os identificadores órfãos.
|
|
115
|
+
*
|
|
116
|
+
* Remove em cascata:
|
|
117
|
+
* 1. user_identifier_challenge: challenges expirados não verificados
|
|
118
|
+
* 2. user_identifier: identificadores que nunca foram verificados e perderam seus challenges
|
|
119
|
+
*
|
|
120
|
+
* Por quê: Quando um usuário tenta adicionar um novo email/telefone, é gerado um challenge
|
|
121
|
+
* para verificação. Se o usuário não verifica dentro do prazo, tanto o challenge quanto
|
|
122
|
+
* o identificador não verificado devem ser removidos para evitar:
|
|
123
|
+
* - Acúmulo de dados órfãos (identificadores que nunca serão usados)
|
|
124
|
+
* - Bloqueio indevido de emails/telefones (um email não verificado não deve impedir
|
|
125
|
+
* outro usuário de cadastrá-lo)
|
|
126
|
+
* - Confusão na interface (mostrar identificadores pendentes indefinidamente)
|
|
127
|
+
*
|
|
128
|
+
* Limite: 20.000 registros por execução para evitar lock prolongado das tabelas.
|
|
129
|
+
*/
|
|
130
|
+
private async clearExpiredUserIdentifierChallenges(): Promise<void> {
|
|
52
131
|
this.logger.verbose('Starting cleanup of expired user identifier challenges');
|
|
53
|
-
startAt = Date.now();
|
|
132
|
+
const startAt = Date.now();
|
|
133
|
+
|
|
54
134
|
await this.prismaService.$queryRaw`
|
|
55
135
|
WITH expired_challenges AS (
|
|
56
136
|
SELECT user_identifier_id
|
|
@@ -62,7 +142,8 @@ export class TasksService {
|
|
|
62
142
|
deleted_challenges AS (
|
|
63
143
|
DELETE FROM user_identifier_challenge c
|
|
64
144
|
USING expired_challenges
|
|
65
|
-
WHERE c.user_identifier_id = expired_challenges.user_identifier_id
|
|
145
|
+
WHERE c.user_identifier_id = expired_challenges.user_identifier_id
|
|
146
|
+
AND c.verified_at IS NULL
|
|
66
147
|
RETURNING c.user_identifier_id
|
|
67
148
|
)
|
|
68
149
|
DELETE FROM user_identifier ui
|
|
@@ -70,10 +151,67 @@ export class TasksService {
|
|
|
70
151
|
WHERE ui.id = deleted_challenges.user_identifier_id
|
|
71
152
|
AND ui.verified_at IS NULL;
|
|
72
153
|
`;
|
|
73
|
-
this.logger.verbose(`Expired user identifier challenges and unverified identifiers cleaned up in ${Date.now() - startAt}ms`);
|
|
74
|
-
this.logger.verbose('++++++++++++++++++++++++++++++++++');
|
|
75
154
|
|
|
76
|
-
this.logger.verbose(
|
|
77
|
-
|
|
155
|
+
this.logger.verbose(
|
|
156
|
+
`Expired user identifier challenges and unverified identifiers cleaned up in ${Date.now() - startAt}ms`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Limpa configurações de autenticação multifator (MFA) não verificadas cujos
|
|
162
|
+
* challenges de verificação expiraram, removendo também os challenges associados.
|
|
163
|
+
*
|
|
164
|
+
* Remove em cascata:
|
|
165
|
+
* 1. user_mfa_challenge: todos os challenges associados ao MFA não verificado
|
|
166
|
+
* 2. user_mfa: configuração de MFA que nunca foi verificada e não possui challenges válidos
|
|
167
|
+
*
|
|
168
|
+
* Por quê: Quando um usuário configura MFA (TOTP, SMS, etc.), é gerado um challenge
|
|
169
|
+
* para verificar que o método funciona. Se o usuário:
|
|
170
|
+
* - Abandonou o processo de configuração (não verificou)
|
|
171
|
+
* - Todos os challenges expiraram (não há tentativas válidas pendentes)
|
|
172
|
+
*
|
|
173
|
+
* Então a configuração incompleta deve ser removida porque:
|
|
174
|
+
* - Não pode ser utilizada para autenticação (nunca foi verificada)
|
|
175
|
+
* - Ocupa espaço desnecessário no banco
|
|
176
|
+
* - Pode causar confusão na interface (métodos MFA "pendentes" indefinidamente)
|
|
177
|
+
* - Permite ao usuário reconfigurar o método sem conflitos
|
|
178
|
+
*
|
|
179
|
+
* Limite: 20.000 registros por execução para evitar lock prolongado das tabelas.
|
|
180
|
+
*/
|
|
181
|
+
private async clearUnverifiedMfaWithExpiredChallenges(): Promise<void> {
|
|
182
|
+
this.logger.verbose(
|
|
183
|
+
'Starting cleanup of unverified MFA with expired challenges',
|
|
184
|
+
);
|
|
185
|
+
const startAt = Date.now();
|
|
186
|
+
|
|
187
|
+
await this.prismaService.$queryRaw`
|
|
188
|
+
WITH unverified_mfa AS (
|
|
189
|
+
SELECT um.id
|
|
190
|
+
FROM user_mfa um
|
|
191
|
+
WHERE um.verified_at IS NULL
|
|
192
|
+
AND NOT EXISTS (
|
|
193
|
+
SELECT 1
|
|
194
|
+
FROM user_mfa_challenge umc
|
|
195
|
+
WHERE umc.user_mfa_id = um.id
|
|
196
|
+
AND umc.expires_at >= now()
|
|
197
|
+
)
|
|
198
|
+
LIMIT 20000
|
|
199
|
+
),
|
|
200
|
+
deleted_challenges AS (
|
|
201
|
+
DELETE FROM user_mfa_challenge umc
|
|
202
|
+
USING unverified_mfa
|
|
203
|
+
WHERE umc.user_mfa_id = unverified_mfa.id
|
|
204
|
+
RETURNING umc.user_mfa_id
|
|
205
|
+
)
|
|
206
|
+
DELETE FROM user_mfa um
|
|
207
|
+
USING unverified_mfa
|
|
208
|
+
WHERE um.id = unverified_mfa.id;
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
this.logger.verbose(
|
|
212
|
+
`Unverified MFA with expired challenges cleaned up in ${Date.now() - startAt}ms`,
|
|
213
|
+
);
|
|
78
214
|
}
|
|
215
|
+
|
|
216
|
+
|
|
79
217
|
}
|