@rempays/shared-core 1.0.2-beta.1
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/README.md +346 -0
- package/dist/auth/authorizer-example.d.ts +24 -0
- package/dist/auth/authorizer-example.js +51 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/jwt-validator.d.ts +37 -0
- package/dist/auth/jwt-validator.js +101 -0
- package/dist/auth/types.d.ts +28 -0
- package/dist/auth/types.js +1 -0
- package/dist/cognito/cognito.service.d.ts +45 -0
- package/dist/cognito/cognito.service.js +180 -0
- package/dist/cognito/index.d.ts +2 -0
- package/dist/cognito/index.js +2 -0
- package/dist/cognito/types.d.ts +38 -0
- package/dist/cognito/types.js +1 -0
- package/dist/dynamodb/dynamodb.client.d.ts +67 -0
- package/dist/dynamodb/dynamodb.client.js +166 -0
- package/dist/dynamodb/index.d.ts +1 -0
- package/dist/dynamodb/index.js +1 -0
- package/dist/facebook-api/facebook.d.ts +83 -0
- package/dist/facebook-api/facebook.js +165 -0
- package/dist/facebook-api/http.d.ts +2 -0
- package/dist/facebook-api/http.js +14 -0
- package/dist/facebook-api/index.d.ts +2 -0
- package/dist/facebook-api/index.js +1 -0
- package/dist/http/client.d.ts +17 -0
- package/dist/http/client.js +46 -0
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/s3/index.d.ts +1 -0
- package/dist/s3/index.js +1 -0
- package/dist/s3/s3.service.d.ts +29 -0
- package/dist/s3/s3.service.js +60 -0
- package/dist/textract/index.d.ts +1 -0
- package/dist/textract/index.js +1 -0
- package/dist/textract/textract.service.d.ts +14 -0
- package/dist/textract/textract.service.js +63 -0
- package/package.json +103 -0
package/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# @rempays/shared-core
|
|
2
|
+
|
|
3
|
+
Core utilities layer for RemPays platform with AWS services integration (Cognito, Auth, S3, Textract, Facebook API, HTTP Client).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @rempays/shared-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Modules
|
|
12
|
+
|
|
13
|
+
### 1. Cognito Service
|
|
14
|
+
|
|
15
|
+
Authentication and user management with AWS Cognito.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { CognitoService } from '@rempays/shared-core/cognito';
|
|
19
|
+
|
|
20
|
+
const cognito = new CognitoService({
|
|
21
|
+
userPoolId: 'us-east-1_xxxxx',
|
|
22
|
+
clientId: 'xxxxx',
|
|
23
|
+
region: 'us-east-1'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Register user
|
|
27
|
+
const result = await cognito.signUp({
|
|
28
|
+
phoneNumber: '+1234567890',
|
|
29
|
+
email: 'user@example.com',
|
|
30
|
+
password: 'SecurePass123'
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Confirm registration with OTP
|
|
34
|
+
await cognito.confirmSignUp('+1234567890', '123456');
|
|
35
|
+
|
|
36
|
+
// Sign in with custom auth
|
|
37
|
+
const authResponse = await cognito.signInCustomAuth({
|
|
38
|
+
phoneNumber: '+1234567890'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Verify OTP code
|
|
42
|
+
const tokens = await cognito.respondToAuthChallenge({
|
|
43
|
+
phoneNumber: '+1234567890',
|
|
44
|
+
code: '123456',
|
|
45
|
+
session: authResponse.session
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Get user info
|
|
49
|
+
const user = await cognito.getUser(tokens.accessToken!);
|
|
50
|
+
|
|
51
|
+
// Refresh token
|
|
52
|
+
const newTokens = await cognito.refreshToken({
|
|
53
|
+
refreshToken: tokens.refreshToken!
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. JWT Validator (Auth)
|
|
58
|
+
|
|
59
|
+
Validate Cognito JWT tokens for Lambda Authorizers.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { JwtValidator } from '@rempays/shared-core/auth';
|
|
63
|
+
|
|
64
|
+
const validator = new JwtValidator({
|
|
65
|
+
region: 'us-east-1',
|
|
66
|
+
userPoolId: 'us-east-1_xxxxx'
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Validate token from Authorization header
|
|
70
|
+
const decoded = await validator.validateFromHeader(event.headers.Authorization);
|
|
71
|
+
// Returns: { sub, username, exp, iat, token_use, ... }
|
|
72
|
+
|
|
73
|
+
// Or validate token directly
|
|
74
|
+
const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...';
|
|
75
|
+
const decoded = await validator.validate(token);
|
|
76
|
+
|
|
77
|
+
// Use in Lambda Authorizer
|
|
78
|
+
export const handler = async (event: any) => {
|
|
79
|
+
try {
|
|
80
|
+
const decoded = await validator.validateFromHeader(event.headers.Authorization);
|
|
81
|
+
return generateAllowPolicy(decoded.sub, event.methodArn, decoded);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return generateDenyPolicy('user', event.methodArn);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. HTTP Client
|
|
89
|
+
|
|
90
|
+
Generic HTTP client built on axios with authentication support.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { createHttpClient, HttpClient } from '@rempays/shared-core/http';
|
|
94
|
+
|
|
95
|
+
// Create basic client
|
|
96
|
+
const client = createHttpClient();
|
|
97
|
+
|
|
98
|
+
// Create client with auth token
|
|
99
|
+
const authClient = createHttpClient({
|
|
100
|
+
token: 'your-bearer-token',
|
|
101
|
+
baseURL: 'https://api.example.com',
|
|
102
|
+
timeout: 5000
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Use convenience methods
|
|
106
|
+
const data = await client.get('/users');
|
|
107
|
+
await client.post('/users', { name: 'John' });
|
|
108
|
+
await client.put('/users/1', { name: 'Jane' });
|
|
109
|
+
await client.delete('/users/1');
|
|
110
|
+
|
|
111
|
+
// Manage auth token dynamically
|
|
112
|
+
authClient.setAuthToken('new-token');
|
|
113
|
+
authClient.removeAuthToken();
|
|
114
|
+
|
|
115
|
+
// Access underlying axios instance
|
|
116
|
+
const axiosInstance = client.getInstance();
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. S3 Service
|
|
120
|
+
|
|
121
|
+
File operations with AWS S3.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { S3Service } from '@rempays/shared-core/s3';
|
|
125
|
+
|
|
126
|
+
// Upload file
|
|
127
|
+
const s3Uri = await S3Service.uploadFile({
|
|
128
|
+
key: 'documents/file.pdf',
|
|
129
|
+
body: buffer,
|
|
130
|
+
contentType: 'application/pdf',
|
|
131
|
+
metadata: { userId: '123' }
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Download file
|
|
135
|
+
const fileBuffer = await S3Service.downloadFile('documents/file.pdf');
|
|
136
|
+
|
|
137
|
+
// Generate document key
|
|
138
|
+
const key = S3Service.generateDocumentKey({
|
|
139
|
+
chatId: 'chat123',
|
|
140
|
+
messageId: 'msg456',
|
|
141
|
+
extension: 'pdf'
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 5. Textract Service
|
|
146
|
+
|
|
147
|
+
Extract text from documents using AWS Textract.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { TextractService } from '@rempays/shared-core/textract';
|
|
151
|
+
|
|
152
|
+
// Analyze document from S3
|
|
153
|
+
const text = await TextractService.analyzeDocumentFromS3('my-bucket', 'document.pdf');
|
|
154
|
+
|
|
155
|
+
// Analyze document from bytes
|
|
156
|
+
const imageBytes = new Uint8Array(buffer);
|
|
157
|
+
const extractedText = await TextractService.analyzeDocumentFromBytes(imageBytes);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 6. Facebook API (WhatsApp)
|
|
161
|
+
|
|
162
|
+
Send messages via WhatsApp Business API.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { FacebookApi } from '@rempays/shared-core/facebook-api';
|
|
166
|
+
|
|
167
|
+
// Send text message
|
|
168
|
+
await FacebookApi.sendText({
|
|
169
|
+
to: '+1234567890',
|
|
170
|
+
body: 'Hello from RemPays!',
|
|
171
|
+
previewUrl: true
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Send template
|
|
175
|
+
await FacebookApi.sendTemplate({
|
|
176
|
+
to: '+1234567890',
|
|
177
|
+
name: 'welcome_template',
|
|
178
|
+
languageCode: 'en',
|
|
179
|
+
components: []
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Send interactive buttons
|
|
183
|
+
await FacebookApi.sendInteractiveButtons({
|
|
184
|
+
to: '+1234567890',
|
|
185
|
+
body: 'Choose an option:',
|
|
186
|
+
buttons: [
|
|
187
|
+
{ type: 'reply', id: 'opt1', title: 'Option 1' },
|
|
188
|
+
{ type: 'reply', id: 'opt2', title: 'Option 2' }
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Send interactive list
|
|
193
|
+
await FacebookApi.sendInteractiveList({
|
|
194
|
+
to: '+1234567890',
|
|
195
|
+
body: 'Select from menu:',
|
|
196
|
+
buttonText: 'View Menu',
|
|
197
|
+
sections: [
|
|
198
|
+
{
|
|
199
|
+
title: 'Main Options',
|
|
200
|
+
rows: [
|
|
201
|
+
{ id: '1', title: 'Option 1', description: 'Description 1' },
|
|
202
|
+
{ id: '2', title: 'Option 2', description: 'Description 2' }
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Send image
|
|
209
|
+
await FacebookApi.sendImage({
|
|
210
|
+
to: '+1234567890',
|
|
211
|
+
link: 'https://example.com/image.jpg',
|
|
212
|
+
caption: 'Check this out!'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Send document
|
|
216
|
+
await FacebookApi.sendDocument({
|
|
217
|
+
to: '+1234567890',
|
|
218
|
+
link: 'https://example.com/document.pdf',
|
|
219
|
+
filename: 'invoice.pdf',
|
|
220
|
+
caption: 'Your invoice'
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Mark as read
|
|
224
|
+
await FacebookApi.markAsRead({ messageId: 'msg_id' });
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Environment Variables
|
|
228
|
+
|
|
229
|
+
### Cognito
|
|
230
|
+
```bash
|
|
231
|
+
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
|
|
232
|
+
COGNITO_CLIENT_ID=XXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
233
|
+
AWS_REGION=us-east-1
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Facebook API (WhatsApp)
|
|
237
|
+
```bash
|
|
238
|
+
FACEBOOK_API_TOKEN=your_facebook_token
|
|
239
|
+
FACEBOOK_PHONE_NUMBER_ID=your_phone_number_id
|
|
240
|
+
FACEBOOK_API_VERSION=v18.0 # Optional, defaults to v18.0
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Available Methods Summary
|
|
244
|
+
|
|
245
|
+
### CognitoService
|
|
246
|
+
- `signUp(params)` - Register new user
|
|
247
|
+
- `confirmSignUp(phoneNumber, code)` - Confirm registration with OTP
|
|
248
|
+
- `signInCustomAuth(params)` - Sign in with custom authentication
|
|
249
|
+
- `signInSRP(params)` - Sign in with SRP
|
|
250
|
+
- `respondToAuthChallenge(params)` - Verify OTP code
|
|
251
|
+
- `refreshToken(params)` - Refresh access tokens
|
|
252
|
+
- `getUser(accessToken)` - Get authenticated user info
|
|
253
|
+
- `adminGetUser(phoneNumber)` - Get user info (admin)
|
|
254
|
+
- `adminInitiateAuth(phoneNumber)` - Initiate auth as admin
|
|
255
|
+
|
|
256
|
+
### JwtValidator
|
|
257
|
+
- `validate(token)` - Validate JWT token
|
|
258
|
+
- `validateFromHeader(authHeader)` - Validate token from Authorization header
|
|
259
|
+
- `extractToken(authHeader)` - Extract token from Bearer header
|
|
260
|
+
|
|
261
|
+
### HttpClient
|
|
262
|
+
- `get<T>(url, config?)` - GET request
|
|
263
|
+
- `post<T>(url, data?, config?)` - POST request
|
|
264
|
+
- `put<T>(url, data?, config?)` - PUT request
|
|
265
|
+
- `patch<T>(url, data?, config?)` - PATCH request
|
|
266
|
+
- `delete<T>(url, config?)` - DELETE request
|
|
267
|
+
- `setAuthToken(token)` - Set Bearer token
|
|
268
|
+
- `removeAuthToken()` - Remove Bearer token
|
|
269
|
+
- `getInstance()` - Get axios instance
|
|
270
|
+
|
|
271
|
+
### S3Service
|
|
272
|
+
- `uploadFile(params)` - Upload file to S3
|
|
273
|
+
- `downloadFile(key)` - Download file from S3
|
|
274
|
+
- `generateDocumentKey(params)` - Generate unique document key
|
|
275
|
+
|
|
276
|
+
### TextractService
|
|
277
|
+
- `analyzeDocumentFromS3(bucket, key)` - Extract text from S3 document
|
|
278
|
+
- `analyzeDocumentFromBytes(bytes)` - Extract text from byte array
|
|
279
|
+
|
|
280
|
+
### FacebookApi
|
|
281
|
+
- `sendText(params)` - Send text message
|
|
282
|
+
- `sendTemplate(params)` - Send template message
|
|
283
|
+
- `sendInteractiveButtons(params)` - Send interactive buttons
|
|
284
|
+
- `sendInteractiveList(params)` - Send interactive list
|
|
285
|
+
- `sendImage(params)` - Send image
|
|
286
|
+
- `sendDocument(params)` - Send document
|
|
287
|
+
- `sendLocation(params)` - Send location
|
|
288
|
+
- `markAsRead(params)` - Mark message as read
|
|
289
|
+
- `setTyping(params)` - Set typing indicator
|
|
290
|
+
|
|
291
|
+
## IAM Permissions Required
|
|
292
|
+
|
|
293
|
+
Your Lambda or application needs these permissions:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"Version": "2012-10-17",
|
|
298
|
+
"Statement": [
|
|
299
|
+
{
|
|
300
|
+
"Effect": "Allow",
|
|
301
|
+
"Action": [
|
|
302
|
+
"cognito-idp:SignUp",
|
|
303
|
+
"cognito-idp:ConfirmSignUp",
|
|
304
|
+
"cognito-idp:InitiateAuth",
|
|
305
|
+
"cognito-idp:RespondToAuthChallenge",
|
|
306
|
+
"cognito-idp:GetUser",
|
|
307
|
+
"cognito-idp:AdminGetUser",
|
|
308
|
+
"cognito-idp:AdminInitiateAuth"
|
|
309
|
+
],
|
|
310
|
+
"Resource": "arn:aws:cognito-idp:REGION:ACCOUNT_ID:userpool/USER_POOL_ID"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"Effect": "Allow",
|
|
314
|
+
"Action": [
|
|
315
|
+
"s3:GetObject",
|
|
316
|
+
"s3:PutObject"
|
|
317
|
+
],
|
|
318
|
+
"Resource": "arn:aws:s3:::YOUR_BUCKET/*"
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"Effect": "Allow",
|
|
322
|
+
"Action": [
|
|
323
|
+
"textract:AnalyzeDocument"
|
|
324
|
+
],
|
|
325
|
+
"Resource": "*"
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Development
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Install dependencies
|
|
335
|
+
npm install
|
|
336
|
+
|
|
337
|
+
# Build
|
|
338
|
+
npm run build
|
|
339
|
+
|
|
340
|
+
# Watch mode
|
|
341
|
+
npm run dev
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
UNLICENSED
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ejemplo de uso del JwtValidator en un Lambda Authorizer
|
|
3
|
+
*
|
|
4
|
+
* Este archivo es solo de referencia, no se exporta en el paquete
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Lambda Authorizer Handler
|
|
8
|
+
*/
|
|
9
|
+
export declare const handler: (event: any) => Promise<{
|
|
10
|
+
principalId: string;
|
|
11
|
+
policyDocument: {
|
|
12
|
+
Version: string;
|
|
13
|
+
Statement: {
|
|
14
|
+
Action: string;
|
|
15
|
+
Effect: "Allow" | "Deny";
|
|
16
|
+
Resource: string;
|
|
17
|
+
}[];
|
|
18
|
+
};
|
|
19
|
+
context: {
|
|
20
|
+
userId: any;
|
|
21
|
+
username: any;
|
|
22
|
+
tokenUse: any;
|
|
23
|
+
} | undefined;
|
|
24
|
+
}>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ejemplo de uso del JwtValidator en un Lambda Authorizer
|
|
3
|
+
*
|
|
4
|
+
* Este archivo es solo de referencia, no se exporta en el paquete
|
|
5
|
+
*/
|
|
6
|
+
import { JwtValidator } from './jwt-validator';
|
|
7
|
+
// Configuración del validador
|
|
8
|
+
const validator = new JwtValidator({
|
|
9
|
+
region: process.env.AWS_REGION || 'us-east-1',
|
|
10
|
+
userPoolId: process.env.USER_POOL_ID,
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Lambda Authorizer Handler
|
|
14
|
+
*/
|
|
15
|
+
export const handler = async (event) => {
|
|
16
|
+
try {
|
|
17
|
+
// Extraer y validar el token
|
|
18
|
+
const token = event.headers?.Authorization || event.headers?.authorization;
|
|
19
|
+
const decoded = await validator.validateFromHeader(token);
|
|
20
|
+
// Token válido - generar policy de Allow
|
|
21
|
+
return generatePolicy(decoded.sub, 'Allow', event.methodArn, decoded);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('Token validation failed:', error);
|
|
25
|
+
// Token inválido - generar policy de Deny
|
|
26
|
+
return generatePolicy('user', 'Deny', event.methodArn);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Genera una policy de IAM para API Gateway
|
|
31
|
+
*/
|
|
32
|
+
function generatePolicy(principalId, effect, resource, context) {
|
|
33
|
+
return {
|
|
34
|
+
principalId,
|
|
35
|
+
policyDocument: {
|
|
36
|
+
Version: '2012-10-17',
|
|
37
|
+
Statement: [
|
|
38
|
+
{
|
|
39
|
+
Action: 'execute-api:Invoke',
|
|
40
|
+
Effect: effect,
|
|
41
|
+
Resource: resource,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
context: context ? {
|
|
46
|
+
userId: context.sub,
|
|
47
|
+
username: context.username,
|
|
48
|
+
tokenUse: context.token_use,
|
|
49
|
+
} : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { JwtValidator } from './jwt-validator';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { JwtValidatorConfig, DecodedToken } from './types';
|
|
2
|
+
export declare class JwtValidator {
|
|
3
|
+
private config;
|
|
4
|
+
private jwksCache;
|
|
5
|
+
private issuer;
|
|
6
|
+
constructor(config: JwtValidatorConfig);
|
|
7
|
+
/**
|
|
8
|
+
* Valida un token JWT de Cognito
|
|
9
|
+
*/
|
|
10
|
+
validate(token: string): Promise<DecodedToken>;
|
|
11
|
+
/**
|
|
12
|
+
* Decodifica el token sin verificar la firma
|
|
13
|
+
*/
|
|
14
|
+
private decodeToken;
|
|
15
|
+
/**
|
|
16
|
+
* Decodifica el header del token
|
|
17
|
+
*/
|
|
18
|
+
private decodeHeader;
|
|
19
|
+
/**
|
|
20
|
+
* Obtiene las claves públicas de Cognito (con caché)
|
|
21
|
+
*/
|
|
22
|
+
private getJWKS;
|
|
23
|
+
/**
|
|
24
|
+
* Verifica la firma del token
|
|
25
|
+
* NOTA: Esta es una implementación simplificada
|
|
26
|
+
* Para producción, usa una librería como 'jsonwebtoken' o 'jose'
|
|
27
|
+
*/
|
|
28
|
+
private verifySignature;
|
|
29
|
+
/**
|
|
30
|
+
* Extrae el token del header Authorization
|
|
31
|
+
*/
|
|
32
|
+
static extractToken(authHeader?: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Valida un token desde el header Authorization
|
|
35
|
+
*/
|
|
36
|
+
validateFromHeader(authHeader?: string): Promise<DecodedToken>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createHttpClient } from '../http/client';
|
|
2
|
+
export class JwtValidator {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.jwksCache = null;
|
|
5
|
+
this.config = config;
|
|
6
|
+
this.issuer = `https://cognito-idp.${config.region}.amazonaws.com/${config.userPoolId}`;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Valida un token JWT de Cognito
|
|
10
|
+
*/
|
|
11
|
+
async validate(token) {
|
|
12
|
+
// Decodificar el token sin verificar (para obtener el kid)
|
|
13
|
+
const decoded = this.decodeToken(token);
|
|
14
|
+
// Verificar expiración
|
|
15
|
+
const now = Math.floor(Date.now() / 1000);
|
|
16
|
+
if (decoded.exp < now) {
|
|
17
|
+
throw new Error('Token expired');
|
|
18
|
+
}
|
|
19
|
+
// Verificar issuer
|
|
20
|
+
if (decoded.iss !== this.issuer) {
|
|
21
|
+
throw new Error('Invalid token issuer');
|
|
22
|
+
}
|
|
23
|
+
// Obtener las claves públicas de Cognito
|
|
24
|
+
const jwks = await this.getJWKS();
|
|
25
|
+
// Obtener el header del token para encontrar el kid
|
|
26
|
+
const header = this.decodeHeader(token);
|
|
27
|
+
const key = jwks.keys.find(k => k.kid === header.kid);
|
|
28
|
+
if (!key) {
|
|
29
|
+
throw new Error('Public key not found');
|
|
30
|
+
}
|
|
31
|
+
// Verificar la firma (simplificado - en producción usa una librería como jsonwebtoken o jose)
|
|
32
|
+
await this.verifySignature(token, key);
|
|
33
|
+
return decoded;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Decodifica el token sin verificar la firma
|
|
37
|
+
*/
|
|
38
|
+
decodeToken(token) {
|
|
39
|
+
const parts = token.split('.');
|
|
40
|
+
if (parts.length !== 3) {
|
|
41
|
+
throw new Error('Invalid token format');
|
|
42
|
+
}
|
|
43
|
+
const payload = parts[1];
|
|
44
|
+
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
45
|
+
return decoded;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decodifica el header del token
|
|
49
|
+
*/
|
|
50
|
+
decodeHeader(token) {
|
|
51
|
+
const parts = token.split('.');
|
|
52
|
+
const header = parts[0];
|
|
53
|
+
return JSON.parse(Buffer.from(header, 'base64url').toString('utf8'));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Obtiene las claves públicas de Cognito (con caché)
|
|
57
|
+
*/
|
|
58
|
+
async getJWKS() {
|
|
59
|
+
if (this.jwksCache) {
|
|
60
|
+
return this.jwksCache;
|
|
61
|
+
}
|
|
62
|
+
const jwksUrl = `${this.issuer}/.well-known/jwks.json`;
|
|
63
|
+
const httpClient = createHttpClient();
|
|
64
|
+
this.jwksCache = await httpClient.get(jwksUrl);
|
|
65
|
+
return this.jwksCache;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Verifica la firma del token
|
|
69
|
+
* NOTA: Esta es una implementación simplificada
|
|
70
|
+
* Para producción, usa una librería como 'jsonwebtoken' o 'jose'
|
|
71
|
+
*/
|
|
72
|
+
async verifySignature(token, key) {
|
|
73
|
+
// Por ahora solo verificamos que el key existe
|
|
74
|
+
// En producción deberías usar crypto para verificar la firma RSA
|
|
75
|
+
if (!key.n || !key.e) {
|
|
76
|
+
throw new Error('Invalid public key');
|
|
77
|
+
}
|
|
78
|
+
// TODO: Implementar verificación real de firma RSA
|
|
79
|
+
// Puedes usar la librería 'jose' o 'jsonwebtoken' para esto
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extrae el token del header Authorization
|
|
83
|
+
*/
|
|
84
|
+
static extractToken(authHeader) {
|
|
85
|
+
if (!authHeader) {
|
|
86
|
+
throw new Error('Authorization header missing');
|
|
87
|
+
}
|
|
88
|
+
const parts = authHeader.split(' ');
|
|
89
|
+
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
|
90
|
+
throw new Error('Invalid authorization header format');
|
|
91
|
+
}
|
|
92
|
+
return parts[1];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Valida un token desde el header Authorization
|
|
96
|
+
*/
|
|
97
|
+
async validateFromHeader(authHeader) {
|
|
98
|
+
const token = JwtValidator.extractToken(authHeader);
|
|
99
|
+
return this.validate(token);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface JwtValidatorConfig {
|
|
2
|
+
region: string;
|
|
3
|
+
userPoolId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface DecodedToken {
|
|
6
|
+
sub: string;
|
|
7
|
+
token_use: 'access' | 'id';
|
|
8
|
+
scope?: string;
|
|
9
|
+
auth_time: number;
|
|
10
|
+
iss: string;
|
|
11
|
+
exp: number;
|
|
12
|
+
iat: number;
|
|
13
|
+
jti?: string;
|
|
14
|
+
client_id: string;
|
|
15
|
+
username: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
export interface JWK {
|
|
19
|
+
alg: string;
|
|
20
|
+
e: string;
|
|
21
|
+
kid: string;
|
|
22
|
+
kty: string;
|
|
23
|
+
n: string;
|
|
24
|
+
use: string;
|
|
25
|
+
}
|
|
26
|
+
export interface JWKS {
|
|
27
|
+
keys: JWK[];
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CognitoConfig, SignUpParams, SignInParams, VerifyCodeParams, RefreshTokenParams, AuthResponse, UserAttributes } from './types';
|
|
2
|
+
export declare class CognitoService {
|
|
3
|
+
private client;
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config: CognitoConfig);
|
|
6
|
+
/**
|
|
7
|
+
* Registra un nuevo usuario en Cognito
|
|
8
|
+
*/
|
|
9
|
+
signUp(params: SignUpParams): Promise<{
|
|
10
|
+
userSub: string;
|
|
11
|
+
codeDeliveryDetails?: any;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Confirma el registro de un usuario con el código OTP
|
|
15
|
+
*/
|
|
16
|
+
confirmSignUp(phoneNumber: string, code: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Inicia sesión con autenticación personalizada (CUSTOM_AUTH)
|
|
19
|
+
*/
|
|
20
|
+
signInCustomAuth(params: SignInParams): Promise<AuthResponse>;
|
|
21
|
+
/**
|
|
22
|
+
* Inicia sesión con SRP (Secure Remote Password)
|
|
23
|
+
*/
|
|
24
|
+
signInSRP(params: SignInParams): Promise<AuthResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Responde al desafío de autenticación (ej: verificar código OTP)
|
|
27
|
+
*/
|
|
28
|
+
respondToAuthChallenge(params: VerifyCodeParams): Promise<AuthResponse>;
|
|
29
|
+
/**
|
|
30
|
+
* Refresca el token de acceso usando el refresh token
|
|
31
|
+
*/
|
|
32
|
+
refreshToken(params: RefreshTokenParams): Promise<AuthResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* Obtiene información del usuario autenticado usando el access token
|
|
35
|
+
*/
|
|
36
|
+
getUser(accessToken: string): Promise<UserAttributes>;
|
|
37
|
+
/**
|
|
38
|
+
* Obtiene información del usuario por username (requiere permisos de admin)
|
|
39
|
+
*/
|
|
40
|
+
adminGetUser(phoneNumber: string): Promise<UserAttributes>;
|
|
41
|
+
/**
|
|
42
|
+
* Inicia autenticación como admin (útil para backend)
|
|
43
|
+
*/
|
|
44
|
+
adminInitiateAuth(phoneNumber: string): Promise<AuthResponse>;
|
|
45
|
+
}
|