· 5 years ago · Dec 24, 2020, 10:54 PM
1addEventListener('fetch', event => {
2 event.respondWith(handleRequest(event.request))
3})
4
5async function handleRequest(request) {
6 // split access token into the right parts with the right encoding
7 const accessToken = getCookie(request, "accessToken");
8 const partialToken = accessToken.split('.').slice(0, 2).join('.')
9 let signaturePart = accessToken.split('.')[2]
10
11 signaturePart = signaturePart.replace(/-/g, "+");
12 signaturePart = signaturePart.replace(/_/g, "/");
13
14 // throw error if the access token is invalid
15 if (!await verify(str2ab(partialToken), str2ab(atob(signaturePart))))
16 return new Response("", {status: 401})
17
18 const upload = await request.arrayBuffer();
19 const uploadSize = upload.byteLength;
20
21 // has properties byteLimit and byteUsage
22 const account = JSON.parse(await ACCOUNT_USAGE.get("seblz432"))
23
24 if ( uploadSize > (account.byteLimit - account.byteUsage))
25 return new Response("not enough storage", {status: 400})
26
27 if (!request.headers.get("Object-Name") || !request.headers.get("Content-Sha1"))
28 return new Response("missing headers", {status: 400})
29
30 //make sure the file actually uploads before updating account usage
31 if (await uploadToObjectStorage(upload, request.headers)) {
32 const accountUpdated = {
33 byteLimit: account.byteLimit,
34 byteUsage: account.byteUsage + uploadSize
35 }
36 await ACCOUNT_USAGE.put("seblz432", JSON.stringify(accountUpdated))
37
38 return new Response("uploaded", {status: 200})
39 } else {
40 return new Response("upload failed", {status: 500})
41 }
42}
43
44async function uploadToObjectStorage (file, headers) {
45 //storage parameters are stored in KV
46 const authorizationToken = await STORAGE_ACCESS.get("authorizationToken")
47 const uploadUrl = await STORAGE_ACCESS.get("uploadUrl");
48
49 var uploadHeaders = new Headers();
50 uploadHeaders.append("Authorization", authorizationToken);
51 uploadHeaders.append("X-Bz-File-Name", headers.get("Object-Name"));
52 uploadHeaders.append("Content-Type", "application/octet-stream");
53 uploadHeaders.append("X-Bz-Content-Sha1", headers.get("Content-Sha1"));
54
55 var requestOptions = {
56 method: 'POST',
57 headers: uploadHeaders,
58 body: file
59 };
60
61 const uploadResponse = await fetch(uploadUrl, requestOptions).then(response => {
62 return response;
63 })
64
65 return await uploadResponse.text().then(res => {
66 if (JSON.parse((res)).action == "upload") {
67 return true;
68 } else {
69 return false;
70 }
71 })
72}
73
74/**
75 * Everything below this is used to verify the access token
76 * it uses the builtin Crypto API to verify a JWT
77 */
78
79function getCookie(request, name) {
80 let result = ""
81 const cookieString = request.headers.get("Cookie")
82 if (cookieString) {
83 const cookies = cookieString.split(";")
84 cookies.forEach(cookie => {
85 const cookiePair = cookie.split("=", 2)
86 const cookieName = cookiePair[0].trim()
87 if (cookieName === name) {
88 const cookieVal = cookiePair[1]
89 result = cookieVal
90 }
91 })
92 }
93 return result
94}
95
96async function verify (encoded, signature) {
97 const publicKey = await AUTH.get("publicKey");
98
99 const keyObject = await importPublicKey(publicKey);
100
101 return await crypto.subtle.verify(
102 {
103 name: "ECDSA",
104 hash: {name: "SHA-256"},
105 },
106 keyObject,
107 signature,
108 encoded
109 ).catch((error) => {
110 console.log(error);
111 });
112}
113
114function str2ab(str) {
115 const buf = new ArrayBuffer(str.length);
116 const bufView = new Uint8Array(buf);
117 for (let i = 0, strLen = str.length; i < strLen; i++) {
118 bufView[i] = str.charCodeAt(i);
119 }
120 return buf;
121}
122
123async function importPublicKey(pem) {
124 // fetch the part of the PEM string between header and footer
125 const pemHeader = "-----BEGIN PUBLIC KEY-----";
126 const pemFooter = "-----END PUBLIC KEY-----";
127 const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
128 // base64 decode the string to get the binary data
129 const binaryDerString = atob(pemContents);
130 // convert from a binary string to an ArrayBuffer
131 const binaryDer = str2ab(binaryDerString);
132
133 return await crypto.subtle.importKey(
134 "spki",
135 binaryDer,
136 {
137 name: "ECDSA",
138 hash: {name: "SHA-256"},
139 namedCurve: "P-256"
140 },
141 false,
142 ["verify"]
143 );
144}
145