instaserve 1.1.19 → 1.1.21
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/api.txt +4 -4
- package/lib/auth0.js +1 -1
- package/lib/r2.js +30 -2
- package/package.json +2 -2
- package/public/index.html +3 -3
- package/test_api.sh +59 -0
package/api.txt
CHANGED
|
@@ -41,7 +41,7 @@ POST /upload
|
|
|
41
41
|
Upload a file.
|
|
42
42
|
Body (JSON):
|
|
43
43
|
{
|
|
44
|
-
"bucket": "my-bucket-name",
|
|
44
|
+
"bucket": "my-bucket-name", // Optional if env var set
|
|
45
45
|
"key": "filename.jpg",
|
|
46
46
|
"body": "base64_encoded_string_contents",
|
|
47
47
|
"contentType": "image/jpeg"
|
|
@@ -49,13 +49,13 @@ POST /upload
|
|
|
49
49
|
|
|
50
50
|
POST /files
|
|
51
51
|
List files in a bucket.
|
|
52
|
-
Body (JSON): { "bucket": "my-bucket-name" }
|
|
52
|
+
Body (JSON): { "bucket": "my-bucket-name" } // Bucket optional if env var set
|
|
53
53
|
Returns: List of file objects (key, size, lastModified).
|
|
54
54
|
|
|
55
55
|
GET /download?bucket=my-bucket-name&key=filename.jpg
|
|
56
|
-
Download a file directly.
|
|
56
|
+
Download a file directly. Bucket optional if env var set.
|
|
57
57
|
(Available via the 'download' route handler)
|
|
58
58
|
|
|
59
59
|
GET /delete?bucket=my-bucket-name&key=filename.jpg
|
|
60
|
-
Delete a file.
|
|
60
|
+
Delete a file. Bucket optional if env var set.
|
|
61
61
|
(Available via the 'delete' route handler)
|
package/lib/auth0.js
CHANGED
|
@@ -41,7 +41,7 @@ export default {
|
|
|
41
41
|
if (token) {
|
|
42
42
|
global.sqlite.prepare("UPDATE users SET token = NULL WHERE token = ?").run(token);
|
|
43
43
|
}
|
|
44
|
-
res.setHeader("Set-Cookie", "token=; Path=/; HttpOnly; Secure; SameSite=
|
|
44
|
+
res.setHeader("Set-Cookie", "token=; Path=/; HttpOnly; Secure; SameSite=Strict");
|
|
45
45
|
res.writeHead(302, { Location: LOGGEDOUT_REDIRECT });
|
|
46
46
|
res.end();
|
|
47
47
|
},
|
package/lib/r2.js
CHANGED
|
@@ -5,6 +5,7 @@ import { URL } from 'url';
|
|
|
5
5
|
const cloudflareAccessKeyId = process.env.CLOUDFLARE_ACCESS_KEY_ID || process.env.cloudflareAccessKeyId;
|
|
6
6
|
const cloudflareSecretAccessKey = process.env.CLOUDFLARE_SECRET_ACCESS_KEY || process.env.cloudflareSecretAccessKey;
|
|
7
7
|
const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID || process.env.cloudflareAccountId;
|
|
8
|
+
const cloudflareBucket = process.env.CLOUDFLARE_BUCKET || process.env.cloudflareBucket || process.env.CLOUDFLARE_BUCKET_NAME || process.env.cloudflareBucketName;
|
|
8
9
|
|
|
9
10
|
if (!cloudflareAccessKeyId || !cloudflareSecretAccessKey || !cloudflareAccountId) {
|
|
10
11
|
console.warn("Cloudflare environment variables not set! Please set CLOUDFLARE_ACCESS_KEY_ID, CLOUDFLARE_SECRET_ACCESS_KEY, and CLOUDFLARE_ACCOUNT_ID.");
|
|
@@ -152,6 +153,7 @@ const getSecrets = ({ cloudflareAccessKeyId, cloudflareSecretAccessKey, cloudfla
|
|
|
152
153
|
);
|
|
153
154
|
|
|
154
155
|
async function uploadToR2(bucket, key, body, contentType, secrets) {
|
|
156
|
+
bucket = bucket || cloudflareBucket;
|
|
155
157
|
if (!body) body = Buffer.alloc(0);
|
|
156
158
|
const signed = signR2('PUT', bucket, key, body, ...getSecrets(secrets));
|
|
157
159
|
signed.headers['Content-Type'] = contentType || 'application/octet-stream';
|
|
@@ -160,6 +162,7 @@ async function uploadToR2(bucket, key, body, contentType, secrets) {
|
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
async function downloadFromR2(bucket, key, secrets) {
|
|
165
|
+
bucket = bucket || cloudflareBucket;
|
|
163
166
|
const signed = signR2('GET', bucket, key, null, ...getSecrets(secrets));
|
|
164
167
|
const { hostname, pathname, search } = new URL(signed.url);
|
|
165
168
|
|
|
@@ -187,11 +190,13 @@ async function downloadFromR2(bucket, key, secrets) {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
async function deleteFromR2(bucket, key, secrets) {
|
|
193
|
+
bucket = bucket || cloudflareBucket;
|
|
190
194
|
await requestR2(signR2('DELETE', bucket, key, null, ...getSecrets(secrets)));
|
|
191
195
|
return { success: true };
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
async function listR2Files(bucket, prefix, secrets) {
|
|
199
|
+
bucket = bucket || cloudflareBucket;
|
|
195
200
|
const qs = prefix ? `list-type=2&prefix=${encodeURIComponent(prefix)}` : 'list-type=2';
|
|
196
201
|
|
|
197
202
|
const data = await requestR2(signR2('GET', bucket, '', null, ...getSecrets(secrets), qs));
|
|
@@ -204,6 +209,7 @@ async function listR2Files(bucket, prefix, secrets) {
|
|
|
204
209
|
}
|
|
205
210
|
|
|
206
211
|
function getSignedDownloadUrl(bucket, key, secrets) {
|
|
212
|
+
bucket = bucket || cloudflareBucket;
|
|
207
213
|
return signR2('GET', bucket, key, null, ...getSecrets(secrets)).url;
|
|
208
214
|
}
|
|
209
215
|
|
|
@@ -229,8 +235,30 @@ export const fileRoutes = {
|
|
|
229
235
|
},
|
|
230
236
|
download: async (req, res, data) => {
|
|
231
237
|
try {
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
const r2Msg = await downloadFromR2(data.bucket, data.key, data.secrets || defaultSecrets);
|
|
239
|
+
res.writeHead(r2Msg.statusCode, r2Msg.statusMessage, r2Msg.headers);
|
|
240
|
+
|
|
241
|
+
await new Promise((resolve, reject) => {
|
|
242
|
+
r2Msg.pipe(res);
|
|
243
|
+
r2Msg.on('error', (err) => {
|
|
244
|
+
res.destroy(err);
|
|
245
|
+
reject(err);
|
|
246
|
+
});
|
|
247
|
+
res.on('finish', resolve);
|
|
248
|
+
res.on('error', reject);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// returning nothing to prevent sendResponse from interfering
|
|
252
|
+
return;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
// If headers weren't sent yet, we can return error JSON
|
|
255
|
+
if (!res.headersSent) {
|
|
256
|
+
return { error: e.message };
|
|
257
|
+
}
|
|
258
|
+
// If headers were sent, we can't cleanly return JSON,
|
|
259
|
+
// but the stream error handling above usually takes care of destroying the socket.
|
|
260
|
+
console.error("Download stream error:", e);
|
|
261
|
+
}
|
|
234
262
|
},
|
|
235
263
|
delete: async (req, res, data) => {
|
|
236
264
|
try {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instaserve",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
4
4
|
"description": "Instant web stack",
|
|
5
5
|
"main": "module.mjs",
|
|
6
6
|
"bin": "./instaserve",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"start": "node instaserve",
|
|
8
|
+
"start": "node instaserve -api ./routes-full.js -secure -port 3001 -public ./public",
|
|
9
9
|
"test": "./test_api.sh"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
package/public/index.html
CHANGED
|
@@ -258,7 +258,7 @@
|
|
|
258
258
|
|
|
259
259
|
<h3 class="section-title">R2 Storage</h3>
|
|
260
260
|
<div style="margin-top: 0.5rem;">
|
|
261
|
-
<input type="text" id="r2-bucket" placeholder="Bucket Name" class="input-field">
|
|
261
|
+
<input type="text" id="r2-bucket" placeholder="Bucket Name (Optional)" class="input-field">
|
|
262
262
|
<input type="file" id="r2-file" style="margin-bottom: 0.5rem; width: 100%;">
|
|
263
263
|
<div style="display: flex; gap: 0.5rem;">
|
|
264
264
|
<button onclick="uploadFile()" class="btn-sm">Upload File</button>
|
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
const bucket = val('r2-bucket');
|
|
322
322
|
const fileInput = document.getElementById('r2-file');
|
|
323
323
|
const file = fileInput.files[0];
|
|
324
|
-
if (!
|
|
324
|
+
if (!file) return log('Please select a file.');
|
|
325
325
|
|
|
326
326
|
const reader = new FileReader();
|
|
327
327
|
reader.onload = async () => {
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
|
|
339
339
|
async function listFiles() {
|
|
340
340
|
const bucket = val('r2-bucket');
|
|
341
|
-
|
|
341
|
+
|
|
342
342
|
await api('POST', '/files', { bucket });
|
|
343
343
|
}
|
|
344
344
|
</script>
|
package/test_api.sh
CHANGED
|
@@ -139,6 +139,65 @@ else
|
|
|
139
139
|
echo " ❌ FAIL: $RESPONSE"
|
|
140
140
|
fi
|
|
141
141
|
|
|
142
|
+
# File API Tests
|
|
143
|
+
TEST_FILE_KEY="test_file.txt"
|
|
144
|
+
TEST_FILE_CONTENT="Hello R2 File Test"
|
|
145
|
+
# Use base64 without newlines
|
|
146
|
+
TEST_FILE_B64=$(echo -n "$TEST_FILE_CONTENT" | base64 | tr -d '\n')
|
|
147
|
+
|
|
148
|
+
echo ""
|
|
149
|
+
echo "--- File API Tests ---"
|
|
150
|
+
|
|
151
|
+
# Test 5.1: Upload File
|
|
152
|
+
echo "- Testing POST /upload..."
|
|
153
|
+
RESPONSE=$(curl -k -s -X POST "$BASE_URL/upload" \
|
|
154
|
+
-H "Cookie: token=$TEST_TOKEN" \
|
|
155
|
+
-H "Content-Type: application/json" \
|
|
156
|
+
-d "{\"key\": \"$TEST_FILE_KEY\", \"body\": \"$TEST_FILE_B64\", \"contentType\": \"text/plain\"}")
|
|
157
|
+
|
|
158
|
+
if [[ "$RESPONSE" == *"true"* ]]; then
|
|
159
|
+
echo " ✅ PASS"
|
|
160
|
+
else
|
|
161
|
+
echo " ❌ FAIL: $RESPONSE"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Test 5.2: Download File
|
|
165
|
+
echo "- Testing GET /download..."
|
|
166
|
+
RESPONSE=$(curl -k -s "$BASE_URL/download?key=$TEST_FILE_KEY" \
|
|
167
|
+
-H "Cookie: token=$TEST_TOKEN")
|
|
168
|
+
|
|
169
|
+
if [[ "$RESPONSE" == *"$TEST_FILE_CONTENT"* ]]; then
|
|
170
|
+
echo " ✅ PASS: Content matches"
|
|
171
|
+
else
|
|
172
|
+
echo " ❌ FAIL: Content mismatch or error. Got: $RESPONSE"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
# Test 5.3: List Files
|
|
176
|
+
echo "- Testing POST /files (List)..."
|
|
177
|
+
RESPONSE=$(curl -k -s -X POST "$BASE_URL/files" \
|
|
178
|
+
-H "Cookie: token=$TEST_TOKEN" \
|
|
179
|
+
-H "Content-Type: application/json" \
|
|
180
|
+
-d "{}")
|
|
181
|
+
|
|
182
|
+
if [[ "$RESPONSE" == *"$TEST_FILE_KEY"* ]]; then
|
|
183
|
+
echo " ✅ PASS: Found file in list"
|
|
184
|
+
else
|
|
185
|
+
echo " ❌ FAIL: $RESPONSE"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Test 5.4: Delete File
|
|
189
|
+
echo "- Testing POST /delete..."
|
|
190
|
+
RESPONSE=$(curl -k -s -X POST "$BASE_URL/delete" \
|
|
191
|
+
-H "Cookie: token=$TEST_TOKEN" \
|
|
192
|
+
-H "Content-Type: application/json" \
|
|
193
|
+
-d "{\"key\": \"$TEST_FILE_KEY\"}")
|
|
194
|
+
|
|
195
|
+
if [[ "$RESPONSE" == *"true"* ]]; then
|
|
196
|
+
echo " ✅ PASS"
|
|
197
|
+
else
|
|
198
|
+
echo " ❌ FAIL: $RESPONSE"
|
|
199
|
+
fi
|
|
200
|
+
|
|
142
201
|
# 7. Test Login Redirect
|
|
143
202
|
echo "- Testing GET /login (Expect 302 Redirect to Auth0)..."
|
|
144
203
|
LOGIN_HTTP_CODE=$(curl -k -s -o /dev/null -w "%{http_code}" "$BASE_URL/login")
|