@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39
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 +662 -658
- package/package.json +10 -3
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +12 -1
- package/src/services/lambda/simulator.js +4 -0
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- package/aws-config +0 -154
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const { DateTime } = require('luxon');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Signed URL/Cookie Generator for CloudFront
|
|
6
|
+
* Implements AWS CloudFront's signing mechanisms for URLs and cookies
|
|
7
|
+
* Supports both Canned Policies (simple TTL) and Custom Policies (advanced restrictions)
|
|
8
|
+
*/
|
|
9
|
+
class SignedUrlGenerator {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.algorithmIdentifier = 'AWS4-HMAC-SHA256';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate a canned signed URL (simple TTL-based)
|
|
16
|
+
* @param {string} distributionUrl - Base CloudFront distribution URL or CNAME
|
|
17
|
+
* @param {string} resourcePath - Path to the resource (e.g., /videos/movie.mp4)
|
|
18
|
+
* @param {string} privateKey - Private key in PEM format
|
|
19
|
+
* @param {string} keyPairId - CloudFront key pair ID
|
|
20
|
+
* @param {Date|number} expirationTime - When the URL expires (Date or Unix timestamp)
|
|
21
|
+
* @returns {string} Signed URL with Signature, Key-Pair-Id, and Policy query parameters
|
|
22
|
+
*/
|
|
23
|
+
generateCannedUrl(
|
|
24
|
+
distributionUrl,
|
|
25
|
+
resourcePath,
|
|
26
|
+
privateKey,
|
|
27
|
+
keyPairId,
|
|
28
|
+
expirationTime
|
|
29
|
+
) {
|
|
30
|
+
const expiration = this._normalizeExpiration(expirationTime);
|
|
31
|
+
|
|
32
|
+
// Build the resource URL
|
|
33
|
+
const baseUrl = this._normalizeUrl(distributionUrl);
|
|
34
|
+
const fullUrl = baseUrl + resourcePath;
|
|
35
|
+
|
|
36
|
+
// Create the policy string for canned policy
|
|
37
|
+
const policy = {
|
|
38
|
+
Statement: [
|
|
39
|
+
{
|
|
40
|
+
Resource: fullUrl,
|
|
41
|
+
Condition: {
|
|
42
|
+
DateLessThan: {
|
|
43
|
+
'AWS:EpochTime': Math.floor(expiration / 1000)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const policyString = JSON.stringify(policy);
|
|
51
|
+
const signature = this._signPolicy(policyString, privateKey);
|
|
52
|
+
const encodedPolicy = Buffer.from(policyString).toString('base64');
|
|
53
|
+
const encodedSignature = this._sanitizeBase64(signature);
|
|
54
|
+
|
|
55
|
+
// Build signed URL
|
|
56
|
+
const separator = fullUrl.includes('?') ? '&' : '?';
|
|
57
|
+
return `${fullUrl}${separator}Signature=${encodedSignature}&Key-Pair-Id=${keyPairId}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate a custom signed URL (advanced policy with multiple restrictions)
|
|
62
|
+
* @param {string} distributionUrl - Base CloudFront distribution URL or CNAME
|
|
63
|
+
* @param {string} resourcePath - Path to the resource (wildcard paths supported)
|
|
64
|
+
* @param {string} privateKey - Private key in PEM format
|
|
65
|
+
* @param {string} keyPairId - CloudFront key pair ID
|
|
66
|
+
* @param {object} options - Policy options
|
|
67
|
+
* @param {Date|number} options.expirationTime - URL expiration time
|
|
68
|
+
* @param {Date|number} [options.notBefore] - Time before which URL is not valid
|
|
69
|
+
* @param {string|string[]} [options.ipRange] - Allowed IP address(es) (CIDR format)
|
|
70
|
+
* @returns {object} Object with signed URL, policy, and signature
|
|
71
|
+
*/
|
|
72
|
+
generateCustomUrl(
|
|
73
|
+
distributionUrl,
|
|
74
|
+
resourcePath,
|
|
75
|
+
privateKey,
|
|
76
|
+
keyPairId,
|
|
77
|
+
options = {}
|
|
78
|
+
) {
|
|
79
|
+
const expiration = this._normalizeExpiration(options.expirationTime);
|
|
80
|
+
const baseUrl = this._normalizeUrl(distributionUrl);
|
|
81
|
+
const fullUrl = baseUrl + resourcePath;
|
|
82
|
+
|
|
83
|
+
// Build custom policy
|
|
84
|
+
const condition = {
|
|
85
|
+
DateLessThan: {
|
|
86
|
+
'AWS:EpochTime': Math.floor(expiration / 1000)
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (options.notBefore) {
|
|
91
|
+
const notBefore = this._normalizeExpiration(options.notBefore);
|
|
92
|
+
condition.DateGreaterThan = {
|
|
93
|
+
'AWS:EpochTime': Math.floor(notBefore / 1000)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (options.ipRange) {
|
|
98
|
+
const ipAddresses = Array.isArray(options.ipRange)
|
|
99
|
+
? options.ipRange
|
|
100
|
+
: [options.ipRange];
|
|
101
|
+
condition.IpAddress = {
|
|
102
|
+
'AWS:SourceIp': ipAddresses
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const policy = {
|
|
107
|
+
Statement: [
|
|
108
|
+
{
|
|
109
|
+
Resource: fullUrl,
|
|
110
|
+
Condition: condition
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const policyString = JSON.stringify(policy);
|
|
116
|
+
const signature = this._signPolicy(policyString, privateKey);
|
|
117
|
+
const encodedPolicy = this._sanitizeBase64(Buffer.from(policyString).toString('base64'));
|
|
118
|
+
const encodedSignature = this._sanitizeBase64(signature);
|
|
119
|
+
|
|
120
|
+
// Build signed URL with custom policy
|
|
121
|
+
const separator = fullUrl.includes('?') ? '&' : '?';
|
|
122
|
+
const signedUrl = `${fullUrl}${separator}Policy=${encodedPolicy}&Signature=${encodedSignature}&Key-Pair-Id=${keyPairId}`;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
url: signedUrl,
|
|
126
|
+
policy: policyString,
|
|
127
|
+
signature: encodedSignature,
|
|
128
|
+
keyPairId: keyPairId
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate signed cookies (for multiple files or alternative to URL signing)
|
|
134
|
+
* @param {string} privateKey - Private key in PEM format
|
|
135
|
+
* @param {string} keyPairId - CloudFront key pair ID
|
|
136
|
+
* @param {string} resourcePath - Path pattern (e.g., /private/*, /videos/*)
|
|
137
|
+
* @param {Date|number} expirationTime - When cookies expire
|
|
138
|
+
* @param {object} [options] - Additional options
|
|
139
|
+
* @param {Date|number} [options.notBefore] - Time before which cookies are not valid
|
|
140
|
+
* @param {string|string[]} [options.ipRange] - Allowed IP address(es)
|
|
141
|
+
* @returns {object} Object with CloudFront-Policy, CloudFront-Signature, and CloudFront-Key-Pair-Id cookies
|
|
142
|
+
*/
|
|
143
|
+
generateCookies(
|
|
144
|
+
privateKey,
|
|
145
|
+
keyPairId,
|
|
146
|
+
resourcePath,
|
|
147
|
+
expirationTime,
|
|
148
|
+
options = {}
|
|
149
|
+
) {
|
|
150
|
+
const expiration = this._normalizeExpiration(expirationTime);
|
|
151
|
+
|
|
152
|
+
// Build policy for cookies
|
|
153
|
+
const condition = {
|
|
154
|
+
DateLessThan: {
|
|
155
|
+
'AWS:EpochTime': Math.floor(expiration / 1000)
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
if (options.notBefore) {
|
|
160
|
+
const notBefore = this._normalizeExpiration(options.notBefore);
|
|
161
|
+
condition.DateGreaterThan = {
|
|
162
|
+
'AWS:EpochTime': Math.floor(notBefore / 1000)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (options.ipRange) {
|
|
167
|
+
const ipAddresses = Array.isArray(options.ipRange)
|
|
168
|
+
? options.ipRange
|
|
169
|
+
: [options.ipRange];
|
|
170
|
+
condition.IpAddress = {
|
|
171
|
+
'AWS:SourceIp': ipAddresses
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const policy = {
|
|
176
|
+
Statement: [
|
|
177
|
+
{
|
|
178
|
+
Resource: resourcePath,
|
|
179
|
+
Condition: condition
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const policyString = JSON.stringify(policy);
|
|
185
|
+
const signature = this._signPolicy(policyString, privateKey);
|
|
186
|
+
const encodedPolicy = this._sanitizeBase64(Buffer.from(policyString).toString('base64'));
|
|
187
|
+
const encodedSignature = this._sanitizeBase64(signature);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
'CloudFront-Policy': encodedPolicy,
|
|
191
|
+
'CloudFront-Signature': encodedSignature,
|
|
192
|
+
'CloudFront-Key-Pair-Id': keyPairId,
|
|
193
|
+
policyObject: policy
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Verify a signed URL or validate its signature
|
|
199
|
+
* @param {string} signedUrl - The signed URL to verify
|
|
200
|
+
* @param {string} publicKey - Public key in PEM format
|
|
201
|
+
* @returns {object} Verification result with isValid, policy, and error details
|
|
202
|
+
*/
|
|
203
|
+
verifySignedUrl(signedUrl, publicKey) {
|
|
204
|
+
try {
|
|
205
|
+
const url = new URL(signedUrl);
|
|
206
|
+
const params = new URLSearchParams(url.search);
|
|
207
|
+
|
|
208
|
+
// Extract signature components
|
|
209
|
+
const signature = params.get('Signature');
|
|
210
|
+
const policy = params.get('Policy');
|
|
211
|
+
const keyPairId = params.get('Key-Pair-Id');
|
|
212
|
+
|
|
213
|
+
if (!signature || !keyPairId) {
|
|
214
|
+
return {
|
|
215
|
+
isValid: false,
|
|
216
|
+
error: 'Missing required signature parameters'
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!policy) {
|
|
221
|
+
return {
|
|
222
|
+
isValid: false,
|
|
223
|
+
error: 'Missing Policy parameter'
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Decode policy
|
|
228
|
+
const decodedPolicy = Buffer.from(policy, 'base64').toString('utf-8');
|
|
229
|
+
let policyObject;
|
|
230
|
+
try {
|
|
231
|
+
policyObject = JSON.parse(decodedPolicy);
|
|
232
|
+
} catch (e) {
|
|
233
|
+
return {
|
|
234
|
+
isValid: false,
|
|
235
|
+
error: 'Invalid policy JSON'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Verify signature
|
|
240
|
+
const isValid = this._verifySignature(decodedPolicy, signature, publicKey);
|
|
241
|
+
|
|
242
|
+
if (!isValid) {
|
|
243
|
+
return {
|
|
244
|
+
isValid: false,
|
|
245
|
+
error: 'Invalid signature'
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check expiration
|
|
250
|
+
const statement = policyObject.Statement[0];
|
|
251
|
+
if (statement && statement.Condition && statement.Condition.DateLessThan) {
|
|
252
|
+
const expirationTimestamp = statement.Condition.DateLessThan['AWS:EpochTime'];
|
|
253
|
+
const now = Math.floor(Date.now() / 1000);
|
|
254
|
+
|
|
255
|
+
if (now >= expirationTimestamp) {
|
|
256
|
+
return {
|
|
257
|
+
isValid: false,
|
|
258
|
+
error: 'URL has expired'
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Check not-before time
|
|
264
|
+
if (statement && statement.Condition && statement.Condition.DateGreaterThan) {
|
|
265
|
+
const notBeforeTimestamp = statement.Condition.DateGreaterThan['AWS:EpochTime'];
|
|
266
|
+
const now = Math.floor(Date.now() / 1000);
|
|
267
|
+
|
|
268
|
+
if (now < notBeforeTimestamp) {
|
|
269
|
+
return {
|
|
270
|
+
isValid: false,
|
|
271
|
+
error: 'URL is not yet valid'
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
isValid: true,
|
|
278
|
+
policy: policyObject,
|
|
279
|
+
keyPairId: keyPairId,
|
|
280
|
+
resource: statement?.Resource
|
|
281
|
+
};
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return {
|
|
284
|
+
isValid: false,
|
|
285
|
+
error: `Verification failed: ${error.message}`
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Validate an IP against CIDR notation
|
|
292
|
+
* @param {string} ip - IP address to check
|
|
293
|
+
* @param {string} cidr - CIDR notation (e.g., 192.0.2.0/24)
|
|
294
|
+
* @returns {boolean} True if IP is in CIDR range
|
|
295
|
+
*/
|
|
296
|
+
isIpInCidr(ip, cidr) {
|
|
297
|
+
try {
|
|
298
|
+
const [network, bits] = cidr.split('/');
|
|
299
|
+
const networkBits = parseInt(bits, 10);
|
|
300
|
+
|
|
301
|
+
const ipParts = ip.split('.').map(Number);
|
|
302
|
+
const networkParts = network.split('.').map(Number);
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < 4; i++) {
|
|
305
|
+
const ipBits = ipParts[i] * Math.pow(2, 8 - (i + 1) * 8);
|
|
306
|
+
const networkBits_ = networkParts[i] * Math.pow(2, 8 - (i + 1) * 8);
|
|
307
|
+
const mask = (0xffffffff << (32 - networkBits)) >>> 0;
|
|
308
|
+
|
|
309
|
+
if ((ipBits & mask) !== (networkBits_ & mask)) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return true;
|
|
314
|
+
} catch (e) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Private Methods
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
_normalizeUrl(url) {
|
|
324
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
325
|
+
url = 'https://' + url;
|
|
326
|
+
}
|
|
327
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_normalizeExpiration(expirationTime) {
|
|
331
|
+
if (typeof expirationTime === 'number') {
|
|
332
|
+
// If it's less than 10^10, assume it's in seconds; otherwise milliseconds
|
|
333
|
+
return expirationTime < 10000000000 ? expirationTime * 1000 : expirationTime;
|
|
334
|
+
}
|
|
335
|
+
if (expirationTime instanceof Date) {
|
|
336
|
+
return expirationTime.getTime();
|
|
337
|
+
}
|
|
338
|
+
throw new Error('Expiration time must be a Date or Unix timestamp');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_signPolicy(policyString, privateKey) {
|
|
342
|
+
const signature = crypto
|
|
343
|
+
.createSign('RSA-SHA1')
|
|
344
|
+
.update(policyString)
|
|
345
|
+
.sign(privateKey, 'base64');
|
|
346
|
+
return signature;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
_verifySignature(policyString, signature, publicKey) {
|
|
350
|
+
try {
|
|
351
|
+
return crypto
|
|
352
|
+
.createVerify('RSA-SHA1')
|
|
353
|
+
.update(policyString)
|
|
354
|
+
.verify(publicKey, signature, 'base64');
|
|
355
|
+
} catch (e) {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
_sanitizeBase64(base64String) {
|
|
361
|
+
// CloudFront uses URL-safe base64: replace +/= with -_~
|
|
362
|
+
return base64String
|
|
363
|
+
.replace(/\+/g, '-')
|
|
364
|
+
.replace(/\//g, '_')
|
|
365
|
+
.replace(/=/g, '~');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
_unsanitizeBase64(base64String) {
|
|
369
|
+
// Reverse the URL-safe encoding
|
|
370
|
+
return base64String
|
|
371
|
+
.replace(/-/g, '+')
|
|
372
|
+
.replace(/_/g, '/')
|
|
373
|
+
.replace(/~/g, '=');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
module.exports = SignedUrlGenerator;
|