@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
- "hosted-zone:account=192933325589:domainName=xcelsior.co:region=ap-southeast-2": {
3
- "Id": "/hostedzone/Z08595403217R035ZUO3M",
4
- "Name": "xcelsior.co."
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.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/monitoring": "1.0.4",
25
- "@xcelsior/lambda-http": "1.0.5"
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",
@@ -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
- function hexToBytes(hex) {
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
- async function handler(event) {
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 key = await crypto.subtle.importKey(
73
- 'raw',
74
- new TextEncoder().encode('${escaped}'),
75
- { name: 'HMAC', hash: 'SHA-256' },
76
- false,
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
- s3.HttpMethods.GET,
133
- s3.HttpMethods.PUT,
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