mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-02-13 02:42:45 +00:00
187 lines
5.6 KiB
TypeScript
187 lines
5.6 KiB
TypeScript
import { createClient } from "redis";
|
|
import { resolve, join } from "path";
|
|
import { existsSync } from "fs";
|
|
import rateLimit from "express-rate-limit";
|
|
import * as slowDown from "express-slow-down";
|
|
import RedisStore from "rate-limit-redis";
|
|
import * as express from "express";
|
|
import * as compression from "compression";
|
|
import * as passport from "passport";
|
|
|
|
import config from "../config";
|
|
import { connect } from "./database/database";
|
|
import { initSession, router as connectionRouter } from "./routes/connection";
|
|
import router from "./routes";
|
|
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
|
import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule";
|
|
import { startWorker } from "./queue";
|
|
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";
|
|
import { getUser } from "./routes/route-utils";
|
|
|
|
function indexResponse(req: express.Request, res: express.Response) {
|
|
if (
|
|
req.params.repoId &&
|
|
req.headers["accept"] &&
|
|
req.headers["accept"].indexOf("text/html") == -1
|
|
) {
|
|
const repoId = req.path.split("/")[2];
|
|
// if it is not an html request, it assumes that the browser try to load a different type of resource
|
|
return res.redirect(
|
|
`/api/repo/${repoId}/file/${req.path.substring(
|
|
req.path.indexOf(repoId) + repoId.length + 1
|
|
)}`
|
|
);
|
|
}
|
|
res.sendFile(resolve("public", "index.html"));
|
|
}
|
|
|
|
export default async function start() {
|
|
const app = express();
|
|
app.use(express.json());
|
|
|
|
app.use(compression());
|
|
app.set("etag", "strong");
|
|
|
|
// handle session and connection
|
|
app.use(initSession());
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
|
|
startWorker();
|
|
|
|
const redisClient = createClient({
|
|
socket: {
|
|
host: config.REDIS_HOSTNAME,
|
|
port: config.REDIS_PORT,
|
|
},
|
|
});
|
|
redisClient.on("error", (err) => console.log("Redis Client Error", err));
|
|
|
|
await redisClient.connect();
|
|
|
|
const rate = rateLimit({
|
|
store: new RedisStore({
|
|
sendCommand: (...args: string[]) => redisClient.sendCommand(args),
|
|
}),
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
max: async (request: express.Request, response: express.Response) => {
|
|
try {
|
|
const user = await getUser(request);
|
|
if (user && user.isAdmin) return 0;
|
|
if (user) return config.RATE_LIMIT;
|
|
} catch (_) {
|
|
// ignore: user not connected
|
|
}
|
|
// if not logged in, limit to half the rate
|
|
return config.RATE_LIMIT / 2;
|
|
},
|
|
keyGenerator(
|
|
request: express.Request,
|
|
_response: express.Response
|
|
): string {
|
|
if (request.headers["cf-connecting-ip"]) {
|
|
return request.headers["cf-connecting-ip"] as string;
|
|
}
|
|
if (!request.ip && request.socket.remoteAddress) {
|
|
console.error("Warning: request.ip is missing!");
|
|
return request.socket.remoteAddress;
|
|
}
|
|
// remove port number from IPv4 addresses
|
|
return request.ip.replace(/:\d+[^:]*$/, "");
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
message: (request: express.Request, response: express.Response) => {
|
|
return `You can only make ${config.RATE_LIMIT} requests every 15min. Please try again later.`;
|
|
},
|
|
});
|
|
const speedLimiter = slowDown({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
delayAfter: 50,
|
|
delayMs: 150,
|
|
maxDelayMs: 5000,
|
|
headers: true,
|
|
});
|
|
const webViewSpeedLimiter = slowDown({
|
|
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
delayAfter: 200,
|
|
delayMs: 150,
|
|
maxDelayMs: 5000,
|
|
headers: true,
|
|
});
|
|
|
|
app.use("/github", rate, speedLimiter, connectionRouter);
|
|
|
|
// api routes
|
|
const apiRouter = express.Router();
|
|
app.use("/api", rate, apiRouter);
|
|
|
|
apiRouter.use("/admin", router.admin);
|
|
apiRouter.use("/options", router.option);
|
|
apiRouter.use("/conferences", router.conference);
|
|
apiRouter.use("/user", router.user);
|
|
apiRouter.use("/repo", router.repositoryPublic);
|
|
apiRouter.use("/repo", speedLimiter, router.file);
|
|
apiRouter.use("/repo", speedLimiter, router.repositoryPrivate);
|
|
apiRouter.use("/pr", speedLimiter, router.pullRequestPublic);
|
|
apiRouter.use("/pr", speedLimiter, router.pullRequestPrivate);
|
|
|
|
apiRouter.get("/message", async (_, res) => {
|
|
if (existsSync("./message.txt")) {
|
|
return res.sendFile(resolve("message.txt"));
|
|
}
|
|
res.sendStatus(404);
|
|
});
|
|
|
|
apiRouter.get("/stat", async (_, res) => {
|
|
const [nbRepositories, users, nbPageViews, nbPullRequests] =
|
|
await Promise.all([
|
|
AnonymizedRepositoryModel.estimatedDocumentCount(),
|
|
AnonymizedRepositoryModel.distinct("owner"),
|
|
AnonymizedRepositoryModel.collection
|
|
.aggregate([
|
|
{
|
|
$group: { _id: null, total: { $sum: "$pageView" } },
|
|
},
|
|
])
|
|
.toArray(),
|
|
AnonymizedPullRequestModel.estimatedDocumentCount(),
|
|
]);
|
|
|
|
res.json({
|
|
nbRepositories,
|
|
nbUsers: users.length,
|
|
nbPageViews: nbPageViews[0]?.total || 0,
|
|
nbPullRequests,
|
|
});
|
|
});
|
|
|
|
// web view
|
|
app.use("/w/", rate, webViewSpeedLimiter, router.webview);
|
|
|
|
app
|
|
.get("/", indexResponse)
|
|
.get("/404", indexResponse)
|
|
.get("/anonymize", indexResponse)
|
|
.get("/r/:repoId/?*", indexResponse)
|
|
.get("/repository/:repoId/?*", indexResponse);
|
|
|
|
app.use(
|
|
express.static(join("public"), {
|
|
etag: true,
|
|
lastModified: true,
|
|
maxAge: 3600, // 1h
|
|
})
|
|
);
|
|
|
|
app.get("*", indexResponse);
|
|
|
|
// start schedules
|
|
conferenceStatusCheck();
|
|
repositoryStatusCheck();
|
|
|
|
await connect();
|
|
app.listen(config.PORT);
|
|
console.log("Database connected and Server started on port: " + config.PORT);
|
|
}
|