forked from UKSOURCE/cms.hailearning.edu.vn
56 lines
1.6 KiB
JavaScript
56 lines
1.6 KiB
JavaScript
const crypto = require('crypto');
|
|
|
|
const SECRET = process.env.FILE_SIGN_SECRET || process.env.SESSION_SECRET || 'file-sign-secret';
|
|
const EXPIRY_SECONDS = 15 * 60; // 15 minutes
|
|
|
|
/**
|
|
* Generate a signed URL for a protected file
|
|
* @param {string} baseUrl - e.g. "https://host"
|
|
* @param {string} filename - basename of the file
|
|
* @returns {string} full signed URL
|
|
*/
|
|
function generateSignedUrl(baseUrl, filename) {
|
|
const expires = Math.floor(Date.now() / 1000) + EXPIRY_SECONDS;
|
|
const payload = `${filename}:${expires}`;
|
|
const token = crypto
|
|
.createHmac('sha256', SECRET)
|
|
.update(payload)
|
|
.digest('base64url');
|
|
|
|
return `${baseUrl}/secure-files/${encodeURIComponent(filename)}?token=${token}&expires=${expires}`;
|
|
}
|
|
|
|
/**
|
|
* Verify a signed URL token
|
|
* @param {string} filename
|
|
* @param {string} token
|
|
* @param {number} expires - unix timestamp
|
|
* @returns {{ valid: boolean, reason?: string }}
|
|
*/
|
|
function verifySignedUrl(filename, token, expires) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
if (!token || !expires) {
|
|
return { valid: false, reason: 'Missing token or expires' };
|
|
}
|
|
|
|
if (now > Number(expires)) {
|
|
return { valid: false, reason: 'URL has expired' };
|
|
}
|
|
|
|
const payload = `${filename}:${expires}`;
|
|
const expected = crypto
|
|
.createHmac('sha256', SECRET)
|
|
.update(payload)
|
|
.digest('base64url');
|
|
|
|
const valid = crypto.timingSafeEqual(
|
|
Buffer.from(token),
|
|
Buffer.from(expected)
|
|
);
|
|
|
|
return valid ? { valid: true } : { valid: false, reason: 'Invalid token' };
|
|
}
|
|
|
|
module.exports = { generateSignedUrl, verifySignedUrl };
|