Skip to content

Commit e2a2cd2

Browse files
authored
Merge pull request #347 from KAMALDEEN333/Auth-Middleware
Implemented the Api key Authentification Middleware
2 parents 9cfdd3c + 82b2040 commit e2a2cd2

15 files changed

Lines changed: 1013 additions & 0 deletions

backend/src/api-keys/README.md

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# API Key Authentication
2+
3+
This document describes the API key authentication system for external integrations in MindBlock.
4+
5+
## Overview
6+
7+
The API key authentication system allows external services, webhooks, and third-party applications to authenticate with the MindBlock API using secure API keys.
8+
9+
## Key Features
10+
11+
- **Secure Generation**: API keys are cryptographically random and follow a specific format
12+
- **Hashed Storage**: Keys are stored as bcrypt hashes, never in plain text
13+
- **Scope-based Permissions**: Keys can have different permission levels (read, write, delete, admin)
14+
- **Rate Limiting**: Per-key rate limiting to prevent abuse
15+
- **Expiration**: Keys can have expiration dates
16+
- **Revocation**: Keys can be instantly revoked
17+
- **Usage Tracking**: All API key usage is logged and tracked
18+
- **IP Whitelisting**: Optional IP address restrictions
19+
20+
## API Key Format
21+
22+
API keys follow this format:
23+
```
24+
mbk_{environment}_{random_string}
25+
```
26+
27+
- **Prefix**: `mbk_` (MindBlock Key)
28+
- **Environment**: `live_` or `test_`
29+
- **Random String**: 32 characters (base62)
30+
31+
Example: `mbk_live_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7Qr9St1U`
32+
33+
## Authentication Methods
34+
35+
API keys can be provided in two ways:
36+
37+
1. **Header**: `X-API-Key: mbk_live_...`
38+
2. **Query Parameter**: `?apiKey=mbk_live_...`
39+
40+
## Scopes and Permissions
41+
42+
- `read`: Can read data (GET requests)
43+
- `write`: Can create/update data (POST, PUT, PATCH)
44+
- `delete`: Can delete data (DELETE requests)
45+
- `admin`: Full access to all operations
46+
- `custom`: Define specific endpoint access
47+
48+
## API Endpoints
49+
50+
### Managing API Keys
51+
52+
All API key management endpoints require JWT authentication.
53+
54+
#### Generate API Key
55+
```
56+
POST /api-keys
57+
Authorization: Bearer <jwt_token>
58+
Content-Type: application/json
59+
60+
{
61+
"name": "My Integration Key",
62+
"scopes": ["read", "write"],
63+
"expiresAt": "2024-12-31T23:59:59Z",
64+
"ipWhitelist": ["192.168.1.1"]
65+
}
66+
```
67+
68+
Response:
69+
```json
70+
{
71+
"apiKey": "mbk_live_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7Qr9St1U",
72+
"apiKeyEntity": {
73+
"id": "key-uuid",
74+
"name": "My Integration Key",
75+
"scopes": ["read", "write"],
76+
"expiresAt": "2024-12-31T23:59:59Z",
77+
"isActive": true,
78+
"usageCount": 0,
79+
"createdAt": "2024-01-01T00:00:00Z"
80+
}
81+
}
82+
```
83+
84+
#### List API Keys
85+
```
86+
GET /api-keys
87+
Authorization: Bearer <jwt_token>
88+
```
89+
90+
#### Revoke API Key
91+
```
92+
DELETE /api-keys/{key_id}
93+
Authorization: Bearer <jwt_token>
94+
```
95+
96+
#### Rotate API Key
97+
```
98+
POST /api-keys/{key_id}/rotate
99+
Authorization: Bearer <jwt_token>
100+
```
101+
102+
### Using API Keys
103+
104+
To authenticate with an API key, include it in requests:
105+
106+
#### Header Authentication
107+
```
108+
GET /users/api-keys/stats
109+
X-API-Key: mbk_live_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7Qr9St1U
110+
```
111+
112+
#### Query Parameter Authentication
113+
```
114+
GET /users/api-keys/stats?apiKey=mbk_live_Ab3Cd5Ef7Gh9Ij1Kl3Mn5Op7Qr9St1U
115+
```
116+
117+
## Error Responses
118+
119+
### Invalid API Key
120+
```json
121+
{
122+
"statusCode": 401,
123+
"message": "Invalid API key",
124+
"error": "Unauthorized"
125+
}
126+
```
127+
128+
### Insufficient Permissions
129+
```json
130+
{
131+
"statusCode": 401,
132+
"message": "Insufficient API key permissions",
133+
"error": "Unauthorized"
134+
}
135+
```
136+
137+
### Expired Key
138+
```json
139+
{
140+
"statusCode": 401,
141+
"message": "API key has expired",
142+
"error": "Unauthorized"
143+
}
144+
```
145+
146+
### Rate Limited
147+
```json
148+
{
149+
"statusCode": 429,
150+
"message": "Too Many Requests",
151+
"error": "Too Many Requests"
152+
}
153+
```
154+
155+
## Rate Limiting
156+
157+
- API keys have a default limit of 100 requests per minute
158+
- Rate limits are tracked per API key
159+
- Exceeding limits returns HTTP 429
160+
161+
## Security Best Practices
162+
163+
1. **Store Keys Securely**: Never expose API keys in client-side code or logs
164+
2. **Use Appropriate Scopes**: Grant only necessary permissions
165+
3. **Set Expiration**: Use expiration dates for temporary access
166+
4. **IP Whitelisting**: Restrict access to known IP addresses when possible
167+
5. **Monitor Usage**: Regularly review API key usage logs
168+
6. **Rotate Keys**: Periodically rotate keys for security
169+
7. **Revoke Compromised Keys**: Immediately revoke keys if compromised
170+
171+
## Implementation Details
172+
173+
### Middleware Order
174+
1. `ApiKeyMiddleware` - Extracts and validates API key (optional)
175+
2. `ApiKeyGuard` - Enforces authentication requirements
176+
3. `ApiKeyThrottlerGuard` - Applies rate limiting
177+
4. `ApiKeyLoggingInterceptor` - Logs usage
178+
179+
### Database Schema
180+
API keys are stored in the `api_keys` table with:
181+
- `keyHash`: Bcrypt hash of the API key
182+
- `userId`: Associated user ID
183+
- `scopes`: Array of permission scopes
184+
- `expiresAt`: Optional expiration timestamp
185+
- `isActive`: Active status
186+
- `usageCount`: Number of uses
187+
- `lastUsedAt`: Last usage timestamp
188+
- `ipWhitelist`: Optional IP restrictions
189+
190+
## Testing
191+
192+
API keys can be tested using the test environment:
193+
- Use `mbk_test_` prefixed keys for testing
194+
- Test keys don't affect production data
195+
- All features work identically in test mode
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
Injectable,
3+
NestInterceptor,
4+
ExecutionContext,
5+
CallHandler,
6+
Logger,
7+
} from '@nestjs/common';
8+
import { Observable } from 'rxjs';
9+
import { RequestWithApiKey } from './api-key.middleware';
10+
11+
@Injectable()
12+
export class ApiKeyLoggingInterceptor implements NestInterceptor {
13+
private readonly logger = new Logger(ApiKeyLoggingInterceptor.name);
14+
15+
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
16+
const request = context.switchToHttp().getRequest<RequestWithApiKey>();
17+
const response = context.switchToHttp().getResponse();
18+
19+
if (request.apiKey) {
20+
const startTime = Date.now();
21+
22+
return next.handle().pipe(
23+
tap(() => {
24+
const duration = Date.now() - startTime;
25+
this.logger.log(
26+
`API Key Usage: ${request.apiKey.id} - ${request.method} ${request.url} - ${response.statusCode} - ${duration}ms`,
27+
);
28+
}),
29+
);
30+
}
31+
32+
return next.handle();
33+
}
34+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Injectable, ExecutionContext, Inject } from '@nestjs/common';
2+
import { ThrottlerGuard } from '@nestjs/throttler';
3+
import { RequestWithApiKey } from './api-key.middleware';
4+
5+
@Injectable()
6+
export class ApiKeyThrottlerGuard extends ThrottlerGuard {
7+
protected async getTracker(req: RequestWithApiKey): Promise<string> {
8+
// Use API key ID as tracker if API key is present
9+
if (req.apiKey) {
10+
return `api-key:${req.apiKey.id}`;
11+
}
12+
13+
// Fall back to IP-based tracking if no API key
14+
return req.ip || req.connection.remoteAddress || req.socket.remoteAddress || 'unknown';
15+
}
16+
17+
protected async getLimit(context: ExecutionContext): Promise<number> {
18+
const req = context.switchToHttp().getRequest<RequestWithApiKey>();
19+
20+
// Different limits for API keys vs regular requests
21+
if (req.apiKey) {
22+
// API keys get higher limits
23+
return 100; // 100 requests per ttl
24+
}
25+
26+
// Regular requests use default limit
27+
return 10; // Default from ThrottlerModule config
28+
}
29+
30+
protected async getTtl(context: ExecutionContext): Promise<number> {
31+
const req = context.switchToHttp().getRequest<RequestWithApiKey>();
32+
33+
// Different TTL for API keys
34+
if (req.apiKey) {
35+
return 60000; // 1 minute
36+
}
37+
38+
return 60000; // Default from ThrottlerModule config
39+
}
40+
}

0 commit comments

Comments
 (0)