-
Notifications
You must be signed in to change notification settings - Fork 231
/
index.js
293 lines (263 loc) · 8.49 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
const MS_SCOPE = "offline_access User.Read Files.Read.All Mail.Read MailboxSettings.Read";
const MS_GRAPH_ROOT = "https://graph.microsoft.com";
const MS_GRAPH_VER = "1.0";
const MS_GRAPH_API_LIST = [
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/recent`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/sharedWithMe`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/root`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/root/children`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/mailFolders`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/mailFolders/inbox`,
`${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/messages`,
];
addEventListener("fetch", (event) => {
event.respondWith(
handleRequest(event.request).catch(
(err) => new Response(err.stack, { status: 500 })
)
);
});
addEventListener('scheduled', event => {
event.waitUntil(handleScheduled(event));
});
async function handleRequest(request) {
if (typeof MS_CLIENT_ID === "undefined"
|| typeof MS_CLIENT_SECRET === "undefined"
|| typeof MS_REDIRECT_URI === "undefined") {
return createHTMLResponse(`<div class="alert alert-danger" role="alert">
Missing MS_CLIENT_ID, MS_CLIENT_SECRET or MS_REDIRECT_URI
</div>`, 500);
}
const { pathname } = new URL(request.url);
if (typeof CRON_PATH !== "undefined" && pathname.startsWith(CRON_PATH)) {
await sendMessage("Scheduled start");
for (let i = 0; i < MS_GRAPH_API_LIST.length; i++) {
await fetchMSApi(MS_GRAPH_API_LIST[i]);
await sleep(randomInt(1000, 5000));
}
await sendMessage("Scheduled finish");
}
if (await Token.get("refresh_token") !== null) {
return fetch("https://welcome.developers.workers.dev");
}
if (pathname.startsWith("/login")) {
return handleLogin(request);
}
if (pathname.startsWith("/callback")) {
return handleCallback(request);
}
return createHTMLResponse(`<a class="w-50 btn btn-lg btn-primary btn-block" href="/login" role="button">Authorize</a>`);
}
async function handleScheduled(event) {
await sendMessage("Scheduled start");
const count = randomInt(2, 10);
for (let i = 0; i < count; i++) {
await randomFetchMSApi();
await sleep(randomInt(1000, 5000));
}
await sendMessage("Scheduled finish");
}
async function sendMessage(message) {
if (typeof TGBOT_TOKEN === "undefined" || typeof TGBOT_CHAT_ID === "undefined") {
console.log(message);
return;
}
const response = await retryFetch(`https://api.telegram.org/bot${TGBOT_TOKEN}/sendMessage`, {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
chat_id: TGBOT_CHAT_ID,
text: message
})
});
if (response.status !== 200) {
console.error(await response.text());
}
}
async function handleLogin(request) {
const url = new URL("https://login.microsoftonline.com/common/oauth2/v2.0/authorize");
url.searchParams.append("client_id", MS_CLIENT_ID);
url.searchParams.append("redirect_uri", MS_REDIRECT_URI);
url.searchParams.append("scope", MS_SCOPE);
url.searchParams.append("response_type", "code");
return Response.redirect(url.href);
}
async function handleCallback(request) {
const response = await retryFetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
"client_id": MS_CLIENT_ID,
"client_secret": MS_CLIENT_SECRET,
"redirect_uri": MS_REDIRECT_URI,
"scope": MS_SCOPE,
"code": new URL(request.url).searchParams.get("code"),
"grant_type": "authorization_code"
}),
});
try {
const responseJson = await response.json();
if (response.status !== 200) {
return createHTMLResponse(`<div class="alert alert-danger" role="alert">
<p>Error occurred: ${responseJson["error"]}</p>
<p>${responseJson["error_description"]}</p>
<p>See: ${responseJson["error_uri"]}</p>
</div>`, response.status);
}
let userInfo
await Promise.all([
Token.put("access_token", responseJson["access_token"], { expirationTtl: responseJson["expires_in"] }),
Token.put("refresh_token", responseJson["refresh_token"]),
getUserInfo(responseJson["access_token"]).then((resp) => {
userInfo = resp;
}),
]);
return createHTMLResponse(`<div class="alert alert-success" role="alert">
Successfully logged in as ${userInfo["displayName"]} (${userInfo["mail"]})
</div>`);
} catch (e) {
return createHTMLResponse(`<div class="alert alert-danger" role="alert">
${e.message}
</div>`, 500);
}
}
async function getAccessToken() {
const accessToken = await Token.get("access_token");
if (accessToken !== null) {
return accessToken;
}
const refreshToken = await Token.get("refresh_token");
if (refreshToken === null) {
return null;
}
const response = await retryFetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
"client_id": MS_CLIENT_ID,
"client_secret": MS_CLIENT_SECRET,
"redirect_uri": MS_REDIRECT_URI,
"scope": MS_SCOPE,
"grant_type": "refresh_token",
"refresh_token": refreshToken
}),
});
if (response.status !== 200) {
console.error("Error refreshing access token:", await response.text());
return null;
}
const responseJson = await response.json();
await Promise.all([
Token.put("access_token", responseJson["access_token"], { expirationTtl: responseJson["expires_in"] }),
Token.put("refresh_token", responseJson["refresh_token"]),
]);
return responseJson["access_token"];
}
async function getUserInfo(accessToken) {
const response = await retryFetch("https://graph.microsoft.com/v1.0/me", {
headers: {
"Authorization": "Bearer " + accessToken
}
});
if (response.status !== 200) {
return null;
}
return await response.json();
}
async function randomFetchMSApi() {
const index = randomInt(0, MS_GRAPH_API_LIST.length);
return await fetchMSApi(MS_GRAPH_API_LIST[index]);
}
async function fetchMSApi(url) {
const accessToken = await getAccessToken();
if (accessToken === null) {
sendMessage("Not login");
return;
}
try {
const response = await retryFetch(url, {
method: "get",
headers: {
"Authorization": "Bearer " + accessToken
}
});
if (response.status === 401) {
Token.delete("access_token");
}
sendMessage(url + ": " + response.statusText);
}
catch (e) {
sendMessage(url + ": " + e.message);
}
}
function randomInt(min, max) {
if (min > max) {
return randomInt(max, min);
}
return Math.floor(Math.random() * (max - min) + min);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function retry(fn, times = 3, delay = 1000) {
return async (...args) => {
for (let i = 0; i < times; i++) {
try {
return await fn(...args);
} catch (e) {
console.error(`Retry: #${i} ${e.message}`);
await sleep(delay);
}
}
console.error("Failed to execute");
}
}
function retryFetch(url, options) {
return retry(fetch)(url, options);
}
function createHTMLResponse(slot, status = 200) {
return new Response(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<title>Microsoft Graph Login</title>
<style>
html,
body {
height: 100%
}
body {
display: flex;
align-items: center;
background-color: #f5f5f5;
}
</style>
</head>
<body>
<div class="container w-70">
<div class="text-center">
<h5 class="mb-4">Microsoft Graph Login</h5>
${slot}
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script>
</html>`, {
status: status,
headers: {
"Content-Type": "text/html"
}
});
}