You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

150 lines
4.6 KiB

"use strict";
const Promise = require("bluebird");
const bhttp = require("bhttp");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const debug = require("debug")("gitea-registration-proxy");
const express = require("express");
const Router = require("express-promise-router");
const toughCookie = require("tough-cookie");
const JSDOM = require("jsdom").JSDOM;
const setting = {
gitea: "127.0.0.1:3000",
port: "8080",
host: "0.0.0.0",
inviteCode: "change_meeee",
};
Object.keys(setting).forEach(k => {
const valueFromEnv = process.env[`REGPROXY_${k.toUpperCase()}`];
if(valueFromEnv !== undefined) {
setting[k] = valueFromEnv;
}
})
const xssSanitize = (input) => {
const inputString = String(input || "");
console.log(inputString);
return inputString.replace(/[^a-zA-Z0-9_]+/g, "_").replace(/(^_)|(_$)/g, "");
};
if(setting.inviteCode == "") {
console.error(`inviteCode '${setting.inviteCode}' must not be left empty`);
process.exit(1);
}
if(setting.inviteCode == "change_meeee") {
console.error(`inviteCode '${setting.inviteCode}' must not be left as its default value`);
process.exit(1);
}
if(xssSanitize(setting.inviteCode) != setting.inviteCode) {
console.error(`inviteCode '${setting.inviteCode}' must only contain alphanumeric characters and underscores`);
process.exit(1);
}
setting.version = require(__dirname + "/package.json").version;
debug("Starting gitea-registration-proxy %s", setting.version);
const app = express();
const router = Router();
app.use(router);
/*
POST /user/sign_up
Form data:
_csrf
user_name
email
password
retype
challenge
nonce
*/
const getBhttpSessionFromRequest = req => {
let jar = new toughCookie.CookieJar();
let passedCookies = "i_like_gitea, session, lang, _csrf".split(", ");
passedCookies.forEach((cookieKey) => {
if (req.cookies && req.cookies[cookieKey] != undefined) {
jar.setCookie(new toughCookie.Cookie({key: cookieKey, value: req.cookies[cookieKey], secure: false}), setting.gitea);
}
});
return bhttp.session({cookieJar: jar});
};
const returnFormResponse = (upstreamResponse, res, inviteCode) => {
const dom = new JSDOM(upstreamResponse.body);
const fieldContainer = dom.window.document.createElement("div");
fieldContainer.className = "required inline field";
fieldContainer.style = "display: flex; align-items: center;"
const label = dom.window.document.createElement("label");
label.for = "invite_code";
label.textContent = "Invite Code";
fieldContainer.appendChild(label);
const inviteCodeInput = dom.window.document.createElement("input");
inviteCodeInput.type = "password";
inviteCodeInput.name = "invite_code";
// prevent xss reflection: since the user can type anything into this, we enforce strict rules on how its interpreted.
// the app wont even start if the correct invite code doesn't follow this format, so its should be fine to do this here.
inviteCodeInput.value = xssSanitize(inviteCode);
fieldContainer.appendChild(inviteCodeInput);
const allRequiredFields = dom.window.document.querySelectorAll('form .required.field');
const lastRequiredField = allRequiredFields[allRequiredFields.length-1];
if(lastRequiredField) {
lastRequiredField.insertAdjacentElement('afterend', fieldContainer);
}
res.send(dom.serialize());
};
router.get("/user/sign_up", (req, res) => {
return Promise.try(() => getBhttpSessionFromRequest(req).get(`${setting.gitea}/user/sign_up`))
.then((upstreamResponse) => {
returnFormResponse(upstreamResponse, res);
});
});
router.post("/user/sign_up", bodyParser.urlencoded({ extended: false }), cookieParser(), (req, res) => {
const form = req.body;
debug(`Signup request: %s (%s)`, form.user_name, form.email, form.challenge, form.nonce);
if(form.invite_code == setting.inviteCode) {
debug("✅ valid invite_code, forwarding request");
return Promise.try(() => {
let passedForm = "_csrf, user_name, email, password, retype".split(", ");
let newForm = {};
passedForm.forEach((field) => {
if (form[field] != undefined) {
newForm[field] = form[field];
}
});
return getBhttpSessionFromRequest(req).post(`${setting.gitea}/user/sign_up`, newForm)
.then((upstreamResponse) => {
returnFormResponse(upstreamResponse, res, form.invite_code);
});
});
} else {
debug("401 unauthorized, invalid invite code: " + setting.inviteCode);
res.write("401 unauthorized: invalid invite code\n");
return res.end();
}
});
app.listen(Number(setting.port), setting.host);