Add options to erquire IP or User Agent to access CDN
This commit is contained in:
parent
6fa604efd1
commit
0e0da6d722
@ -37,7 +37,7 @@ router.post(
|
||||
async (req: Request, res: Response) => {
|
||||
const { attachment_urls } = req.body as RefreshUrlsRequestSchema;
|
||||
|
||||
const refreshed_urls = attachment_urls.map(resignUrl);
|
||||
const refreshed_urls = attachment_urls.map(url => resignUrl(url, req));
|
||||
|
||||
return res.status(200).json({
|
||||
refreshed_urls,
|
||||
|
||||
@ -210,8 +210,8 @@ router.get(
|
||||
|
||||
y.proxy_url = url.toString();
|
||||
|
||||
y.proxy_url = resignUrl(y.proxy_url);
|
||||
y.url = resignUrl(y.url);
|
||||
y.proxy_url = resignUrl(y.proxy_url, req);
|
||||
y.url = resignUrl(y.url, req);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@ -72,7 +72,7 @@ router.post(
|
||||
let finalUrl = `${endpoint}/${path}`;
|
||||
|
||||
if (Config.get().security.cdnSignUrls) {
|
||||
const signatureData = getUrlSignature(path);
|
||||
const signatureData = getUrlSignature(path, req);
|
||||
finalUrl = `${finalUrl}?ex=${signatureData.expiresAt}&is=${signatureData.issuedAt}&hm=${signatureData.hash}`;
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ router.get(
|
||||
|
||||
if (
|
||||
Config.get().security.cdnSignUrls &&
|
||||
!hasValidSignature(path, req.query)
|
||||
!hasValidSignature(path, req.query, req)
|
||||
) {
|
||||
return res.status(404).send("This content is no longer available.");
|
||||
}
|
||||
|
||||
@ -20,8 +20,12 @@ import { Config } from "@spacebar/util";
|
||||
import { createHmac, timingSafeEqual } from "crypto";
|
||||
import ms, { StringValue } from "ms";
|
||||
import { ParsedQs } from "qs";
|
||||
import { Request } from "express";
|
||||
|
||||
export const getUrlSignature = (path: string) => {
|
||||
export const getUrlSignature = (
|
||||
path: string,
|
||||
req: Request,
|
||||
) => {
|
||||
const { cdnSignatureKey, cdnSignatureDuration } = Config.get().security;
|
||||
|
||||
// calculate the expiration time
|
||||
@ -32,11 +36,7 @@ export const getUrlSignature = (path: string) => {
|
||||
);
|
||||
|
||||
// hash the url with the cdnSignatureKey
|
||||
const hash = createHmac("sha256", cdnSignatureKey as string)
|
||||
.update(path)
|
||||
.update(issuedAt)
|
||||
.update(expiresAt)
|
||||
.digest("hex");
|
||||
const hash = calculateHash(path, issuedAt, expiresAt, req);
|
||||
|
||||
return {
|
||||
hash,
|
||||
@ -49,14 +49,31 @@ export const calculateHash = (
|
||||
url: string,
|
||||
issuedAt: string,
|
||||
expiresAt: string,
|
||||
req: Request,
|
||||
) => {
|
||||
const { cdnSignatureKey } = Config.get().security;
|
||||
const hash = createHmac("sha256", cdnSignatureKey as string)
|
||||
.update(url)
|
||||
.update(issuedAt)
|
||||
.update(expiresAt)
|
||||
.digest("hex");
|
||||
return hash;
|
||||
.update(expiresAt);
|
||||
|
||||
if (Config.get().security.cdnSignatureIncludeIp) {
|
||||
if (!req || !req.ip)
|
||||
console.log(
|
||||
"[Signing] CDN Signature IP is enabled but no request object was provided. This may cause issues with signature validation. Please report this to the Spacebar team!",
|
||||
);
|
||||
hash.update(req.ip!);
|
||||
}
|
||||
|
||||
if (Config.get().security.cdnSignatureIncludeUserAgent) {
|
||||
if (!req || !req.headers || !req.headers["user-agent"])
|
||||
console.log(
|
||||
"[Signing] CDN Signature User-Agent is enabled but no request object was provided. This may cause issues with signature validation. Please report this to the Spacebar team!",
|
||||
);
|
||||
hash.update(req.headers["user-agent"] as string);
|
||||
}
|
||||
|
||||
return hash.digest("hex");
|
||||
};
|
||||
|
||||
export const isExpired = (ex: string, is: string) => {
|
||||
@ -80,7 +97,7 @@ export const isExpired = (ex: string, is: string) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const hasValidSignature = (path: string, query: ParsedQs) => {
|
||||
export const hasValidSignature = (path: string, query: ParsedQs, req: Request) => {
|
||||
// get url path
|
||||
const { ex, is, hm } = query;
|
||||
|
||||
@ -92,21 +109,18 @@ export const hasValidSignature = (path: string, query: ParsedQs) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const calcd = calculateHash(path, is as string, ex as string);
|
||||
const calcd = calculateHash(path, is as string, ex as string, req);
|
||||
const calculated = Buffer.from(calcd);
|
||||
const received = Buffer.from(hm as string);
|
||||
|
||||
const isHashValid =
|
||||
calculated.length === received.length &&
|
||||
timingSafeEqual(calculated, received);
|
||||
// if (!isHashValid) {
|
||||
// console.debug("Invalid signature");
|
||||
// console.debug(calcd, hm);
|
||||
// }
|
||||
|
||||
return isHashValid;
|
||||
};
|
||||
|
||||
export const resignUrl = (attachmentUrl: string) => {
|
||||
export const resignUrl = (attachmentUrl: string, req: Request) => {
|
||||
const url = new URL(attachmentUrl);
|
||||
|
||||
// if theres an existing signature, check if its expired or not. no reason to resign if its not expired
|
||||
@ -127,7 +141,7 @@ export const resignUrl = (attachmentUrl: string) => {
|
||||
path = path.slice(1);
|
||||
}
|
||||
|
||||
const { hash, issuedAt, expiresAt } = getUrlSignature(path);
|
||||
const { hash, issuedAt, expiresAt } = getUrlSignature(path, req);
|
||||
url.searchParams.set("ex", expiresAt);
|
||||
url.searchParams.set("is", issuedAt);
|
||||
url.searchParams.set("hm", hash);
|
||||
|
||||
@ -22,9 +22,5 @@ export class CdnConfiguration extends EndpointConfiguration {
|
||||
resizeHeightMax: number = 1000;
|
||||
resizeWidthMax: number = 1000;
|
||||
imagorServerUrl: string | null = null;
|
||||
|
||||
endpointPublic: string | null = null;
|
||||
endpointPrivate: string | null = null;
|
||||
|
||||
proxyCacheHeaderSeconds: number = 60 * 60 * 24;
|
||||
}
|
||||
|
||||
@ -41,4 +41,6 @@ export class SecurityConfiguration {
|
||||
cdnSignUrls: boolean = false;
|
||||
cdnSignatureKey: string = crypto.randomBytes(32).toString("base64");
|
||||
cdnSignatureDuration: string = "24h";
|
||||
cdnSignatureIncludeIp: boolean = true;
|
||||
cdnSignatureIncludeUserAgent: boolean = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user