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,461 @@
|
|
|
1
|
+
# [CHANGE-SAMPLE-001] Diseño Técnico: Autenticación de Usuario
|
|
2
|
+
|
|
3
|
+
| Campo | Valor |
|
|
4
|
+
|-------|-------|
|
|
5
|
+
| ID | CHANGE-SAMPLE-001 |
|
|
6
|
+
| Propuesta | `proposal.md` |
|
|
7
|
+
| Fecha | 2026-02-12 |
|
|
8
|
+
| Estado | approved |
|
|
9
|
+
| Arquitecto | SpecLeap Contributor |
|
|
10
|
+
|
|
11
|
+
## Resumen del Diseño
|
|
12
|
+
|
|
13
|
+
Sistema de autenticación basado en JWT con validación de credenciales contra base de datos, hashing bcrypt, rate limiting por IP, y middleware de autorización para rutas protegidas.
|
|
14
|
+
|
|
15
|
+
## Arquitectura
|
|
16
|
+
|
|
17
|
+
### Diagrama de Componentes
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
flowchart TB
|
|
21
|
+
Cliente["Cliente<br/>(Browser)"] --> AuthController["AuthController<br/>(API Layer)"]
|
|
22
|
+
AuthController --> RateLimiter["RateLimitMiddleware<br/>(Brute Force Protection)"]
|
|
23
|
+
RateLimiter --> AuthService["AuthService<br/>(Business Logic)"]
|
|
24
|
+
AuthService --> UserRepo["UserRepository<br/>(Data Access)"]
|
|
25
|
+
AuthService --> JWTService["JWTService<br/>(Token Manager)"]
|
|
26
|
+
UserRepo --> DB[("Database<br/>users table")]
|
|
27
|
+
RateLimiter --> LoginAttempts[("Database<br/>login_attempts")]
|
|
28
|
+
|
|
29
|
+
style Cliente fill:#e1f5ff
|
|
30
|
+
style AuthController fill:#fff4e1
|
|
31
|
+
style RateLimiter fill:#ffe1e1
|
|
32
|
+
style AuthService fill:#e1ffe1
|
|
33
|
+
style JWTService fill:#f0e1ff
|
|
34
|
+
style UserRepo fill:#fff0e1
|
|
35
|
+
style DB fill:#e8e8e8
|
|
36
|
+
style LoginAttempts fill:#e8e8e8
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Diagrama ASCII (alternativo):**
|
|
40
|
+
```
|
|
41
|
+
┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
42
|
+
│ Cliente │────▶│ AuthController │────▶│ AuthService │
|
|
43
|
+
│ (Browser) │◀────│ (API Layer) │◀────│ (Business Logic)│
|
|
44
|
+
└─────────────┘ └─────────────────┘ └─────────────────┘
|
|
45
|
+
│ │
|
|
46
|
+
│ ▼
|
|
47
|
+
│ ┌─────────────────┐
|
|
48
|
+
│ │ UserRepository │
|
|
49
|
+
│ │ (Data Layer) │
|
|
50
|
+
│ └─────────────────┘
|
|
51
|
+
│ │
|
|
52
|
+
▼ ▼
|
|
53
|
+
┌─────────────────┐ ┌─────────────────┐
|
|
54
|
+
│ RateLimiter │ │ Database │
|
|
55
|
+
│ (Middleware) │ │ (users table) │
|
|
56
|
+
└─────────────────┘ └─────────────────┘
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
┌─────────────────┐
|
|
60
|
+
│ JWTService │
|
|
61
|
+
│ (Token Manager) │
|
|
62
|
+
└─────────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Componentes Afectados
|
|
66
|
+
|
|
67
|
+
| Componente | Tipo de Cambio | Descripción |
|
|
68
|
+
|------------|----------------|-------------|
|
|
69
|
+
| AuthController | Nuevo | Controlador de endpoints de autenticación |
|
|
70
|
+
| AuthService | Nuevo | Lógica de negocio de autenticación |
|
|
71
|
+
| UserRepository | Nuevo | Acceso a datos de usuarios |
|
|
72
|
+
| JWTService | Nuevo | Generación y validación de tokens |
|
|
73
|
+
| AuthMiddleware | Nuevo | Middleware de autorización para rutas |
|
|
74
|
+
| RateLimitMiddleware | Nuevo | Protección contra brute force |
|
|
75
|
+
| routes/api.php | Modificado | Registro de rutas de autenticación |
|
|
76
|
+
|
|
77
|
+
## Modelo de Datos
|
|
78
|
+
|
|
79
|
+
### Tabla Existente: `users`
|
|
80
|
+
|
|
81
|
+
```sql
|
|
82
|
+
CREATE TABLE users (
|
|
83
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
84
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
85
|
+
password_hash VARCHAR(255) NOT NULL,
|
|
86
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
87
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
88
|
+
INDEX idx_email (email)
|
|
89
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Nueva Tabla: `login_attempts`
|
|
93
|
+
|
|
94
|
+
```sql
|
|
95
|
+
CREATE TABLE login_attempts (
|
|
96
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
97
|
+
email VARCHAR(255) NOT NULL,
|
|
98
|
+
ip_address VARCHAR(45) NOT NULL,
|
|
99
|
+
user_agent TEXT,
|
|
100
|
+
success BOOLEAN DEFAULT FALSE,
|
|
101
|
+
attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
102
|
+
INDEX idx_email_ip (email, ip_address),
|
|
103
|
+
INDEX idx_attempted_at (attempted_at)
|
|
104
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Migraciones de Base de Datos
|
|
108
|
+
|
|
109
|
+
```sql
|
|
110
|
+
-- Migration: 20260212000001_create_login_attempts_table.sql
|
|
111
|
+
CREATE TABLE login_attempts (
|
|
112
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
113
|
+
email VARCHAR(255) NOT NULL,
|
|
114
|
+
ip_address VARCHAR(45) NOT NULL,
|
|
115
|
+
user_agent TEXT,
|
|
116
|
+
success BOOLEAN DEFAULT FALSE,
|
|
117
|
+
attempted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
118
|
+
INDEX idx_email_ip (email, ip_address),
|
|
119
|
+
INDEX idx_attempted_at (attempted_at)
|
|
120
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API / Interfaces
|
|
124
|
+
|
|
125
|
+
### Nuevos Endpoints
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
POST /api/v1/auth/login
|
|
129
|
+
POST /api/v1/auth/logout
|
|
130
|
+
POST /api/v1/auth/refresh
|
|
131
|
+
GET /api/v1/auth/me
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Contratos de API
|
|
135
|
+
|
|
136
|
+
#### POST /api/v1/auth/login
|
|
137
|
+
|
|
138
|
+
**Request:**
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"email": "usuario@example.com",
|
|
142
|
+
"password": "contraseña_segura",
|
|
143
|
+
"remember": false
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Response (200 OK):**
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
151
|
+
"token_type": "Bearer",
|
|
152
|
+
"expires_in": 86400,
|
|
153
|
+
"user": {
|
|
154
|
+
"id": 123,
|
|
155
|
+
"email": "usuario@example.com"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Response (401 Unauthorized):**
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"error": "Credenciales inválidas"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Response (429 Too Many Requests):**
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"error": "Cuenta temporalmente bloqueada por intentos fallidos. Intente nuevamente en 30 minutos."
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### POST /api/v1/auth/logout
|
|
175
|
+
|
|
176
|
+
**Headers:**
|
|
177
|
+
```
|
|
178
|
+
Authorization: Bearer {token}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Response (200 OK):**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"message": "Sesión cerrada exitosamente"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### GET /api/v1/auth/me
|
|
189
|
+
|
|
190
|
+
**Headers:**
|
|
191
|
+
```
|
|
192
|
+
Authorization: Bearer {token}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Response (200 OK):**
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"id": 123,
|
|
199
|
+
"email": "usuario@example.com",
|
|
200
|
+
"created_at": "2026-01-15T10:30:00Z"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Flujo de Datos
|
|
205
|
+
|
|
206
|
+
### Secuencia Principal: Login Exitoso
|
|
207
|
+
|
|
208
|
+
```mermaid
|
|
209
|
+
sequenceDiagram
|
|
210
|
+
actor Usuario
|
|
211
|
+
participant Cliente as Cliente<br/>(Browser)
|
|
212
|
+
participant Controller as AuthController
|
|
213
|
+
participant RateLimit as RateLimitMiddleware
|
|
214
|
+
participant Service as AuthService
|
|
215
|
+
participant Repo as UserRepository
|
|
216
|
+
participant JWT as JWTService
|
|
217
|
+
participant DB as Database
|
|
218
|
+
|
|
219
|
+
Usuario->>Cliente: Ingresa email + password
|
|
220
|
+
Cliente->>Controller: POST /api/v1/auth/login
|
|
221
|
+
Controller->>RateLimit: Verificar intentos
|
|
222
|
+
RateLimit->>DB: Contar intentos fallidos (15min)
|
|
223
|
+
DB-->>RateLimit: 2 intentos
|
|
224
|
+
RateLimit-->>Controller: ✓ OK (< 5 intentos)
|
|
225
|
+
|
|
226
|
+
Controller->>Service: authenticate(email, password)
|
|
227
|
+
Service->>Repo: findByEmail(email)
|
|
228
|
+
Repo->>DB: SELECT * FROM users WHERE email=?
|
|
229
|
+
DB-->>Repo: User found
|
|
230
|
+
Repo-->>Service: User object
|
|
231
|
+
|
|
232
|
+
Service->>Service: Hash.check(password, user.password_hash)
|
|
233
|
+
Note over Service: bcrypt verification ~250ms
|
|
234
|
+
|
|
235
|
+
Service->>JWT: generateToken(user, remember=false)
|
|
236
|
+
JWT->>JWT: Sign with RS256 private key
|
|
237
|
+
JWT-->>Service: JWT Token (24h expiry)
|
|
238
|
+
|
|
239
|
+
Service->>DB: INSERT INTO login_attempts<br/>(email, ip, success=true)
|
|
240
|
+
|
|
241
|
+
Service-->>Controller: {token, user}
|
|
242
|
+
Controller-->>Cliente: 200 OK {token, token_type, expires_in, user}
|
|
243
|
+
Cliente-->>Usuario: Redirigir a Dashboard
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Casos de Error
|
|
247
|
+
|
|
248
|
+
| Código | Escenario | Respuesta |
|
|
249
|
+
|--------|-----------|-----------|
|
|
250
|
+
| 400 | Email/password faltantes | `{ error: "Email y contraseña son requeridos" }` |
|
|
251
|
+
| 401 | Credenciales inválidas | `{ error: "Credenciales inválidas" }` |
|
|
252
|
+
| 429 | Cuenta bloqueada | `{ error: "Cuenta temporalmente bloqueada..." }` |
|
|
253
|
+
| 500 | Error interno (DB, etc.) | `{ error: "Error del servidor, intente más tarde" }` |
|
|
254
|
+
|
|
255
|
+
## Seguridad
|
|
256
|
+
|
|
257
|
+
### Autenticación/Autorización
|
|
258
|
+
|
|
259
|
+
- **Método de autenticación:** JWT con firma RS256
|
|
260
|
+
- **Duración del token:** 24 horas (normal) / 30 días (remember me)
|
|
261
|
+
- **Roles con acceso:** Ninguno (endpoint público)
|
|
262
|
+
- **Rutas protegidas:** Aplicar `AuthMiddleware` a rutas que requieran autenticación
|
|
263
|
+
|
|
264
|
+
### Validación de Inputs
|
|
265
|
+
|
|
266
|
+
- **email:** string, formato email válido, max 255 chars, sanitizado
|
|
267
|
+
- **password:** string, min 8 chars, max 255 chars, no sanitizado (hash directo)
|
|
268
|
+
- **remember:** boolean, opcional
|
|
269
|
+
|
|
270
|
+
### Hashing de Contraseñas
|
|
271
|
+
|
|
272
|
+
```php
|
|
273
|
+
// Registro (no en este CHANGE, pero para contexto)
|
|
274
|
+
$hash = Hash::make($password); // bcrypt cost=12
|
|
275
|
+
|
|
276
|
+
// Login
|
|
277
|
+
Hash::check($password, $user->password_hash);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Rate Limiting
|
|
281
|
+
|
|
282
|
+
- **Límite:** 5 intentos fallidos por IP + email en 15 minutos
|
|
283
|
+
- **Bloqueo:** 30 minutos
|
|
284
|
+
- **Desbloqueo:** Automático tras expiración
|
|
285
|
+
- **Override manual:** Admin puede desbloquear desde DB
|
|
286
|
+
|
|
287
|
+
### Consideraciones de Seguridad
|
|
288
|
+
|
|
289
|
+
- [x] CSRF protection (tokens en formularios web)
|
|
290
|
+
- [x] Rate limiting (brute force protection)
|
|
291
|
+
- [x] Input sanitization (email)
|
|
292
|
+
- [x] SQL injection prevention (ORM/prepared statements)
|
|
293
|
+
- [x] XSS prevention (no renderizar inputs sin escapar)
|
|
294
|
+
- [x] HTTPS obligatorio en producción
|
|
295
|
+
- [x] Headers de seguridad:
|
|
296
|
+
- `X-Content-Type-Options: nosniff`
|
|
297
|
+
- `X-Frame-Options: DENY`
|
|
298
|
+
- `Content-Security-Policy: default-src 'self'`
|
|
299
|
+
- [x] Logging de intentos para auditoría
|
|
300
|
+
- [x] Tokens HttpOnly (no accesibles desde JavaScript)
|
|
301
|
+
|
|
302
|
+
## Performance
|
|
303
|
+
|
|
304
|
+
### Estimaciones de Carga
|
|
305
|
+
|
|
306
|
+
- **Requests esperados:** 50 logins/minuto en hora pico
|
|
307
|
+
- **Usuarios concurrentes:** ~100 logins simultáneos
|
|
308
|
+
- **Tamaño de respuesta:** ~500 bytes (JSON + token)
|
|
309
|
+
|
|
310
|
+
### Optimizaciones
|
|
311
|
+
|
|
312
|
+
#### Índices de Base de Datos
|
|
313
|
+
|
|
314
|
+
```sql
|
|
315
|
+
-- Ya existente en users
|
|
316
|
+
INDEX idx_email (email)
|
|
317
|
+
|
|
318
|
+
-- Nuevos en login_attempts
|
|
319
|
+
INDEX idx_email_ip (email, ip_address)
|
|
320
|
+
INDEX idx_attempted_at (attempted_at)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### Caché Strategy
|
|
324
|
+
|
|
325
|
+
- **Validación de tokens:** Cachear public key para verificación (Redis, 1 hora)
|
|
326
|
+
- **Rate limiting counters:** Redis con TTL de 15 minutos
|
|
327
|
+
- **No cachear:** Credenciales, tokens generados
|
|
328
|
+
|
|
329
|
+
#### Paginación
|
|
330
|
+
|
|
331
|
+
No aplica (endpoints no devuelven listas).
|
|
332
|
+
|
|
333
|
+
## Testing Strategy
|
|
334
|
+
|
|
335
|
+
### Unit Tests
|
|
336
|
+
|
|
337
|
+
- [x] **AuthService:**
|
|
338
|
+
- `test_authenticate_with_valid_credentials()`
|
|
339
|
+
- `test_authenticate_with_invalid_password()`
|
|
340
|
+
- `test_authenticate_with_nonexistent_email()`
|
|
341
|
+
- [x] **JWTService:**
|
|
342
|
+
- `test_generate_token_with_user()`
|
|
343
|
+
- `test_validate_valid_token()`
|
|
344
|
+
- `test_validate_expired_token()`
|
|
345
|
+
- `test_validate_tampered_token()`
|
|
346
|
+
- [x] **RateLimitMiddleware:**
|
|
347
|
+
- `test_allows_requests_under_limit()`
|
|
348
|
+
- `test_blocks_requests_over_limit()`
|
|
349
|
+
- `test_resets_counter_after_timeout()`
|
|
350
|
+
|
|
351
|
+
### Integration Tests
|
|
352
|
+
|
|
353
|
+
- [x] **POST /api/v1/auth/login:**
|
|
354
|
+
- `test_login_success_with_valid_credentials()`
|
|
355
|
+
- `test_login_fails_with_invalid_password()`
|
|
356
|
+
- `test_login_fails_with_nonexistent_email()`
|
|
357
|
+
- `test_login_blocked_after_5_failed_attempts()`
|
|
358
|
+
- `test_login_with_remember_me_extends_token_expiry()`
|
|
359
|
+
- [x] **POST /api/v1/auth/logout:**
|
|
360
|
+
- `test_logout_invalidates_token()`
|
|
361
|
+
- `test_logout_fails_without_token()`
|
|
362
|
+
- [x] **GET /api/v1/auth/me:**
|
|
363
|
+
- `test_returns_user_with_valid_token()`
|
|
364
|
+
- `test_fails_with_expired_token()`
|
|
365
|
+
|
|
366
|
+
### E2E Tests (si aplica)
|
|
367
|
+
|
|
368
|
+
- [x] Flujo completo: Login → Acceder a ruta protegida → Logout
|
|
369
|
+
- [x] Flujo de bloqueo: 5 intentos fallidos → Bloqueo → Espera 30min → Reintento exitoso
|
|
370
|
+
|
|
371
|
+
## Plan de Rollback
|
|
372
|
+
|
|
373
|
+
### Pasos para Revertir
|
|
374
|
+
|
|
375
|
+
1. **Código:**
|
|
376
|
+
```bash
|
|
377
|
+
git revert <commit-hash>
|
|
378
|
+
git push origin main
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
2. **Migración de BD:**
|
|
382
|
+
```sql
|
|
383
|
+
DROP TABLE IF EXISTS login_attempts;
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
3. **Caché:**
|
|
387
|
+
```bash
|
|
388
|
+
redis-cli FLUSHDB
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
4. **Rutas protegidas:** Comentar `AuthMiddleware` temporalmente
|
|
392
|
+
|
|
393
|
+
### Datos a Preservar
|
|
394
|
+
|
|
395
|
+
- **Tabla `users`:** NO tocar (ya existente)
|
|
396
|
+
- **Tabla `login_attempts`:** Puede eliminarse (solo auditoría)
|
|
397
|
+
- **Tokens activos:** Se invalidarán automáticamente al revertir
|
|
398
|
+
|
|
399
|
+
## Decisiones Técnicas (ADR)
|
|
400
|
+
|
|
401
|
+
### ADR-001: JWT con RS256 en lugar de HS256
|
|
402
|
+
|
|
403
|
+
**Contexto:** Necesitamos decidir algoritmo de firma de tokens JWT.
|
|
404
|
+
|
|
405
|
+
**Alternativas:**
|
|
406
|
+
- **HS256:** Firma simétrica (misma clave para generar y validar)
|
|
407
|
+
- **RS256:** Firma asimétrica (clave privada para generar, pública para validar)
|
|
408
|
+
|
|
409
|
+
**Decisión:** Elegimos **RS256**.
|
|
410
|
+
|
|
411
|
+
**Razones:**
|
|
412
|
+
- Permite validación de tokens en servicios sin acceso a clave privada
|
|
413
|
+
- Mayor seguridad (comprometer clave pública no permite generar tokens falsos)
|
|
414
|
+
- Estándar en sistemas distribuidos
|
|
415
|
+
|
|
416
|
+
**Consecuencias:**
|
|
417
|
+
- ✅ Mejor seguridad y escalabilidad
|
|
418
|
+
- ⚠️ Ligeramente más lento que HS256 (despreciable en nuestra carga)
|
|
419
|
+
- ⚠️ Requiere gestión de par de claves RSA
|
|
420
|
+
|
|
421
|
+
### ADR-002: Bcrypt cost factor = 12
|
|
422
|
+
|
|
423
|
+
**Contexto:** Definir balance entre seguridad y performance para hashing.
|
|
424
|
+
|
|
425
|
+
**Decisión:** Bcrypt con cost factor **12**.
|
|
426
|
+
|
|
427
|
+
**Razones:**
|
|
428
|
+
- OWASP recomienda mínimo 10, ideal 12-14
|
|
429
|
+
- Cost 12 toma ~250ms en hardware moderno (aceptable para login)
|
|
430
|
+
- Protege contra GPUs de ataques offline
|
|
431
|
+
|
|
432
|
+
**Consecuencias:**
|
|
433
|
+
- ✅ Resistencia fuerte contra brute force offline
|
|
434
|
+
- ⚠️ ~250ms por hash (solo en login/registro, no es problema)
|
|
435
|
+
|
|
436
|
+
### ADR-003: Rate limiting por IP + Email
|
|
437
|
+
|
|
438
|
+
**Contexto:** Decidir granularidad del rate limiting.
|
|
439
|
+
|
|
440
|
+
**Alternativas:**
|
|
441
|
+
- Solo por IP
|
|
442
|
+
- Solo por email
|
|
443
|
+
- IP + email
|
|
444
|
+
|
|
445
|
+
**Decisión:** **IP + email** combinados.
|
|
446
|
+
|
|
447
|
+
**Razones:**
|
|
448
|
+
- Previene ataques distribuidos (múltiples IPs a mismo email)
|
|
449
|
+
- Previene bloqueo global de una IP compartida
|
|
450
|
+
|
|
451
|
+
**Consecuencias:**
|
|
452
|
+
- ✅ Protección más robusta
|
|
453
|
+
- ⚠️ Requiere índice compuesto en `login_attempts`
|
|
454
|
+
|
|
455
|
+
## Referencias
|
|
456
|
+
|
|
457
|
+
- Propuesta: `proposal.md`
|
|
458
|
+
- Spec principal: `openspec/specs/functional/F001-authentication.spec.md`
|
|
459
|
+
- Delta specs: `specs/functional/F001-authentication.spec.md`
|
|
460
|
+
- JWT RFC: https://tools.ietf.org/html/rfc7519
|
|
461
|
+
- OWASP Auth Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# [CHANGE-SAMPLE-001] Propuesta: Implementar Autenticación de Usuario
|
|
2
|
+
|
|
3
|
+
| Campo | Valor |
|
|
4
|
+
|-------|-------|
|
|
5
|
+
| ID | CHANGE-SAMPLE-001 |
|
|
6
|
+
| Fecha | 2026-02-12 |
|
|
7
|
+
| Estado | completed |
|
|
8
|
+
| Autor | SpecLeap Contributor |
|
|
9
|
+
| Prioridad | high |
|
|
10
|
+
| Ticket | app-tienda-23 |
|
|
11
|
+
|
|
12
|
+
## Resumen Ejecutivo
|
|
13
|
+
|
|
14
|
+
Implementar sistema de autenticación seguro con email y contraseña, generación de tokens JWT, y protección contra ataques de fuerza bruta. Base fundamental para todas las funcionalidades autenticadas del sistema.
|
|
15
|
+
|
|
16
|
+
## Contexto y Problema
|
|
17
|
+
|
|
18
|
+
### Situación Actual
|
|
19
|
+
El sistema actualmente no tiene ningún mecanismo de autenticación. Todas las rutas son públicas y no hay forma de identificar usuarios ni proteger datos personales.
|
|
20
|
+
|
|
21
|
+
### Problema a Resolver
|
|
22
|
+
- **P1:** Exposición de datos sensibles sin control de acceso
|
|
23
|
+
- **P2:** Imposibilidad de personalizar funcionalidades por usuario
|
|
24
|
+
- **P3:** Incumplimiento de regulaciones de protección de datos (GDPR, etc.)
|
|
25
|
+
- **P4:** No hay trazabilidad de acciones por usuario
|
|
26
|
+
|
|
27
|
+
### Impacto si No se Resuelve
|
|
28
|
+
- Violaciones de seguridad y privacidad
|
|
29
|
+
- Imposibilidad de lanzar funcionalidades que requieran identificación
|
|
30
|
+
- Incumplimiento legal
|
|
31
|
+
- Pérdida de confianza de usuarios
|
|
32
|
+
|
|
33
|
+
## Propuesta de Solución
|
|
34
|
+
|
|
35
|
+
### Descripción General
|
|
36
|
+
Implementar sistema de autenticación basado en JWT (JSON Web Tokens) con:
|
|
37
|
+
- Endpoint de login que valida credenciales
|
|
38
|
+
- Generación de tokens firmados
|
|
39
|
+
- Middleware de autenticación para rutas protegidas
|
|
40
|
+
- Rate limiting y protección contra brute force
|
|
41
|
+
- Logging y auditoría completos
|
|
42
|
+
|
|
43
|
+
### User Stories Afectadas
|
|
44
|
+
|
|
45
|
+
#### US-AUTH-001: Como usuario registrado, quiero autenticarme con email y contraseña
|
|
46
|
+
- Criterios de aceptación:
|
|
47
|
+
- [x] CA-01: Login exitoso con credenciales válidas genera token JWT
|
|
48
|
+
- [x] CA-02: Login fallido muestra error genérico sin revelar información
|
|
49
|
+
- [x] CA-03: Cuenta bloqueada temporalmente tras 5 intentos fallidos
|
|
50
|
+
- [x] CA-04: Email no registrado muestra error genérico
|
|
51
|
+
- [x] CA-05: Token válido por 24h (o 30 días con "Remember Me")
|
|
52
|
+
- [x] CA-06: Logout invalida token actual
|
|
53
|
+
- [x] CA-07: Token expirado redirige a login
|
|
54
|
+
|
|
55
|
+
## Alcance
|
|
56
|
+
|
|
57
|
+
### Incluido
|
|
58
|
+
- Endpoint POST `/api/v1/auth/login`
|
|
59
|
+
- Endpoint POST `/api/v1/auth/logout`
|
|
60
|
+
- Middleware `AuthMiddleware` para proteger rutas
|
|
61
|
+
- Rate limiting (5 intentos/15min por IP)
|
|
62
|
+
- Hashing de contraseñas con bcrypt
|
|
63
|
+
- Generación de tokens JWT con RS256
|
|
64
|
+
- Logging de intentos de login
|
|
65
|
+
- Tests unitarios e integración (>80% coverage)
|
|
66
|
+
- Documentación de API (OpenAPI)
|
|
67
|
+
|
|
68
|
+
### Excluido (fuera de alcance)
|
|
69
|
+
- OAuth / Social login (propuesta futura: CHANGE-002)
|
|
70
|
+
- 2FA / MFA (propuesta futura: CHANGE-003)
|
|
71
|
+
- Recuperación de contraseña (propuesta futura: CHANGE-004)
|
|
72
|
+
- Registro de usuarios (propuesta futura: CHANGE-005)
|
|
73
|
+
- SSO (propuesta futura: CHANGE-006)
|
|
74
|
+
|
|
75
|
+
## Riesgos y Mitigaciones
|
|
76
|
+
|
|
77
|
+
| Riesgo | Probabilidad | Impacto | Mitigación |
|
|
78
|
+
|--------|--------------|---------|------------|
|
|
79
|
+
| Vulnerabilidad en implementación JWT | Media | Alto | Code review exhaustivo + herramientas SAST (Semgrep) |
|
|
80
|
+
| Bloqueo de usuarios legítimos | Media | Medio | Rate limiting configurable + desbloqueo manual |
|
|
81
|
+
| Performance bajo alta carga | Baja | Medio | Cachear validación de tokens + load testing |
|
|
82
|
+
| Tokens comprometidos | Baja | Alto | Expiración corta + rotación periódica |
|
|
83
|
+
|
|
84
|
+
## Dependencias
|
|
85
|
+
|
|
86
|
+
- [x] Tabla `users` con campos `email`, `password_hash`, `created_at`, `updated_at`
|
|
87
|
+
- [x] Librería JWT (tymon/jwt-auth para Laravel o jsonwebtoken para Node)
|
|
88
|
+
- [ ] Configuración de claves RSA para firma de tokens (bloqueante)
|
|
89
|
+
- [ ] Sistema de logging centralizado (no bloqueante)
|
|
90
|
+
|
|
91
|
+
## Estimación Inicial
|
|
92
|
+
|
|
93
|
+
| Concepto | Estimación |
|
|
94
|
+
|----------|------------|
|
|
95
|
+
| Esfuerzo | M (2 sprints) |
|
|
96
|
+
| Tiempo | 2 semanas |
|
|
97
|
+
| Complejidad | Alta |
|
|
98
|
+
|
|
99
|
+
Desglose:
|
|
100
|
+
- Backend (API + middleware): 5 días
|
|
101
|
+
- Tests: 2 días
|
|
102
|
+
- Seguridad (rate limiting, auditoría): 2 días
|
|
103
|
+
- Documentación: 1 día
|
|
104
|
+
|
|
105
|
+
## Decisión
|
|
106
|
+
|
|
107
|
+
- [x] **Aprobada** — Fecha: 2026-02-12
|
|
108
|
+
- Prioridad: High (bloqueante para features autenticadas)
|
|
109
|
+
- Aprobado por: Tech Lead + Product Owner
|
|
110
|
+
|
|
111
|
+
## Referencias
|
|
112
|
+
|
|
113
|
+
- Spec relacionada: `openspec/specs/functional/F001-authentication.spec.md`
|
|
114
|
+
- Delta spec: `specs/functional/F001-authentication.spec.md`
|
|
115
|
+
- Diseño: `design.md`
|
|
116
|
+
- Tareas: `tasks.md`
|
|
117
|
+
- User story refinada: `01-refined-user-story.md`
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Completada
|
|
122
|
+
- Fecha: 2026-02-12
|
|
123
|
+
- PR: #45 (merged)
|
|
124
|
+
- Deploy: Production v1.1.0
|