Permalink
Newer
100644
279 lines (254 sloc)
7.24 KB
1
function arrBufToB64Url(buf: ArrayBuffer) {
2
const b64data = btoa(String.fromCharCode(...new Uint8Array(buf)));
3
return b64data.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
4
}
5
6
function stringToBuffer(str: string) {
7
const buffer = new ArrayBuffer(str.length);
8
const ui8 = new Uint8Array(buffer);
9
for (let i = 0, bufLen = str.length; i < bufLen; i++) {
10
ui8[i] = str.charCodeAt(i);
11
}
12
return buffer;
13
}
14
15
export async function GenerateUploadURL(
16
env: Env,
17
path: string,
18
size: number,
21
): Promise<string> {
22
const accessToken = await GetAccessToken(env);
23
const contentTypes: { [k: string]: string } = {
24
gif: "image/gif",
25
heic: "image/heic",
26
heif: "image/heif",
27
jfif: "image/jpeg",
28
jpeg: "image/jpeg",
29
jpg: "image/jpeg",
30
m4v: "video/x-m4v",
31
mkv: "video/x-matroska",
33
mp4: "video/mp4",
34
png: "image/png",
35
webp: "image/webp",
36
webm: "video/webm",
37
wmv: "video/x-ms-wmv",
38
};
39
40
const resumableUploadReq = await fetch(
41
`https://storage.googleapis.com/upload/storage/v1/b/portal-carcrushers-cc/o?uploadType=resumable&name=${encodeURIComponent(
48
"x-upload-content-type": contentTypes[fileExt],
49
"x-upload-content-length": size.toString(),
50
},
51
method: "POST",
58
);
59
60
const url = resumableUploadReq.headers.get("location");
61
62
if (!url) throw new Error("No location header returned");
63
64
return url;
65
}
66
67
async function GetAccessToken(env: Env): Promise<string> {
68
const claimSet = btoa(
69
JSON.stringify({
70
aud: "https://oauth2.googleapis.com/token",
71
exp: Math.floor(Date.now() / 1000) + 120,
72
iat: Math.floor(Date.now() / 1000),
73
iss: env.WORKER_GSERVICEACCOUNT,
74
scope:
75
"https://www.googleapis.com/auth/datastore https://www.googleapis.com/auth/devstorage.read_write",
77
)
78
.replaceAll("+", "-")
79
.replaceAll("/", "_")
80
.replaceAll("=", "");
81
82
const signingKey = await crypto.subtle.importKey(
83
"pkcs8",
84
stringToBuffer(atob(env.STORAGE_ACCOUNT_KEY.replace(/(\r\n|\n|\r)/gm, ""))),
88
);
89
const signature = await crypto.subtle.sign(
90
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
91
signingKey,
93
);
94
const assertion = `eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.${claimSet}.${arrBufToB64Url(
96
)}`;
97
const tokenRequest = await fetch("https://oauth2.googleapis.com/token", {
98
body: `grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=${assertion}`,
99
headers: {
100
"content-type": "application/x-www-form-urlencoded",
101
},
102
method: "POST",
103
});
104
105
if (!tokenRequest.ok)
106
throw new Error(`Failed to get access token: ${await tokenRequest.text()}`);
107
108
return ((await tokenRequest.json()) as { [k: string]: any }).access_token;
109
}
110
111
async function getKeyIDs(
112
access_token: string,
113
projectId: string,
114
keys: { partitionId: { projectId: string }; path: { kind: string }[] }[],
115
) {
116
const keyRequest = await fetch(
117
`https://datastore.googleapis.com/v1/projects/${projectId}:allocateIds`,
118
{
119
body: JSON.stringify({ keys }),
120
headers: {
121
authorization: `Bearer ${access_token}`,
122
"content-type": "application/json",
123
},
124
method: "POST",
126
);
127
128
if (!keyRequest.ok) {
129
console.log(await keyRequest.json());
130
throw new Error("Failed to allocate key IDs");
131
}
132
133
return ((await keyRequest.json()) as { keys: { [k: string]: any }[] }).keys;
134
}
135
136
export async function insertLogs(
137
userActionMap: { [k: string]: number },
138
reportId: string,
140
) {
141
const accessToken = await GetAccessToken(context.env);
142
const actionBaseURLs: { [k: number]: string } = {
143
1: "https://portal.carcrushers.cc/view-closed-report/",
144
2: "https://portal.carcrushers.cc/view-closed-report/",
145
3: "",
146
4: "https://portal.carcrushers.cc/game-appeals/",
147
};
148
const actionIntegers: { [k: number]: string } = {
149
1: "blacklist",
150
2: "ban",
151
3: "revoke",
152
4: "accept_appeal",
153
};
154
const incompleteLogKey = {
155
partitionId: {
156
projectId: context.env.DATASTORE_PROJECT,
157
},
158
path: [
159
{
160
kind: "log",
161
},
162
],
163
};
164
const payload: { mode: string; mutations: { [k: string]: any }[] } = {
165
mode: "NON_TRANSACTIONAL",
166
mutations: [],
167
};
168
const preAllocatedLogKeys = [];
169
170
while (preAllocatedLogKeys.length < Object.keys(userActionMap).length)
171
preAllocatedLogKeys.push(incompleteLogKey);
172
173
const keys = await getKeyIDs(
174
accessToken,
175
context.env.DATASTORE_PROJECT,
177
);
178
179
for (const [user, action] of Object.entries(userActionMap)) {
180
payload.mutations.push({
181
insert: {
182
key: keys.pop(),
183
properties: {
184
action: {
185
stringValue: actionIntegers[action],
186
},
187
evidence: {
188
stringValue: actionBaseURLs[action] + reportId,
189
},
190
executed_at: {
191
integerValue: Date.now(),
192
},
193
executor: {
194
stringValue: context.data.current_user.id,
195
},
196
target: {
197
integerValue: user,
198
},
199
},
200
},
201
});
202
}
203
204
const mutationRequest = await fetch(
205
`https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:commit`,
206
{
207
body: JSON.stringify(payload),
208
headers: {
209
authorization: `Bearer ${accessToken}`,
210
"content-type": "application/json",
211
},
212
method: "POST",
214
);
215
216
if (!mutationRequest.ok) {
217
console.log(await mutationRequest.json());
218
throw new Error("Failed to commit mutation");
219
}
220
221
return await mutationRequest.json();
222
}
223
224
export async function queryLogs(user: number, context: RequestContext) {
225
const accessToken = await GetAccessToken(context.env);
226
227
const queryRequest = await fetch(
228
`https://datastore.googleapis.com/v1/projects/${context.env.DATASTORE_PROJECT}:runQuery`,
229
{
230
body: JSON.stringify({
231
partitionId: {
232
projectId: context.env.DATASTORE_PROJECT,
233
},
234
query: {
235
filter: {
236
propertyFilter: {
237
op: "EQUAL",
238
property: {
239
name: "target",
240
},
241
value: {
242
integerValue: user.toString(),
243
},
244
},
245
},
246
kind: [
247
{
248
name: "log",
249
},
250
],
251
},
252
}),
253
headers: {
254
authorization: `Bearer ${accessToken}`,
255
"content-type": "application/json",
256
},
257
method: "POST",
259
);
260
261
if (!queryRequest.ok) {
262
console.log(await queryRequest.json());
263
throw new Error("Failed to query logs");
264
}
265
269
batch: {
270
entityResults: {
271
cursor: string;
272
entity: { [k: string]: any };
273
version: string;
274
}[];
275
};