-
-
Notifications
You must be signed in to change notification settings - Fork 24.4k
/
Copy pathpat-info.js
139 lines (129 loc) · 3.72 KB
/
pat-info.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
/**
* @file Contains a simple cloud function that can be used to check which PATs are no
* longer working. It returns a list of valid PATs, expired PATs and PATs with errors.
*
* @description This function is currently rate limited to 1 request per 5 minutes.
*/
import { logger, request, dateDiff } from "../../src/common/utils.js";
export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes
/**
* Simple uptime check fetcher for the PATs.
*
* @param {import('axios').AxiosRequestHeaders} variables
* @param {string} token
*/
const uptimeFetcher = (variables, token) => {
return request(
{
query: `
query {
rateLimit {
remaining
resetAt
},
}`,
variables,
},
{
Authorization: `bearer ${token}`,
},
);
};
const getAllPATs = () => {
return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key));
};
/**
* Check whether any of the PATs is expired.
*/
const getPATInfo = async (fetcher, variables) => {
const details = {};
const PATs = getAllPATs();
for (const pat of PATs) {
try {
const response = await fetcher(variables, process.env[pat]);
const errors = response.data.errors;
const hasErrors = Boolean(errors);
const errorType = errors?.[0]?.type;
const isRateLimited =
(hasErrors && errorType === "RATE_LIMITED") ||
response.data.data?.rateLimit?.remaining === 0;
// Store PATs with errors.
if (hasErrors && errorType !== "RATE_LIMITED") {
details[pat] = {
status: "error",
error: {
type: errors[0].type,
message: errors[0].message,
},
};
continue;
} else if (isRateLimited) {
const date1 = new Date();
const date2 = new Date(response.data?.data?.rateLimit?.resetAt);
details[pat] = {
status: "exhausted",
remaining: 0,
resetIn: dateDiff(date2, date1) + " minutes",
};
} else {
details[pat] = {
status: "valid",
remaining: response.data.data.rateLimit.remaining,
};
}
} catch (err) {
// Store the PAT if it is expired.
const errorMessage = err.response?.data?.message?.toLowerCase();
if (errorMessage === "bad credentials") {
details[pat] = {
status: "expired",
};
} else if (errorMessage === "sorry. your account was suspended.") {
details[pat] = {
status: "suspended",
};
} else {
throw err;
}
}
}
const filterPATsByStatus = (status) => {
return Object.keys(details).filter((pat) => details[pat].status === status);
};
const sortedDetails = Object.keys(details)
.sort()
.reduce((obj, key) => {
obj[key] = details[key];
return obj;
}, {});
return {
validPATs: filterPATsByStatus("valid"),
expiredPATs: filterPATsByStatus("expired"),
exhaustedPATs: filterPATsByStatus("exhausted"),
suspendedPATs: filterPATsByStatus("suspended"),
errorPATs: filterPATsByStatus("error"),
details: sortedDetails,
};
};
/**
* Cloud function that returns information about the used PATs.
*/
export default async (_, res) => {
res.setHeader("Content-Type", "application/json");
try {
// Add header to prevent abuse.
const PATsInfo = await getPATInfo(uptimeFetcher, {});
if (PATsInfo) {
res.setHeader(
"Cache-Control",
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
);
}
res.send(JSON.stringify(PATsInfo, null, 2));
} catch (err) {
// Throw error if something went wrong.
logger.error(err);
res.setHeader("Cache-Control", "no-store");
res.send("Something went wrong: " + err.message);
}
};