Files
uldp-degree-mangement-system/utils/signedUrl.js

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 };