@xcelsior/storage-api 1.0.0 → 1.0.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/cdk.context.json +4 -4
- package/package.json +3 -3
- package/stacks/StorageStack.ts +43 -33
package/cdk.context.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
"hosted-zone:account=192933325589:domainName=xcelsior.co:region=ap-southeast-2": {
|
|
3
|
+
"Id": "/hostedzone/Z08595403217R035ZUO3M",
|
|
4
|
+
"Name": "xcelsior.co."
|
|
5
|
+
}
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcelsior/storage-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"packages/**",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@tsconfig/node18": "^18.2.2",
|
|
22
22
|
"@types/node": "^20.11.16",
|
|
23
23
|
"@xcelsior/aws": "1.0.6",
|
|
24
|
-
"@xcelsior/
|
|
25
|
-
"@xcelsior/
|
|
24
|
+
"@xcelsior/lambda-http": "1.0.5",
|
|
25
|
+
"@xcelsior/monitoring": "1.0.4"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "sst dev --stage dev --mode basic",
|
package/stacks/StorageStack.ts
CHANGED
|
@@ -42,14 +42,7 @@ function requireEnv(name: string): string {
|
|
|
42
42
|
function buildSecureAccessFunctionCode(signingSecret: string): string {
|
|
43
43
|
const escaped = signingSecret.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
44
44
|
return `
|
|
45
|
-
|
|
46
|
-
var len = hex.length / 2;
|
|
47
|
-
var out = new Uint8Array(len);
|
|
48
|
-
for (var i = 0; i < len; i++) {
|
|
49
|
-
out[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
45
|
+
import crypto from 'crypto';
|
|
53
46
|
|
|
54
47
|
function deny(msg) {
|
|
55
48
|
return {
|
|
@@ -60,7 +53,7 @@ function deny(msg) {
|
|
|
60
53
|
};
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
|
|
56
|
+
function handler(event) {
|
|
64
57
|
var request = event.request;
|
|
65
58
|
var tokenP = request.querystring['token'];
|
|
66
59
|
|
|
@@ -69,20 +62,11 @@ async function handler(event) {
|
|
|
69
62
|
}
|
|
70
63
|
|
|
71
64
|
try {
|
|
72
|
-
var
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
['verify']
|
|
78
|
-
);
|
|
79
|
-
var ok = await crypto.subtle.verify(
|
|
80
|
-
'HMAC',
|
|
81
|
-
key,
|
|
82
|
-
hexToBytes(tokenP.value),
|
|
83
|
-
new TextEncoder().encode(request.uri)
|
|
84
|
-
);
|
|
85
|
-
if (!ok) return deny('Invalid token.');
|
|
65
|
+
var expected = crypto.createHmac('sha256', '${escaped}')
|
|
66
|
+
.update(request.uri)
|
|
67
|
+
.digest('hex');
|
|
68
|
+
|
|
69
|
+
if (expected !== tokenP.value) return deny('Invalid token.');
|
|
86
70
|
} catch (_) {
|
|
87
71
|
return deny('Token validation failed.');
|
|
88
72
|
}
|
|
@@ -105,6 +89,37 @@ export function StorageStack({ stack }: StackContext) {
|
|
|
105
89
|
const maxFileSizeMb = Number(process.env.MAX_FILE_SIZE_MB || '100');
|
|
106
90
|
const presignedUrlExpiry = Number(process.env.PRESIGNED_URL_EXPIRY_SECONDS || '300');
|
|
107
91
|
|
|
92
|
+
// ── CORS configuration ──────────────────────────────────────────────────
|
|
93
|
+
// Comma-separated lists; default to permissive values.
|
|
94
|
+
const corsAllowedOrigins = (process.env.CORS_ALLOWED_ORIGINS || '*')
|
|
95
|
+
.split(',')
|
|
96
|
+
.map((s) => s.trim());
|
|
97
|
+
const corsAllowedHeaders = (process.env.CORS_ALLOWED_HEADERS || '*')
|
|
98
|
+
.split(',')
|
|
99
|
+
.map((s) => s.trim());
|
|
100
|
+
const corsExposedHeaders = (process.env.CORS_EXPOSED_HEADERS || 'ETag')
|
|
101
|
+
.split(',')
|
|
102
|
+
.map((s) => s.trim());
|
|
103
|
+
const corsMaxAge = Number(process.env.CORS_MAX_AGE || '86400');
|
|
104
|
+
|
|
105
|
+
const validMethods: Record<string, s3.HttpMethods> = {
|
|
106
|
+
GET: s3.HttpMethods.GET,
|
|
107
|
+
PUT: s3.HttpMethods.PUT,
|
|
108
|
+
POST: s3.HttpMethods.POST,
|
|
109
|
+
HEAD: s3.HttpMethods.HEAD,
|
|
110
|
+
DELETE: s3.HttpMethods.DELETE,
|
|
111
|
+
};
|
|
112
|
+
const corsAllowedMethods = (process.env.CORS_ALLOWED_METHODS || 'GET,PUT,POST,HEAD')
|
|
113
|
+
.split(',')
|
|
114
|
+
.map((m) => {
|
|
115
|
+
const method = validMethods[m.trim().toUpperCase()];
|
|
116
|
+
if (!method)
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Invalid CORS method: "${m.trim()}". Valid values: ${Object.keys(validMethods).join(', ')}`
|
|
119
|
+
);
|
|
120
|
+
return method;
|
|
121
|
+
});
|
|
122
|
+
|
|
108
123
|
// ── Secure-bucket access secret ─────────────────────────────────────────
|
|
109
124
|
const accessSecret = requireEnv('SECURE_STORAGE_ACCESS_SECRET');
|
|
110
125
|
|
|
@@ -126,16 +141,11 @@ export function StorageStack({ stack }: StackContext) {
|
|
|
126
141
|
|
|
127
142
|
const sharedCorsRules: s3.CorsRule[] = [
|
|
128
143
|
{
|
|
129
|
-
allowedOrigins:
|
|
130
|
-
allowedHeaders:
|
|
131
|
-
allowedMethods:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
s3.HttpMethods.POST,
|
|
135
|
-
s3.HttpMethods.HEAD,
|
|
136
|
-
],
|
|
137
|
-
maxAge: 86400, // 1 day
|
|
138
|
-
exposedHeaders: ['ETag'],
|
|
144
|
+
allowedOrigins: corsAllowedOrigins,
|
|
145
|
+
allowedHeaders: corsAllowedHeaders,
|
|
146
|
+
allowedMethods: corsAllowedMethods,
|
|
147
|
+
maxAge: corsMaxAge,
|
|
148
|
+
exposedHeaders: corsExposedHeaders,
|
|
139
149
|
},
|
|
140
150
|
];
|
|
141
151
|
|