Skip to content

Commit 4e6476b

Browse files
authored
Merge pull request #120 from adetumilara/feature/NV-43-staff-auth-rbac
feat(auth): implement staff authentication and RBAC with JWT
2 parents e0ae862 + 0046160 commit 4e6476b

16 files changed

Lines changed: 2621 additions & 596 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"kiroAgent.configureMCP": "Disabled"
3+
}

backend/SECURITY.md

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
# Security Documentation - Staff Authentication & RBAC
2+
3+
## Overview
4+
5+
This document describes the security measures implemented for staff authentication and role-based access control (RBAC) in the Niffy Insure backend.
6+
7+
## Authentication Flow
8+
9+
### Login Process
10+
11+
Staff authenticate using email and password via `POST /api/auth/login`:
12+
13+
```
14+
POST /api/auth/login
15+
Content-Type: application/json
16+
17+
{
18+
"email": "admin@example.com",
19+
"password": "securepassword"
20+
}
21+
```
22+
23+
Response:
24+
```json
25+
{
26+
"accessToken": "eyJhbGciOiJIUzI1...",
27+
"refreshToken": "eyJhbGciOiJIUzI1...",
28+
"user": {
29+
"id": "uuid",
30+
"email": "admin@example.com",
31+
"role": "admin"
32+
}
33+
}
34+
```
35+
36+
### Token Usage
37+
38+
Include the access token in the `Authorization` header:
39+
```
40+
Authorization: Bearer <access_token>
41+
```
42+
43+
### Token Refresh
44+
45+
Use the refresh token to obtain a new access token via `POST /api/auth/refresh`:
46+
47+
```
48+
POST /api/auth/refresh
49+
Content-Type: application/json
50+
51+
{
52+
"refreshToken": "eyJhbGciOiJIUzI1..."
53+
}
54+
```
55+
56+
## Password Security
57+
58+
- Passwords are hashed using **bcrypt** with salt rounds configured in `config.security.bcryptRounds` (default: 12)
59+
- Raw passwords are never stored or logged
60+
- Password verification failures are logged without exposing credentials
61+
62+
## Role-Based Access Control (RBAC)
63+
64+
### Roles
65+
66+
| Role | Description | Permissions |
67+
|------|-------------|-------------|
68+
| `admin` | Full system administrator | All permissions |
69+
| `support_readonly` | Support staff with read-only access | `read:claims`, `read:policies`, `read:users`, `read:audit` |
70+
71+
### Permission Matrix
72+
73+
```typescript
74+
const ROLE_PERMISSIONS = {
75+
admin: [
76+
'read:claims', 'write:claims',
77+
'read:policies', 'write:policies',
78+
'read:users', 'write:users',
79+
'read:audit', 'write:audit',
80+
'read:settings', 'write:settings',
81+
'read:reports'
82+
],
83+
support_readonly: [
84+
'read:claims', 'read:policies', 'read:users', 'read:audit'
85+
]
86+
};
87+
```
88+
89+
### Support Staff Limitations
90+
91+
Support staff (`support_readonly` role):
92+
- Can view claims, policies, users, and audit logs
93+
- Cannot create, update, or delete any resources
94+
- Cannot access system settings or administrative functions
95+
- Cannot generate reports
96+
97+
## Token Configuration
98+
99+
### Access Token
100+
101+
- **Expiry**: 15 minutes (configurable via `config.jwt.accessTokenExpiry`)
102+
- **Algorithm**: HS256
103+
- **Payload**: `{ id, email, role, iat, exp }`
104+
105+
### Refresh Token
106+
107+
- **Expiry**: 7 days (configurable via `config.jwt.refreshTokenExpiry`)
108+
- **Algorithm**: HS256
109+
- **Payload**: `{ id, email, role, iat, exp, type: 'refresh' }`
110+
111+
## Security Headers
112+
113+
The following security headers are enforced via Helmet:
114+
115+
- `X-Content-Type-Options: nosniff`
116+
- `X-Frame-Options: DENY`
117+
- `X-XSS-Protection: 1; mode=block`
118+
- `Strict-Transport-Security: max-age=31536000; includeSubDomains`
119+
- `Content-Security-Policy` (customizable)
120+
121+
## CORS Configuration
122+
123+
CORS is configured to allow specific origins (configurable via `config.security.corsOrigins`). In production, restrict to known frontend domains.
124+
125+
## Rate Limiting
126+
127+
- **Login attempts**: 5 requests per 15 minutes per IP
128+
- **General API**: 100 requests per 15 minutes per IP
129+
- Rate limit headers are included in responses (`X-RateLimit-Limit`, `X-RateLimit-Remaining`)
130+
131+
## Logging Security
132+
133+
Authentication failures are logged without exposing sensitive information:
134+
- ✅ Logged: email (partial), IP, timestamp, failure reason
135+
- ❌ Not logged: passwords, tokens, full user details
136+
137+
Example log output:
138+
```
139+
[AUTH] Login failed for user: admin@***.com from IP: 192.168.1.1 - Invalid credentials
140+
[AUTH] Token verification failed: TokenExpiredError
141+
[AUTH] Role check failed: user support@example.com with role support_readonly attempted to access POST /admin/users requiring admin
142+
```
143+
144+
## Threat Model
145+
146+
### XSS (Cross-Site Scripting)
147+
148+
**Risk**: Attackers could steal JWT tokens stored in browser localStorage.
149+
150+
**Mitigation**:
151+
- Consider using httpOnly cookies for token storage (requires CSRF protection)
152+
- Implement token rotation on suspicious activity
153+
- Set short expiry for access tokens (15 min)
154+
- Frontend should implement Content Security Policy
155+
156+
**Recommendation**: For production, implement httpOnly secure cookies with CSRF tokens.
157+
158+
### CSRF (Cross-Site Request Forgery)
159+
160+
**Risk**: Attackers could make authenticated requests on behalf of users.
161+
162+
**Mitigation**:
163+
- Use `SameSite` cookie attribute (strict/lax)
164+
- Implement CSRF token validation for state-changing operations
165+
- Use double-submit cookie pattern
166+
167+
**Production Setup**: When using cookies, implement CSRF protection.
168+
169+
### Token Theft
170+
171+
**Risk**: Tokens could be intercepted in transit or stolen from storage.
172+
173+
**Mitigation**:
174+
- Enforce HTTPS in production (see below)
175+
- Short access token expiry (15 min)
176+
- Refresh token rotation on use
177+
- Implement token revocation mechanism
178+
179+
### Brute Force Attacks
180+
181+
**Risk**: Attackers could guess passwords via brute force.
182+
183+
**Mitigation**:
184+
- Rate limiting on login endpoint (5 attempts per 15 min)
185+
- bcrypt with high salt rounds (12)
186+
- Account lockout after failed attempts (future enhancement)
187+
188+
## Production Requirements
189+
190+
### HTTPS Enforcement
191+
192+
⚠️ **Critical**: The application MUST be deployed behind HTTPS in production.
193+
194+
```bash
195+
# Environment variables for production
196+
NODE_ENV=production
197+
FORCE_SSL=true
198+
```
199+
200+
Configuration for reverse proxy (nginx example):
201+
```nginx
202+
server {
203+
listen 443 ssl http2;
204+
server_name api.niffyinsure.com;
205+
206+
ssl_certificate /path/to/cert.pem;
207+
ssl_certificate_key /path/to/key.pem;
208+
ssl_protocols TLSv1.2 TLSv1.3;
209+
210+
location / {
211+
proxy_pass http://localhost:3000;
212+
proxy_set_header X-Forwarded-Proto $scheme;
213+
}
214+
}
215+
```
216+
217+
### Secure Cookie Configuration
218+
219+
When using cookies for token storage, configure:
220+
221+
```typescript
222+
// Example cookie options for production
223+
const cookieOptions = {
224+
httpOnly: true,
225+
secure: true, // HTTPS only
226+
sameSite: 'strict', // CSRF protection
227+
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
228+
path: '/'
229+
};
230+
```
231+
232+
### Environment Variables
233+
234+
Required production configuration:
235+
236+
```bash
237+
# JWT Configuration
238+
JWT_SECRET=<minimum-256-bit-secret>
239+
JWT_REFRESH_SECRET=<minimum-256-bit-secret>
240+
241+
# Security
242+
BCRYPT_ROUNDS=12
243+
NODE_ENV=production
244+
245+
# HTTPS (via reverse proxy)
246+
# FORCE_SSL=true
247+
```
248+
249+
## Key Rotation
250+
251+
### JWT Signing Keys
252+
253+
**Recommendation**: Rotate JWT signing keys periodically.
254+
255+
Implementation approach:
256+
1. Maintain multiple key versions in configuration
257+
2. Use key ID (`kid`) in JWT header to identify which key was used
258+
3. Accept tokens signed by current and previous keys during transition
259+
4. Schedule key rotation every 90 days
260+
261+
```typescript
262+
// Key rotation configuration
263+
const jwtOptions = {
264+
issuer: 'niffyinsure',
265+
audience: 'niffyinsure-api',
266+
algorithm: 'HS256',
267+
keyid: 'key-v1' // Rotate this value when changing keys
268+
};
269+
```
270+
271+
### Refresh Token Rotation
272+
273+
- Each refresh creates a new refresh token
274+
- Store refresh tokens in database for revocation capability
275+
- Invalidate refresh tokens on password change or logout
276+
- Implement token blacklist for immediate revocation
277+
278+
## MFA Roadmap
279+
280+
Multi-factor authentication is planned for future implementation:
281+
282+
### Phase 1 (Post-Launch)
283+
- TOTP-based authenticator apps (Google Authenticator, Authy)
284+
- Recovery codes
285+
286+
### Phase 2
287+
- Email-based verification for sensitive actions
288+
- Session management (view/revoke active sessions)
289+
290+
### Phase 3
291+
- SMS/Email OTP for login confirmation
292+
- Hardware security keys (WebAuthn/FIDO2)
293+
294+
### Implementation Notes
295+
296+
```typescript
297+
// Future MFA types
298+
interface MFAMethods {
299+
totp: boolean;
300+
email: boolean;
301+
sms: boolean;
302+
webauthn: boolean;
303+
}
304+
305+
// Login flow with MFA
306+
interface LoginResponse {
307+
accessToken: string;
308+
mfaRequired: boolean;
309+
mfaMethod: 'totp' | 'email' | 'sms';
310+
}
311+
312+
// Second factor verification endpoint
313+
POST /api/auth/verify-mfa
314+
{
315+
"tempToken": "...",
316+
"code": "123456"
317+
}
318+
```
319+
320+
## Security Testing
321+
322+
### Test Matrix (Implemented)
323+
324+
| Scenario | Expected Status |
325+
|----------|-----------------|
326+
| No auth header on protected route | 401 Unauthorized |
327+
| Invalid token format | 401 Unauthorized |
328+
| Expired token | 401 Unauthorized |
329+
| Valid token, insufficient role | 403 Forbidden |
330+
| Valid token, correct role | 200 OK |
331+
332+
Run tests:
333+
```bash
334+
npm test
335+
```
336+
337+
### Additional Security Tests (Recommended)
338+
339+
- Rate limiting effectiveness
340+
- SQL injection prevention
341+
- XSS prevention in error messages
342+
- Password strength requirements
343+
- Session timeout verification
344+
345+
## API Endpoints Summary
346+
347+
| Endpoint | Method | Auth Required | Role Required |
348+
|----------|--------|---------------|---------------|
349+
| `/api/auth/login` | POST | No | - |
350+
| `/api/auth/refresh` | POST | No | - |
351+
| `/api/auth/me` | GET | Yes | Any |
352+
| `/api/auth/logout` | POST | Yes | Any |
353+
| `/api/admin/dashboard` | GET | Yes | admin |
354+
| `/api/admin/users` | GET/POST | Yes | admin |
355+
| `/api/admin/users/:id` | GET/PUT/DELETE | Yes | admin |
356+
| `/api/admin/policies` | GET/POST | Yes | admin |
357+
| `/api/admin/claims` | GET/POST | Yes | admin |
358+
| `/api/admin/audit` | GET | Yes | admin/support_readonly |
359+
| `/api/admin/settings` | GET/PUT | Yes | admin |
360+
| `/api/admin/reports` | GET | Yes | admin |
361+
362+
## References
363+
364+
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
365+
- [JWT Best Practices](https://datatracker.ietf.org/doc/html/rfc8725)
366+
- [Helmet.js](https://helmetjs.github.io/)
367+
- [Express Rate Limit](https://github.com/express-rate-limit/express-rate-limit)
368+
- [BCrypt](https://github.com/kelektiv/node.bcrypt.js)

backend/jest.config.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
/** @type {import('ts-jest').JestConfigWithTsJest} */
2-
module.exports = {
3-
preset: "ts-jest",
4-
testEnvironment: "node",
5-
roots: ["<rootDir>/tests"],
6-
testMatch: ["**/*.test.ts"],
7-
globals: {
8-
"ts-jest": {
9-
tsconfig: "tsconfig.test.json",
10-
},
11-
},
12-
};
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
roots: ["<rootDir>/src", "<rootDir>/tests"],
6+
testMatch: ["**/__tests__/**/*.test.ts", "**/*.test.ts"],
7+
globals: {
8+
"ts-jest": {
9+
tsconfig: "tsconfig.test.json",
10+
},
11+
},
12+
};

0 commit comments

Comments
 (0)