Initial commit
This commit is contained in:
109
functions/gcloud.ts
Normal file
109
functions/gcloud.ts
Normal file
@ -0,0 +1,109 @@
|
||||
function arrBufToB64Url(buf: ArrayBuffer) {
|
||||
const b64data = btoa(String.fromCharCode(...new Uint8Array(buf)));
|
||||
return b64data.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
function stringToBuffer(str: string) {
|
||||
const buffer = new ArrayBuffer(str.length);
|
||||
const ui8 = new Uint8Array(buffer);
|
||||
for (let i = 0, bufLen = str.length; i < bufLen; i++) {
|
||||
ui8[i] = str.charCodeAt(i);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
export async function GenerateUploadURL(
|
||||
env: Env,
|
||||
path: string,
|
||||
size: number,
|
||||
fileExt: string
|
||||
): Promise<string> {
|
||||
const accessToken = await GetAccessToken(env);
|
||||
const contentTypes: { [k: string]: string } = {
|
||||
gif: "image/gif",
|
||||
heic: "image/heic",
|
||||
heif: "image/heif",
|
||||
jfif: "image/jpeg",
|
||||
jpeg: "image/jpeg",
|
||||
jpg: "image/jpeg",
|
||||
m4v: "video/x-m4v",
|
||||
mkv: "video/x-matroska",
|
||||
mov: "video/quicktime",
|
||||
mp4: "video/mp4",
|
||||
png: "image/png",
|
||||
webp: "image/webp",
|
||||
webm: "video/webm",
|
||||
wmv: "video/x-ms-wmv",
|
||||
};
|
||||
|
||||
const resumableUploadReq = await fetch(
|
||||
`https://storage.googleapis.com/upload/storage/v1/b/portal-carcrushers-cc/o?uploadType=resumable&name=${encodeURIComponent(
|
||||
path
|
||||
)}`,
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
"x-upload-content-type": contentTypes[fileExt],
|
||||
"x-upload-content-length": size.toString(),
|
||||
},
|
||||
method: "POST",
|
||||
}
|
||||
);
|
||||
|
||||
if (!resumableUploadReq.ok)
|
||||
throw new Error(
|
||||
`Failed to create resumable upload: ${await resumableUploadReq.text()}`
|
||||
);
|
||||
|
||||
const url = resumableUploadReq.headers.get("location");
|
||||
|
||||
if (!url) throw new Error("No location header returned");
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
async function GetAccessToken(env: Env): Promise<string> {
|
||||
const claimSet = btoa(
|
||||
JSON.stringify({
|
||||
aud: "https://oauth2.googleapis.com/token",
|
||||
exp: Math.floor(Date.now() / 1000) + 120,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
iss: env.WORKER_GSERVICEACCOUNT,
|
||||
scope:
|
||||
"https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/devstorage.read_write",
|
||||
})
|
||||
)
|
||||
.replaceAll("+", "-")
|
||||
.replaceAll("/", "_")
|
||||
.replaceAll("=", "");
|
||||
|
||||
const signingKey = await crypto.subtle.importKey(
|
||||
"pkcs8",
|
||||
stringToBuffer(
|
||||
atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))
|
||||
),
|
||||
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
||||
false,
|
||||
["sign"]
|
||||
);
|
||||
const signature = await crypto.subtle.sign(
|
||||
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
|
||||
signingKey,
|
||||
stringToBuffer(`eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}`)
|
||||
);
|
||||
const assertion = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}.${arrBufToB64Url(
|
||||
signature
|
||||
)}`;
|
||||
const tokenRequest = await fetch("https://oauth2.googleapis.com/token", {
|
||||
body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${assertion}`,
|
||||
headers: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
if (!tokenRequest.ok)
|
||||
throw new Error(`Failed to get access token: ${await tokenRequest.text()}`);
|
||||
|
||||
return ((await tokenRequest.json()) as { [k: string]: any }).access_token;
|
||||
}
|
Reference in New Issue
Block a user