minecraft-web/backend/lib/s3.js

114 lines
3.2 KiB
JavaScript
Raw Normal View History

2025-12-16 08:40:32 +09:00
import crypto from "crypto";
import http from "http";
// S3 설정 (RustFS) - 환경변수에서 로드
const s3Config = {
endpoint: process.env.S3_ENDPOINT,
accessKey: process.env.S3_ACCESS_KEY,
secretKey: process.env.S3_SECRET_KEY,
bucket: "minecraft",
publicUrl: "https://s3.caadiq.co.kr",
};
/**
* AWS Signature V4 서명
*/
function signV4(method, path, headers, payload, accessKey, secretKey) {
const service = "s3";
const region = "us-east-1";
const now = new Date();
const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
const dateStamp = amzDate.slice(0, 8);
headers["x-amz-date"] = amzDate;
headers["x-amz-content-sha256"] = crypto
.createHash("sha256")
.update(payload)
.digest("hex");
const signedHeaders = Object.keys(headers)
.map((k) => k.toLowerCase())
.sort()
.join(";");
const canonicalHeaders = Object.keys(headers)
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
.map((k) => `${k.toLowerCase()}:${headers[k].trim()}`)
.join("\n");
const canonicalRequest = [
method,
path,
"",
canonicalHeaders + "\n",
signedHeaders,
headers["x-amz-content-sha256"],
].join("\n");
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
const stringToSign = [
"AWS4-HMAC-SHA256",
amzDate,
credentialScope,
crypto.createHash("sha256").update(canonicalRequest).digest("hex"),
].join("\n");
const getSignatureKey = (key, ds, rg, sv) => {
const kDate = crypto.createHmac("sha256", `AWS4${key}`).update(ds).digest();
const kRegion = crypto.createHmac("sha256", kDate).update(rg).digest();
const kService = crypto.createHmac("sha256", kRegion).update(sv).digest();
return crypto
.createHmac("sha256", kService)
.update("aws4_request")
.digest();
};
const signingKey = getSignatureKey(secretKey, dateStamp, region, service);
const signature = crypto
.createHmac("sha256", signingKey)
.update(stringToSign)
.digest("hex");
headers[
"Authorization"
] = `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
return headers;
}
/**
* S3(RustFS) 파일 업로드
*/
function uploadToS3(bucket, key, data) {
return new Promise((resolve, reject) => {
const url = new URL(s3Config.endpoint);
const path = `/${bucket}/${key}`;
const headers = {
Host: url.host,
"Content-Type": "image/png",
"Content-Length": data.length.toString(),
};
signV4("PUT", path, headers, data, s3Config.accessKey, s3Config.secretKey);
const options = {
hostname: url.hostname,
port: url.port || 80,
path,
method: "PUT",
headers,
};
const req = http.request(options, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(`${s3Config.publicUrl}/${bucket}/${key}`);
} else {
reject(new Error(`S3 Upload failed: ${res.statusCode}`));
}
});
});
req.on("error", reject);
req.write(data);
req.end();
});
}
export { s3Config, uploadToS3 };