mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-05-15 14:38:03 +02:00
121 lines
4.0 KiB
TypeScript
121 lines
4.0 KiB
TypeScript
import { Octokit } from "@octokit/rest";
|
|
|
|
import Repository from "./Repository";
|
|
import UserModel from "./model/users/users.model";
|
|
import config from "../config";
|
|
import { createLogger } from "./logger";
|
|
|
|
const logger = createLogger("github");
|
|
|
|
export function octokit(token: string) {
|
|
return new Octokit({
|
|
auth: token,
|
|
request: {
|
|
fetch: fetch,
|
|
},
|
|
});
|
|
}
|
|
|
|
export async function checkToken(token: string) {
|
|
const oct = octokit(token);
|
|
try {
|
|
await oct.users.getAuthenticated();
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function getToken(repository: Repository) {
|
|
logger.debug("getToken", { repoId: repository.repoId });
|
|
// if (repository.model.source.accessToken) {
|
|
// // only check the token if the repo has been visited less than 10 minutes ago
|
|
// if (
|
|
// repository.status == RepositoryStatus.READY &&
|
|
// repository.model.lastView > new Date(Date.now() - 1000 * 60 * 10)
|
|
// ) {
|
|
// return repository.model.source.accessToken;
|
|
// } else if (await checkToken(repository.model.source.accessToken)) {
|
|
// return repository.model.source.accessToken;
|
|
// }
|
|
// }
|
|
if (!repository.owner.model.accessTokens?.github) {
|
|
const query = await UserModel.findById(repository.owner.id, {
|
|
accessTokens: 1,
|
|
accessTokenDates: 1,
|
|
});
|
|
if (query?.accessTokens) {
|
|
repository.owner.model.accessTokens = query.accessTokens;
|
|
repository.owner.model.accessTokenDates = query.accessTokenDates;
|
|
}
|
|
}
|
|
const ownerAccessToken = repository.owner.model.accessTokens?.github;
|
|
if (ownerAccessToken) {
|
|
const tokenAge = repository.owner.model.accessTokenDates?.github;
|
|
// if the token is older than 7 days, refresh it
|
|
if (
|
|
!tokenAge ||
|
|
tokenAge < new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)
|
|
) {
|
|
const url = `https://api.github.com/applications/${config.CLIENT_ID}/token`;
|
|
const headers = {
|
|
Accept: "application/vnd.github+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
};
|
|
|
|
const res = await fetch(url, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({
|
|
access_token: ownerAccessToken,
|
|
}),
|
|
credentials: "include",
|
|
headers: {
|
|
...headers,
|
|
Authorization:
|
|
"Basic " +
|
|
Buffer.from(
|
|
config.CLIENT_ID + ":" + config.CLIENT_SECRET
|
|
).toString("base64"),
|
|
},
|
|
});
|
|
// Only persist a refreshed token if GitHub actually returned a
|
|
// valid one. Without this guard, a 4xx/5xx error body (revoked
|
|
// OAuth, rate limit, transient outage) silently overwrites the
|
|
// user's stored token with `undefined`, which then propagates as
|
|
// `Authorization: token undefined` to every subsequent API call —
|
|
// 401 even on public repos, and the config.GITHUB_TOKEN fallback
|
|
// below is unreachable because the token field is no longer falsy.
|
|
if (res.ok) {
|
|
const resBody = (await res.json().catch(() => null)) as
|
|
| { token?: unknown }
|
|
| null;
|
|
const refreshed =
|
|
resBody && typeof resBody.token === "string" && resBody.token.length > 0
|
|
? resBody.token
|
|
: null;
|
|
if (refreshed) {
|
|
repository.owner.model.accessTokens.github = refreshed;
|
|
if (!repository.owner.model.accessTokenDates) {
|
|
repository.owner.model.accessTokenDates = { github: new Date() };
|
|
} else {
|
|
repository.owner.model.accessTokenDates.github = new Date();
|
|
}
|
|
await repository.owner.model.save();
|
|
return refreshed;
|
|
}
|
|
}
|
|
logger.warn("token refresh failed; falling back", {
|
|
username: repository.owner.model.username,
|
|
status: res.status,
|
|
});
|
|
// fall through to the checkToken path / config.GITHUB_TOKEN
|
|
}
|
|
const check = await checkToken(ownerAccessToken);
|
|
if (check) {
|
|
repository.model.source.accessToken = ownerAccessToken;
|
|
return ownerAccessToken;
|
|
}
|
|
}
|
|
return config.GITHUB_TOKEN;
|
|
}
|