diff --git a/src/constant.ts b/src/constant.ts index 82331dc..a7bfcc2 100644 --- a/src/constant.ts +++ b/src/constant.ts @@ -1,3 +1,4 @@ export const SERVICE_URL = 'pb.nekoid.cc'; export const PASTE_WEB_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/frontend'; export const UUID_LENGTH = 4; +export const CORS_DOMAIN = 'nekoid.cc'; diff --git a/src/index.ts b/src/index.ts index 203efc9..b8aeb39 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,7 +22,7 @@ import { Router, error } from 'itty-router'; import { ERequest, Env, PasteIndexEntry, PASTE_TYPES } from './types'; import { serve_static } from './proxy'; import { check_password_rules, get_paste_info, get_basic_auth, gen_id } from './utils'; -import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL } from './constant'; +import { UUID_LENGTH, PASTE_WEB_URL, SERVICE_URL, CORS_DOMAIN } from './constant'; import { get_presign_url, router as large_upload } from './v2/large_upload'; const router = Router(); @@ -44,7 +44,7 @@ router.options('*', (request) => { if (!request.origin) return new Response(null); const url = new URL(request.origin); // Allow all subdomain of nekoid.cc - if (url.hostname.endsWith('nekoid.cc')) { + if (url.hostname.endsWith(CORS_DOMAIN)) { return new Response(null, { status: 204, headers: { @@ -217,6 +217,7 @@ router.post('/', async (request, env, ctx) => { const s3 = new AwsClient({ accessKeyId: env.AWS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY, + service: 's3', // required }); const res = await s3.fetch(`${env.ENDPOINT}/${uuid}`, { @@ -344,10 +345,18 @@ router.get('/:uuid/:option?', async (request, env, ctx) => { } const signed_url = await get_presign_url(uuid, descriptor, env); + + ctx.waitUntil( + env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), { + expiration: descriptor.expiration! / 1000, + }) + ); + return new Response(null, { status: 301, headers: { location: signed_url, + 'cache-control': 'no-store', }, }); } @@ -372,6 +381,7 @@ router.get('/:uuid/:option?', async (request, env, ctx) => { const s3 = new AwsClient({ accessKeyId: env.AWS_ACCESS_KEY_ID, secretAccessKey: env.AWS_SECRET_ACCESS_KEY, + service: 's3', // required }); // Fetch form origin if not hit cache let origin = await s3.fetch(`${env.ENDPOINT}/${uuid}`, { @@ -559,7 +569,7 @@ export default { if (!req.origin) return res; const url = new URL(req.origin); // Allow all subdomain of nekoid.cc - if (url.hostname.endsWith('nekoid.cc')) { + if (url.hostname.endsWith(CORS_DOMAIN)) { res.headers.set('Access-Control-Allow-Origin', url.origin); res.headers.set('Vary', 'Origin'); } diff --git a/src/types.d.ts b/src/types.d.ts index c9fb09f..dfb4677 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -22,7 +22,7 @@ export interface PasteIndexEntry { upload_completed?: boolean; sha256_hash?: string; cached_presigned_url?: string; - cached_presigned_url_expiration?: string; + cached_presigned_url_expiration?: number; } export interface Env { diff --git a/src/v2/large_upload.ts b/src/v2/large_upload.ts index 9916357..da72892 100644 --- a/src/v2/large_upload.ts +++ b/src/v2/large_upload.ts @@ -9,8 +9,17 @@ import { UUID_LENGTH } from '../constant'; export const router = Router({ base: '/v2/large_upload' }); export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, env: Env) { + // Use cached presigned url if expiration is more than 10 mins + if (descriptor.cached_presigned_url) { + const expiration = new Date(descriptor.cached_presigned_url_expiration ?? 0); + const time_to_renew = new Date(Date.now() + 600 * 1000); // 10 mins after + if (expiration >= time_to_renew) { + return descriptor.cached_presigned_url; + } + } + const endpoint_url = new URL(`${env.LARGE_DOWNLOAD_ENDPOINT}/${uuid}`); - endpoint_url.searchParams.set('X-Amz-Expires', '3600'); + endpoint_url.searchParams.set('X-Amz-Expires', '14400'); // Valid for 4 hours endpoint_url.searchParams.set( 'response-content-disposition', `inline; filename*=UTF-8''${encodeURIComponent(descriptor.title ?? uuid)}` @@ -21,7 +30,7 @@ export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, const s3 = new AwsClient({ accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!, secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!, - service: 's3', + service: 's3', // required }); const signed = await s3.sign(endpoint_url, { @@ -32,6 +41,9 @@ export async function get_presign_url(uuid: string, descriptor: PasteIndexEntry, }, }); + descriptor.cached_presigned_url = signed.url; + descriptor.cached_presigned_url_expiration = new Date(Date.now() + 14400 * 1000).getTime(); + return signed.url; } @@ -111,13 +123,13 @@ router.post('/create', async (request, env, ctx) => { const s3 = new AwsClient({ accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!, secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!, - service: 's3', + service: 's3', // required }); const current = Date.now(); const expiration = new Date(current + 14400 * 1000).getTime(); const endpoint_url = new URL(`${env.LARGE_ENDPOINT}/${uuid}`); - endpoint_url.searchParams.set('X-Amz-Expires', '14400'); + endpoint_url.searchParams.set('X-Amz-Expires', '900'); // Valid for 15 mins const required_headers = { 'Content-Length': file_size.toString(), 'X-Amz-Content-Sha256': file_hash, @@ -147,7 +159,7 @@ router.post('/create', async (request, env, ctx) => { title: file_title || undefined, mime_type: file_mime || undefined, last_modified: current, - expiration: new Date(Date.now() + 3600 * 1000).getTime(), + expiration: new Date(Date.now() + 900 * 1000).getTime(), password: password ? sha256(password).slice(0, 16) : undefined, read_count_remain: read_limit ?? undefined, type: 'large_paste', @@ -192,7 +204,7 @@ router.post('/complete/:uuid', async (request, env, ctx) => { const s3 = new AwsClient({ accessKeyId: env.LARGE_AWS_ACCESS_KEY_ID!, secretAccessKey: env.LARGE_AWS_SECRET_ACCESS_KEY!, - service: 's3', + service: 's3', // required }); try {