Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a505538
feat: add PAT monitoring functions
rickstaa Oct 4, 2022
c239673
feat: add shields.io dynamic badge json response
rickstaa Oct 11, 2022
26bc4bc
feat: add 'json' type to up monitor
rickstaa Oct 11, 2022
c261bdb
feat: cleanup status functions
rickstaa Oct 11, 2022
cf6e7c5
ci: decrease pat-info rate limiting time
rickstaa Oct 18, 2022
85b4349
feat: decrease monitoring functions rate limits
rickstaa Oct 25, 2022
fbf2793
refactor: pat code
anuraghazra Nov 23, 2022
cd9e685
feat: add PAT monitoring functions
rickstaa Oct 4, 2022
a44cd16
feat: add shields.io dynamic badge json response
rickstaa Oct 11, 2022
964ad66
feat: add 'json' type to up monitor
rickstaa Oct 11, 2022
3c0aae1
feat: cleanup status functions
rickstaa Oct 11, 2022
c502d55
ci: decrease pat-info rate limiting time
rickstaa Oct 18, 2022
3303f90
feat: decrease monitoring functions rate limits
rickstaa Oct 25, 2022
106ce11
refactor: pat code
anuraghazra Nov 23, 2022
b866378
Merge branch 'monitoring' of https://github.com/anuraghazra/github-re…
anuraghazra Jan 28, 2023
169fa96
test: fix pat-info tests
rickstaa Jan 28, 2023
6015466
Update api/status/pat-info.js
rickstaa Jan 28, 2023
8c4feef
test: fix broken tests
rickstaa Jan 28, 2023
b2381ee
Merge branch 'monitoring' of https://github.com/anuraghazra/github-re…
anuraghazra Jan 28, 2023
d3d1d1a
chore: fix suspended account
anuraghazra Jan 28, 2023
2f14ed0
chore: simplify and refactor
anuraghazra Jan 28, 2023
bcde859
chore: fix test
anuraghazra Jan 28, 2023
b79cc44
chore: add resetIn field
anuraghazra Jan 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions api/status/pat-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* @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 10 minutes.
*/

import { logger, request, dateDiff } from "../../src/common/utils.js";
export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 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);
};

return {
validPATs: filterPATsByStatus("valid"),
expiredPATs: filterPATsByStatus("expired"),
exhaustedPATS: filterPATsByStatus("exhausted"),
errorPATs: filterPATsByStatus("error"),
details,
};
};

/**
* 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);
}
};
103 changes: 103 additions & 0 deletions api/status/up.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @file Contains a simple cloud function that can be used to check if the PATs are still
* functional.
*
* @description This function is currently rate limited to 1 request per 10 minutes.
*/

import retryer from "../../src/common/retryer.js";
import { logger, request } from "../../src/common/utils.js";

export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 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
}
}
`,
variables,
},
{
Authorization: `bearer ${token}`,
},
);
};

/**
* Creates Json response that can be used for shields.io dynamic card generation.
*
* @param {*} up Whether the PATs are up or not.
* @returns Dynamic shields.io JSON response object.
*
* @see https://shields.io/endpoint.
*/
const shieldsUptimeBadge = (up) => {
const schemaVersion = 1;
const isError = true;
const label = "Public Instance";
const message = up ? "up" : "down";
const color = up ? "brightgreen" : "red";
return {
schemaVersion,
label,
message,
color,
isError,
};
};

/**
* Cloud function that returns whether the PATs are still functional.
*/
export default async (req, res) => {
let { type } = req.query;
type = type ? type.toLowerCase() : "boolean";

res.setHeader("Content-Type", "application/json");

try {
let PATsValid = true;
try {
await retryer(uptimeFetcher, {});
} catch (err) {
PATsValid = false;
}

if (PATsValid) {
res.setHeader(
"Cache-Control",
`max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`,
);
} else {
res.setHeader("Cache-Control", "no-store");
}

switch (type) {
case "shields":
res.send(shieldsUptimeBadge(PATsValid));
break;
case "json":
res.send({ up: PATsValid });
break;
default:
res.send(PATsValid);
break;
}
} catch (err) {
// Return fail boolean if something went wrong.
logger.error(err);
res.setHeader("Cache-Control", "no-store");
res.send("Something went wrong: " + err.message);
}
};
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"@testing-library/dom": "^8.17.1",
"@testing-library/jest-dom": "^5.16.5",
"@uppercod/css-to-object": "^1.1.1",
"axios-mock-adapter": "^1.18.1",
"axios-mock-adapter": "^1.21.2",
"color-contrast-checker": "^2.1.0",
"hjson": "^3.2.2",
"husky": "^8.0.0",
Expand Down
14 changes: 14 additions & 0 deletions src/common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,19 @@ const parseEmojis = (str) => {
});
};

/**
* Get diff in minutes
* @param {Date} d1
* @param {Date} d2
* @returns {number}
*/
const dateDiff = (d1, d2) => {
const date1 = new Date(d1);
const date2 = new Date(d2);
const diff = date1.getTime() - date2.getTime();
return Math.round(diff / (1000 * 60));
};

export {
ERROR_CARD_LENGTH,
renderError,
Expand All @@ -447,4 +460,5 @@ export {
lowercaseTrim,
chunkArray,
parseEmojis,
dateDiff,
};
Loading