Files
NianAIGC/lib/volcengine/signature.ts
2026-05-29 10:26:02 +08:00

103 lines
3.0 KiB
TypeScript

import { createHash, createHmac } from "node:crypto";
export type VolcengineSignatureInput = {
method: "POST" | "GET";
endpoint: string;
query: Record<string, string>;
body: string;
accessKeyId: string;
secretAccessKey: string;
region: string;
service: string;
date?: Date;
};
export type SignedVolcengineRequest = {
url: string;
headers: Record<string, string>;
canonicalRequest: string;
stringToSign: string;
};
export function sha256Hex(value: string | Buffer): string {
return createHash("sha256").update(value).digest("hex");
}
export function signVolcengineRequest(input: VolcengineSignatureInput): SignedVolcengineRequest {
const endpoint = new URL(input.endpoint);
const date = input.date || new Date();
const xDate = formatAmzDate(date);
const shortDate = xDate.slice(0, 8);
const bodyHash = sha256Hex(input.body);
const canonicalQuery = canonicalizeQuery(input.query);
const canonicalHeaders = [
`content-type:application/json`,
`host:${endpoint.host}`,
`x-content-sha256:${bodyHash}`,
`x-date:${xDate}`
].join("\n");
const signedHeaders = "content-type;host;x-content-sha256;x-date";
const canonicalRequest = [
input.method,
endpoint.pathname || "/",
canonicalQuery,
`${canonicalHeaders}\n`,
signedHeaders,
bodyHash
].join("\n");
const credentialScope = `${shortDate}/${input.region}/${input.service}/request`;
const stringToSign = [
"HMAC-SHA256",
xDate,
credentialScope,
sha256Hex(canonicalRequest)
].join("\n");
const signingKey = getSigningKey(input.secretAccessKey, shortDate, input.region, input.service);
const signature = hmacHex(signingKey, stringToSign);
const authorization = [
`HMAC-SHA256 Credential=${input.accessKeyId}/${credentialScope}`,
`SignedHeaders=${signedHeaders}`,
`Signature=${signature}`
].join(", ");
endpoint.search = canonicalQuery;
return {
url: endpoint.toString(),
headers: {
"Authorization": authorization,
"Content-Type": "application/json",
"Host": endpoint.host,
"X-Content-Sha256": bodyHash,
"X-Date": xDate
},
canonicalRequest,
stringToSign
};
}
function canonicalizeQuery(query: Record<string, string>): string {
return Object.entries(query)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join("&");
}
function formatAmzDate(date: Date): string {
return date.toISOString().replace(/[:-]|\.\d{3}/g, "");
}
function hmac(key: string | Buffer, value: string): Buffer {
return createHmac("sha256", key).update(value).digest();
}
function hmacHex(key: string | Buffer, value: string): string {
return createHmac("sha256", key).update(value).digest("hex");
}
function getSigningKey(secretAccessKey: string, date: string, region: string, service: string): Buffer {
const kDate = hmac(secretAccessKey, date);
const kRegion = hmac(kDate, region);
const kService = hmac(kRegion, service);
return hmac(kService, "request");
}