specleap-framework 2.1.0 → 2.1.5
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/.agents/backend.md +3 -3
- package/.agents/frontend.md +2 -2
- package/.agents/producto.md +9 -11
- package/.claude/hooks/spec-guard.sh +132 -0
- package/.claude/settings.json.template +15 -0
- package/.clinerules +3 -3
- package/.coderabbit.yaml +2 -4
- package/.commands/compliance.md +89 -0
- package/.commands/inicio.md +15 -15
- package/.commands/nuevo/README.md +2 -2
- package/.commands/planificar.md +1 -1
- package/.continue/rules/04-git-workflow.md +5 -5
- package/.continuerules +3 -4
- package/.cursorrules +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/.specleap/i18n/en.json +177 -0
- package/.specleap/i18n/es.json +177 -0
- package/.specleap/i18n.sh +63 -0
- package/CHANGELOG.md +276 -0
- package/CLAUDE.md +54 -13
- package/README.md +169 -528
- package/SETUP.md +16 -13
- package/openspec/INDEX.md +53 -0
- package/openspec/README.md +104 -0
- package/openspec/SPEC-FORMAT.md +168 -0
- package/openspec/changes/.gitkeep +0 -0
- package/openspec/cli/COMMAND_REFERENCE.md +817 -0
- package/openspec/cli/README.md +189 -0
- package/openspec/cli/apply.sh +229 -0
- package/openspec/cli/archive.sh +240 -0
- package/openspec/cli/code-review.sh +207 -0
- package/openspec/cli/common.sh +171 -0
- package/openspec/cli/enrich.sh +188 -0
- package/openspec/cli/ff.sh +329 -0
- package/openspec/cli/new.sh +260 -0
- package/openspec/cli/openspec +82 -0
- package/openspec/cli/report.sh +244 -0
- package/openspec/cli/status.sh +178 -0
- package/openspec/cli/verify.sh +246 -0
- package/openspec/config.yaml +76 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/00-original-user-story.md +5 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/01-refined-user-story.md +106 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/README.md +333 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/design.md +461 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/proposal.md +124 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/specs/functional/F001-authentication.spec.md +399 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/specs/technical/T001-jwt-implementation.spec.md +606 -0
- package/openspec/examples/CHANGE-SAMPLE-001-user-authentication/tasks.md +433 -0
- package/openspec/examples/MERMAID_DIAGRAMS.md +481 -0
- package/openspec/examples/README.md +334 -0
- package/openspec/specs/functional/.gitkeep +0 -0
- package/openspec/specs/integration/.gitkeep +0 -0
- package/openspec/specs/security/.gitkeep +0 -0
- package/openspec/specs/technical/.gitkeep +0 -0
- package/openspec/templates/.coderabbit.yaml +259 -0
- package/openspec/templates/design.md +181 -0
- package/openspec/templates/proposal.md +79 -0
- package/openspec/templates/tasks.md +193 -0
- package/package.json +10 -5
- package/rules/git-workflow.md +3 -3
- package/rules/session-protocol.md +3 -3
- package/scripts/README.md +13 -25
- package/scripts/compliance-audit.sh +325 -0
- package/scripts/generate-contract.sh +4 -4
- package/scripts/install-skills.sh +12 -11
- package/scripts/lib/render-contrato.py +1 -1
- package/scripts/quality-baseline.sh +210 -0
- package/scripts/quality-healing.sh +241 -0
- package/setup.sh +3 -3
- package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-314.pyc +0 -0
- package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-314.pyc +0 -0
- package/.claude/skills/ui-ux-pro-max/scripts/__pycache__/search.cpython-314.pyc +0 -0
- package/scripts/lib/jira-project-utils.sh +0 -222
- package/scripts/setup-mcp.sh +0 -654
- package/scripts/test-cuestionario.sh +0 -428
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# F001 — Autenticación de Usuario
|
|
2
|
+
|
|
3
|
+
| Campo | Valor |
|
|
4
|
+
|-------|-------|
|
|
5
|
+
| ID | F001 |
|
|
6
|
+
| Título | Autenticación de Usuario |
|
|
7
|
+
| Dominio | functional |
|
|
8
|
+
| Estado | implemented |
|
|
9
|
+
| Autor | SpecLeap Contributor |
|
|
10
|
+
| Fecha Creación | 2026-02-12 |
|
|
11
|
+
| Última Actualización | 2026-02-12 |
|
|
12
|
+
| Versión | 1.0 |
|
|
13
|
+
|
|
14
|
+
## Descripción
|
|
15
|
+
|
|
16
|
+
Sistema de autenticación basado en email y contraseña que permite a los usuarios identificarse de forma segura y acceder a funcionalidades protegidas del sistema mediante tokens JWT.
|
|
17
|
+
|
|
18
|
+
## Actores
|
|
19
|
+
|
|
20
|
+
- **Usuario Registrado:** Usuario con cuenta activa en el sistema
|
|
21
|
+
- **Sistema:** Backend que valida credenciales y genera tokens
|
|
22
|
+
- **Usuario Anónimo:** Visitante sin credenciales válidas
|
|
23
|
+
|
|
24
|
+
## Precondiciones
|
|
25
|
+
|
|
26
|
+
- El usuario debe estar registrado en el sistema
|
|
27
|
+
- El email del usuario debe estar verificado (fuera de alcance de esta spec)
|
|
28
|
+
- El sistema debe tener par de claves RSA configuradas para JWT
|
|
29
|
+
|
|
30
|
+
## Escenarios
|
|
31
|
+
|
|
32
|
+
### Escenario 1: Login exitoso con credenciales válidas
|
|
33
|
+
|
|
34
|
+
**Descripción:** Usuario registrado se autentica correctamente.
|
|
35
|
+
|
|
36
|
+
**GIVEN:**
|
|
37
|
+
- Un usuario registrado con email `usuario@example.com`
|
|
38
|
+
- Contraseña válida `Passw0rd!123`
|
|
39
|
+
- Menos de 5 intentos fallidos en los últimos 15 minutos
|
|
40
|
+
|
|
41
|
+
**WHEN:**
|
|
42
|
+
- El usuario envía POST a `/api/v1/auth/login` con:
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"email": "usuario@example.com",
|
|
46
|
+
"password": "Passw0rd!123"
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**THEN:**
|
|
51
|
+
- El sistema valida las credenciales contra la base de datos
|
|
52
|
+
- AND genera un token JWT válido por 24 horas
|
|
53
|
+
- AND devuelve respuesta 200 OK con:
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"token": "eyJhbGciOiJSUzI1NiIs...",
|
|
57
|
+
"token_type": "Bearer",
|
|
58
|
+
"expires_in": 86400,
|
|
59
|
+
"user": {
|
|
60
|
+
"id": 123,
|
|
61
|
+
"email": "usuario@example.com"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
- AND registra el intento exitoso en `login_attempts`
|
|
66
|
+
- AND resetea el contador de intentos fallidos
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### Escenario 2: Login con "Remember Me"
|
|
71
|
+
|
|
72
|
+
**Descripción:** Usuario solicita sesión extendida.
|
|
73
|
+
|
|
74
|
+
**GIVEN:**
|
|
75
|
+
- Un usuario registrado con credenciales válidas
|
|
76
|
+
|
|
77
|
+
**WHEN:**
|
|
78
|
+
- El usuario envía POST a `/api/v1/auth/login` con:
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"email": "usuario@example.com",
|
|
82
|
+
"password": "Passw0rd!123",
|
|
83
|
+
"remember": true
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**THEN:**
|
|
88
|
+
- El sistema genera token JWT válido por **30 días** (en lugar de 24h)
|
|
89
|
+
- AND devuelve respuesta 200 OK con `expires_in: 2592000`
|
|
90
|
+
- AND el token incluye claim `remember: true`
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Escenario 3: Login fallido - contraseña incorrecta
|
|
95
|
+
|
|
96
|
+
**Descripción:** Usuario ingresa contraseña errónea.
|
|
97
|
+
|
|
98
|
+
**GIVEN:**
|
|
99
|
+
- Un usuario registrado con email `usuario@example.com`
|
|
100
|
+
- Contraseña incorrecta `WrongPassword`
|
|
101
|
+
|
|
102
|
+
**WHEN:**
|
|
103
|
+
- El usuario envía POST con contraseña incorrecta
|
|
104
|
+
|
|
105
|
+
**THEN:**
|
|
106
|
+
- El sistema devuelve 401 Unauthorized con:
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"error": "Credenciales inválidas"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
- AND **NO** revela que el email existe
|
|
113
|
+
- AND **NO** revela que la contraseña es incorrecta
|
|
114
|
+
- AND registra el intento fallido en `login_attempts`
|
|
115
|
+
- AND incrementa contador de intentos fallidos
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Escenario 4: Login fallido - email no registrado
|
|
120
|
+
|
|
121
|
+
**Descripción:** Usuario intenta login con email que no existe.
|
|
122
|
+
|
|
123
|
+
**GIVEN:**
|
|
124
|
+
- Email `noexiste@example.com` no está en la base de datos
|
|
125
|
+
|
|
126
|
+
**WHEN:**
|
|
127
|
+
- El usuario envía POST con ese email
|
|
128
|
+
|
|
129
|
+
**THEN:**
|
|
130
|
+
- El sistema devuelve 401 Unauthorized con:
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"error": "Credenciales inválidas"
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
- AND **NO** revela que el email no existe
|
|
137
|
+
- AND registra el intento con IP y email
|
|
138
|
+
- AND **NO** bloquea por intentos (no hay cuenta que bloquear)
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Escenario 5: Bloqueo por intentos fallidos
|
|
143
|
+
|
|
144
|
+
**Descripción:** Protección contra brute force.
|
|
145
|
+
|
|
146
|
+
**GIVEN:**
|
|
147
|
+
- Un usuario ha fallado 5 intentos de login en los últimos 15 minutos
|
|
148
|
+
- Desde la misma IP
|
|
149
|
+
|
|
150
|
+
**WHEN:**
|
|
151
|
+
- El usuario intenta login nuevamente (6º intento)
|
|
152
|
+
|
|
153
|
+
**THEN:**
|
|
154
|
+
- El sistema devuelve 429 Too Many Requests con:
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"error": "Cuenta temporalmente bloqueada por intentos fallidos. Intente nuevamente en 30 minutos."
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
- AND **NO** valida las credenciales (bloqueo preventivo)
|
|
161
|
+
- AND registra el intento bloqueado
|
|
162
|
+
- AND el bloqueo dura 30 minutos desde el 5º intento fallido
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Escenario 6: Desbloqueo automático tras expiración
|
|
167
|
+
|
|
168
|
+
**Descripción:** Bloqueo temporal se levanta automáticamente.
|
|
169
|
+
|
|
170
|
+
**GIVEN:**
|
|
171
|
+
- Un usuario fue bloqueado hace 31 minutos
|
|
172
|
+
- No hubo más intentos desde entonces
|
|
173
|
+
|
|
174
|
+
**WHEN:**
|
|
175
|
+
- El usuario intenta login con credenciales válidas
|
|
176
|
+
|
|
177
|
+
**THEN:**
|
|
178
|
+
- El sistema permite el login
|
|
179
|
+
- AND resetea contador de intentos fallidos
|
|
180
|
+
- AND genera token JWT normal
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### Escenario 7: Logout
|
|
185
|
+
|
|
186
|
+
**Descripción:** Usuario cierra sesión.
|
|
187
|
+
|
|
188
|
+
**GIVEN:**
|
|
189
|
+
- Un usuario autenticado con token JWT válido
|
|
190
|
+
|
|
191
|
+
**WHEN:**
|
|
192
|
+
- El usuario envía POST a `/api/v1/auth/logout` con header:
|
|
193
|
+
```
|
|
194
|
+
Authorization: Bearer {token}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**THEN:**
|
|
198
|
+
- El sistema invalida el token (añade a blacklist o marca como revocado)
|
|
199
|
+
- AND devuelve 200 OK con:
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"message": "Sesión cerrada exitosamente"
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
- AND el token ya no es válido para requests subsiguientes
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
### Escenario 8: Acceso a ruta protegida con token válido
|
|
210
|
+
|
|
211
|
+
**Descripción:** Middleware de autenticación permite acceso.
|
|
212
|
+
|
|
213
|
+
**GIVEN:**
|
|
214
|
+
- Un usuario tiene token JWT válido y no expirado
|
|
215
|
+
|
|
216
|
+
**WHEN:**
|
|
217
|
+
- El usuario envía request a ruta protegida con header:
|
|
218
|
+
```
|
|
219
|
+
Authorization: Bearer {token}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**THEN:**
|
|
223
|
+
- El middleware valida el token
|
|
224
|
+
- AND extrae el user_id del payload
|
|
225
|
+
- AND adjunta el objeto `user` al request
|
|
226
|
+
- AND permite continuar con el request
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### Escenario 9: Acceso a ruta protegida con token expirado
|
|
231
|
+
|
|
232
|
+
**Descripción:** Middleware rechaza tokens expirados.
|
|
233
|
+
|
|
234
|
+
**GIVEN:**
|
|
235
|
+
- Un usuario tiene token JWT que expiró hace 1 hora
|
|
236
|
+
|
|
237
|
+
**WHEN:**
|
|
238
|
+
- El usuario envía request a ruta protegida con token expirado
|
|
239
|
+
|
|
240
|
+
**THEN:**
|
|
241
|
+
- El middleware valida el token
|
|
242
|
+
- AND detecta que está expirado
|
|
243
|
+
- AND devuelve 401 Unauthorized con:
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"error": "Token expirado. Inicie sesión nuevamente."
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
- AND **NO** procesa el request
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
### Escenario 10: Acceso a ruta protegida sin token
|
|
254
|
+
|
|
255
|
+
**Descripción:** Middleware rechaza requests sin autenticación.
|
|
256
|
+
|
|
257
|
+
**GIVEN:**
|
|
258
|
+
- Un usuario no autenticado
|
|
259
|
+
|
|
260
|
+
**WHEN:**
|
|
261
|
+
- El usuario envía request a ruta protegida sin header `Authorization`
|
|
262
|
+
|
|
263
|
+
**THEN:**
|
|
264
|
+
- El middleware devuelve 401 Unauthorized con:
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"error": "No autenticado. Token requerido."
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
### Escenario 11: Token con firma alterada (tampered)
|
|
274
|
+
|
|
275
|
+
**Descripción:** Detección de tokens manipulados.
|
|
276
|
+
|
|
277
|
+
**GIVEN:**
|
|
278
|
+
- Un token JWT con firma modificada manualmente
|
|
279
|
+
|
|
280
|
+
**WHEN:**
|
|
281
|
+
- Se envía request con el token alterado
|
|
282
|
+
|
|
283
|
+
**THEN:**
|
|
284
|
+
- El middleware detecta firma inválida
|
|
285
|
+
- AND devuelve 401 Unauthorized con:
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"error": "Token inválido"
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
- AND registra el intento en logs de seguridad
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### Escenario 12: Obtener información del usuario autenticado
|
|
296
|
+
|
|
297
|
+
**Descripción:** Endpoint `/me` devuelve datos del usuario actual.
|
|
298
|
+
|
|
299
|
+
**GIVEN:**
|
|
300
|
+
- Un usuario autenticado con token válido
|
|
301
|
+
|
|
302
|
+
**WHEN:**
|
|
303
|
+
- El usuario envía GET a `/api/v1/auth/me` con token válido
|
|
304
|
+
|
|
305
|
+
**THEN:**
|
|
306
|
+
- El sistema devuelve 200 OK con:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"id": 123,
|
|
310
|
+
"email": "usuario@example.com",
|
|
311
|
+
"created_at": "2026-01-15T10:30:00Z"
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Reglas de Negocio
|
|
318
|
+
|
|
319
|
+
### RN-001: Contraseñas hasheadas
|
|
320
|
+
- Las contraseñas NUNCA se almacenan en texto plano
|
|
321
|
+
- Usar bcrypt con cost factor 12 mínimo
|
|
322
|
+
- Verificación con `Hash::check()` o equivalente
|
|
323
|
+
|
|
324
|
+
### RN-002: Rate limiting
|
|
325
|
+
- Máximo 5 intentos fallidos por IP + email en 15 minutos
|
|
326
|
+
- Bloqueo automático de 30 minutos tras exceder límite
|
|
327
|
+
- No aplicar rate limiting a intentos exitosos
|
|
328
|
+
|
|
329
|
+
### RN-003: Mensajes de error genéricos
|
|
330
|
+
- NUNCA revelar si un email existe o no en el sistema
|
|
331
|
+
- Siempre responder "Credenciales inválidas" (sin detalles)
|
|
332
|
+
- Evitar ataques de enumeración de usuarios
|
|
333
|
+
|
|
334
|
+
### RN-004: Tokens JWT
|
|
335
|
+
- Firma con RS256 (asimétrico)
|
|
336
|
+
- Expiración: 24 horas (normal) / 30 días (remember me)
|
|
337
|
+
- Payload mínimo: user_id, exp, iat
|
|
338
|
+
- No incluir datos sensibles en el payload
|
|
339
|
+
|
|
340
|
+
### RN-005: Logging y auditoría
|
|
341
|
+
- Registrar TODOS los intentos de login (exitosos y fallidos)
|
|
342
|
+
- Almacenar: email, IP, user agent, timestamp, resultado
|
|
343
|
+
- Retener logs mínimo 90 días (configurable)
|
|
344
|
+
|
|
345
|
+
### RN-006: HTTPS obligatorio
|
|
346
|
+
- En producción, rechazar requests HTTP
|
|
347
|
+
- Redirigir HTTP → HTTPS automáticamente
|
|
348
|
+
|
|
349
|
+
## Dependencias
|
|
350
|
+
|
|
351
|
+
### Dependencias de Entrada
|
|
352
|
+
- Usuario debe estar registrado (tabla `users`)
|
|
353
|
+
- Par de claves RSA generadas y configuradas
|
|
354
|
+
- Sistema de logging disponible
|
|
355
|
+
|
|
356
|
+
### Dependencias de Salida
|
|
357
|
+
- Tabla `login_attempts` para auditoría
|
|
358
|
+
- Cache (Redis) opcional para tokens revocados
|
|
359
|
+
- Sistema de notificaciones para alertas de bloqueo
|
|
360
|
+
|
|
361
|
+
## Métricas
|
|
362
|
+
|
|
363
|
+
### KPIs
|
|
364
|
+
- **Tasa de éxito de login:** >95%
|
|
365
|
+
- **Tiempo medio de autenticación:** <500ms
|
|
366
|
+
- **Intentos de brute force bloqueados:** trackear y alertar
|
|
367
|
+
|
|
368
|
+
### Monitoreo
|
|
369
|
+
- Alertar si tasa de fallos >10% en 1 hora
|
|
370
|
+
- Alertar si múltiples IPs atacan mismo usuario
|
|
371
|
+
- Dashboard de intentos por hora/día
|
|
372
|
+
|
|
373
|
+
## Casos No Cubiertos (Fuera de Alcance)
|
|
374
|
+
|
|
375
|
+
- OAuth / Social login
|
|
376
|
+
- Autenticación de dos factores (2FA)
|
|
377
|
+
- Recuperación de contraseña
|
|
378
|
+
- Verificación de email
|
|
379
|
+
- SSO (Single Sign-On)
|
|
380
|
+
- Biometría
|
|
381
|
+
|
|
382
|
+
Estos se abordarán en specs futuras.
|
|
383
|
+
|
|
384
|
+
## Referencias
|
|
385
|
+
|
|
386
|
+
- OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
|
|
387
|
+
- JWT RFC 7519: https://tools.ietf.org/html/rfc7519
|
|
388
|
+
- Bcrypt: https://en.wikipedia.org/wiki/Bcrypt
|
|
389
|
+
|
|
390
|
+
## Historial de Cambios
|
|
391
|
+
|
|
392
|
+
| Versión | Fecha | Autor | Cambios |
|
|
393
|
+
|---------|-------|-------|---------|
|
|
394
|
+
| 1.0 | 2026-02-12 | SpecLeap Contributor | Versión inicial implementada |
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
**Estado:** implemented
|
|
399
|
+
**Última revisión:** 2026-02-12
|