272 lines
7.0 KiB
TypeScript
272 lines
7.0 KiB
TypeScript
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,
|
|
origin: string,
|
|
): Promise<string> {
|
|
const accessToken = await GetAccessToken(env);
|
|
const contentTypes: { [k: string]: string } = {
|
|
gif: "image/gif",
|
|
m4v: "video/x-m4v",
|
|
mkv: "video/x-matroska",
|
|
mov: "video/mp4",
|
|
mp4: "video/mp4",
|
|
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}`,
|
|
origin,
|
|
"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;
|
|
}
|
|
|
|
export 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/cloud-platform",
|
|
}),
|
|
)
|
|
.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;
|
|
}
|
|
|
|
async function getKeyIDs(
|
|
access_token: string,
|
|
projectId: string,
|
|
keys: { partitionId: { projectId: string }; path: { kind: string }[] }[],
|
|
) {
|
|
const keyRequest = await fetch(
|
|
`https://datastore.googleapis.com/v1/projects/${projectId}:allocateIds`,
|
|
{
|
|
body: JSON.stringify({ keys }),
|
|
headers: {
|
|
authorization: `Bearer ${access_token}`,
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
},
|
|
);
|
|
|
|
if (!keyRequest.ok) {
|
|
console.log(await keyRequest.json());
|
|
throw new Error("Failed to allocate key IDs");
|
|
}
|
|
|
|
return ((await keyRequest.json()) as { keys: { [k: string]: any }[] }).keys;
|
|
}
|
|
|
|
export async function insertLogs(
|
|
userActionMap: { [k: string]: number },
|
|
reportId: string,
|
|
context: RequestContext,
|
|
) {
|
|
const accessToken = await GetAccessToken(context.env);
|
|
const actionBaseURLs: { [k: number]: string } = {
|
|
1: "https://portal.carcrushers.cc/view-closed-report/",
|
|
2: "https://portal.carcrushers.cc/view-closed-report/",
|
|
3: "",
|
|
4: "https://portal.carcrushers.cc/game-appeals/",
|
|
};
|
|
const actionIntegers: { [k: number]: string } = {
|
|
1: "blacklist",
|
|
2: "ban",
|
|
3: "revoke",
|
|
4: "accept_appeal",
|
|
};
|
|
const incompleteLogKey = {
|
|
partitionId: {
|
|
projectId: context.env.DATASTORE_PROJECT,
|
|
},
|
|
path: [
|
|
{
|
|
kind: "log",
|
|
},
|
|
],
|
|
};
|
|
const payload: { mode: string; mutations: { [k: string]: any }[] } = {
|
|
mode: "NON_TRANSACTIONAL",
|
|
mutations: [],
|
|
};
|
|
const preAllocatedLogKeys = [];
|
|
|
|
while (preAllocatedLogKeys.length < Object.keys(userActionMap).length)
|
|
preAllocatedLogKeys.push(incompleteLogKey);
|
|
|
|
const keys = await getKeyIDs(
|
|
accessToken,
|
|
context.env.DATASTORE_PROJECT,
|
|
preAllocatedLogKeys,
|
|
);
|
|
|
|
for (const [user, action] of Object.entries(userActionMap)) {
|
|
payload.mutations.push({
|
|
insert: {
|
|
key: keys.pop(),
|
|
properties: {
|
|
action: {
|
|
stringValue: actionIntegers[action],
|
|
},
|
|
evidence: {
|
|
stringValue: actionBaseURLs[action] + reportId,
|
|
},
|
|
executed_at: {
|
|
integerValue: Date.now(),
|
|
},
|
|
executor: {
|
|
stringValue: context.data.current_user.id,
|
|
},
|
|
target: {
|
|
integerValue: user,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
const mutationRequest = await fetch(
|
|
`https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:commit`,
|
|
{
|
|
body: JSON.stringify(payload),
|
|
headers: {
|
|
authorization: `Bearer ${accessToken}`,
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
},
|
|
);
|
|
|
|
if (!mutationRequest.ok) {
|
|
console.log(await mutationRequest.json());
|
|
throw new Error("Failed to commit mutation");
|
|
}
|
|
|
|
return await mutationRequest.json();
|
|
}
|
|
|
|
export async function queryLogs(user: number, context: RequestContext) {
|
|
const accessToken = await GetAccessToken(context.env);
|
|
|
|
const queryRequest = await fetch(
|
|
`https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:runQuery`,
|
|
{
|
|
body: JSON.stringify({
|
|
partitionId: {
|
|
projectId: context.env.DATASTORE_PROJECT,
|
|
},
|
|
query: {
|
|
filter: {
|
|
propertyFilter: {
|
|
op: "EQUAL",
|
|
property: {
|
|
name: "target",
|
|
},
|
|
value: {
|
|
integerValue: user.toString(),
|
|
},
|
|
},
|
|
},
|
|
kind: [
|
|
{
|
|
name: "log",
|
|
},
|
|
],
|
|
},
|
|
}),
|
|
headers: {
|
|
authorization: `Bearer ${accessToken}`,
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
},
|
|
);
|
|
|
|
if (!queryRequest.ok) {
|
|
console.log(await queryRequest.json());
|
|
throw new Error("Failed to query logs");
|
|
}
|
|
|
|
return (
|
|
(
|
|
(await queryRequest.json()) as {
|
|
batch: {
|
|
entityResults: {
|
|
cursor: string;
|
|
entity: { [k: string]: any };
|
|
version: string;
|
|
}[];
|
|
};
|
|
}
|
|
).batch?.entityResults ?? []
|
|
);
|
|
}
|