"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);