Support authenticated GreenLens TikTok video uploads

This commit is contained in:
Timo
2026-07-13 11:58:12 +02:00
parent eb6f5f2200
commit 7c210524a9
3 changed files with 308 additions and 37 deletions

View File

@@ -0,0 +1,97 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { isAllowedR2MediaUrl, isOwnedTiktokMediaUrl, prepareTiktokPhotoUrls } = require('../lib/tiktok-assets');
const publicBaseUrl = 'https://greenlenspro.com/storage';
test('accepts only HTTPS media URLs under the configured GreenLens storage path', () => {
assert.equal(isOwnedTiktokMediaUrl('https://greenlenspro.com/storage/plant-images/slide.jpg', publicBaseUrl), true);
assert.equal(isOwnedTiktokMediaUrl('https://greenlenspro.com.evil.test/storage/slide.jpg', publicBaseUrl), false);
assert.equal(isOwnedTiktokMediaUrl('http://greenlenspro.com/storage/slide.jpg', publicBaseUrl), false);
assert.equal(isOwnedTiktokMediaUrl('https://greenlenspro.com/other/slide.jpg', publicBaseUrl), false);
});
test('keeps existing GreenLens storage URLs without uploading them again', async () => {
let uploads = 0;
const url = 'https://greenlenspro.com/storage/plant-images/slide.jpg';
const result = await prepareTiktokPhotoUrls([url], {
publicBaseUrl,
uploadImage: async () => { uploads += 1; },
});
assert.deepEqual(result, [url]);
assert.equal(uploads, 0);
});
test('hosts base64 photos on the verified GreenLens domain', async () => {
const uploaded = [];
const result = await prepareTiktokPhotoUrls([{ imageBase64: Buffer.from('source').toString('base64') }], {
publicBaseUrl,
normalizePhoto: async (buffer) => {
assert.equal(buffer.toString(), 'source');
return Buffer.from('jpeg');
},
uploadImage: async (base64, contentType) => {
uploaded.push({ base64, contentType });
return { url: 'https://greenlenspro.com/storage/plant-images/normalized.jpg' };
},
});
assert.deepEqual(result, ['https://greenlenspro.com/storage/plant-images/normalized.jpg']);
assert.deepEqual(uploaded, [{ base64: Buffer.from('jpeg').toString('base64'), contentType: 'image/jpeg' }]);
});
test('allows only HTTPS R2 source hosts without credentials or custom ports', () => {
assert.equal(isAllowedR2MediaUrl('https://pub-example.r2.dev/slide.jpg'), true);
assert.equal(isAllowedR2MediaUrl('http://pub-example.r2.dev/slide.jpg'), false);
assert.equal(isAllowedR2MediaUrl('https://user:pass@pub-example.r2.dev/slide.jpg'), false);
assert.equal(isAllowedR2MediaUrl('https://pub-example.r2.dev:8443/slide.jpg'), false);
assert.equal(isAllowedR2MediaUrl('https://r2.dev.evil.test/slide.jpg'), false);
});
test('rehosts an external R2 photo instead of leaking its URL into the TikTok payload', async () => {
const sourceUrl = 'https://pub-example.r2.dev/slide.png';
const result = await prepareTiktokPhotoUrls([sourceUrl], {
publicBaseUrl,
downloadPhoto: async (url) => {
assert.equal(url, sourceUrl);
return Buffer.from('png');
},
normalizePhoto: async () => Buffer.from('jpeg'),
uploadImage: async () => ({ url: 'https://greenlenspro.com/storage/plant-images/slide.jpg' }),
});
assert.deepEqual(result, ['https://greenlenspro.com/storage/plant-images/slide.jpg']);
assert.equal(result.includes(sourceUrl), false);
});
test('prefers supplied base64 over an external legacy URL', async () => {
let downloaded = false;
const result = await prepareTiktokPhotoUrls([{
url: 'https://pub-example.r2.dev/slide.png',
imageBase64: Buffer.from('local').toString('base64'),
}], {
publicBaseUrl,
downloadPhoto: async () => { downloaded = true; },
normalizePhoto: async (buffer) => {
assert.equal(buffer.toString(), 'local');
return Buffer.from('jpeg');
},
uploadImage: async () => ({ url: 'https://greenlenspro.com/storage/plant-images/slide.jpg' }),
});
assert.deepEqual(result, ['https://greenlenspro.com/storage/plant-images/slide.jpg']);
assert.equal(downloaded, false);
});
test('rejects a storage result outside the verified GreenLens domain', async () => {
await assert.rejects(
prepareTiktokPhotoUrls([{ imageBase64: Buffer.from('source').toString('base64') }], {
publicBaseUrl,
normalizePhoto: async () => Buffer.from('jpeg'),
uploadImage: async () => ({ url: 'https://pub-example.r2.dev/slide.jpg' }),
}),
/unverified public URL/,
);
});