const secret = "THESECRET";
const validCenturyLinkPattern = new RegExp(
"/token=nva=(\\d+)~dirs=(\\d+)~hash=0([\\da-f]{20})(/.*)"
);
const validHash = new RegExp("[\\da-f]{20}");
async function verifyToken(req) {
const reqUrl = new URL(req.url);
const pathname = reqUrl.pathname;
const urlBasename = reqUrl.pathname.split(/[\\/]/).pop();
let nva;
let dirs;
let hash;
let path;
let expectedNumOfDirs;
let expectedHash;
if (/(playlist.m3u8)|(manifest.mpd)/.test(urlBasename)) {
if (validCenturyLinkPattern.test(pathname)) {
const centuryLinkSegments = pathname.match(validCenturyLinkPattern);
nva = centuryLinkSegments[1];
dirs = centuryLinkSegments[2];
hash = centuryLinkSegments[3];
path = centuryLinkSegments[4];
console.debug(`nva=${nva}; dirs=${dirs}; hash=${hash}; path=${path}`);
} else {
console.warn("Missing 'token' in URL path");
return new Response("Missing 'token' in URL path", {
status: 403,
statusText: "Forbidden"
});
}
const tokenAggregate = `${path}?nva=${nva}&dirs=${dirs}`;
console.debug(`token=${tokenAggregate}`);
expectedNumOfDirs = path.match(/\//g).length - 1;
console.debug(`expectedNumOfDirs=${expectedNumOfDirs} | dirs=${dirs}`);
if (dirs != expectedNumOfDirs) {
console.warn("Expected number of directories is invalid.");
return new Response("Expected number of directories is invalid.", {
status: 403,
statusText: "Forbidden"
});
}
const utf8Encoder = new TextEncoder();
const crypto = require("crypto");
const hmacSha1 = crypto.createHmac("sha1", utf8Encoder.encode(secret));
const digest = hmacSha1.update(tokenAggregate, "utf8").digest("hex");
console.debug(`digest: ${digest}`);
expectedHash = digest.match(validHash);
if (expectedHash.length == 1) {
console.debug(`expectedHash: ${expectedHash[0]}`);
if (expectedHash == hash) {
const normalizedTimeInMsToCompare = new Date(parseInt(nva + "000"));
if (normalizedTimeInMsToCompare < Date.now()) {
console.warn("Token has expired");
return new Response("Token has expired", {
status: 403,
statusText: "Forbidden"
});
}
} else {
console.warn("Token is incorrect, expected: " + expectedHash);
return new Response("Token is incorrect", {
status: 403,
statusText: "Forbidden"
});
}
}
console.info("Token is good");
}
console.info(`${pathname} ${urlBasename}`);
return fetch(req, {
backend: "origin_0"
});
}
async function handleRequest(event) {
const req = event.request;
return verifyToken(req);
}
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));