aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-04-05 16:37:02 -0700
committerPinapelz <yukais@pinapelz.com>2026-04-05 16:39:38 -0700
commitee8e8ba44e72caa0fdd88b5b4a8cd63e07339e8d (patch)
tree42ae92ff3aa4edb8ca03fe4da216796f2e9f7178
parentabc7f3331d43e081b8501632e584f24dcc2581ac (diff)
improve some EN locale. add frontend admin registration page via token
Signed-off-by: Pinapelz <yukais@pinapelz.com>
-rw-r--r--README.md5
-rw-r--r--default-config.json1
-rw-r--r--res/admin-register.html89
-rw-r--r--res/langs/en.json10
-rw-r--r--src/Types.hx1
-rw-r--r--src/server/HttpServer.hx75
6 files changed, 176 insertions, 5 deletions
diff --git a/README.md b/README.md
index 1cc497b..87cf61b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+Fork Key Differences
+- `gatePassword` config option, allowing only people who know the password to enter
+- `adminToken` allows for registering admin on the frontend using a known token
+- `/admin-register` page that is visible only if `adminToken` is set
+
This fork adds basic password based authentication to prevent unwanted users from joining and strips most existing emotes.
# <img src="./res/img/favicon.svg" width="40" height="40" align="top"> SyncTube
diff --git a/default-config.json b/default-config.json
index afe50c2..e4bb98d 100644
--- a/default-config.json
+++ b/default-config.json
@@ -2,6 +2,7 @@
"port": 4200,
"channelName": "Dohee Cinema",
"gatePassword": "changeme",
+ "adminToken": "admin",
"maxLoginLength": 20,
"maxMessageLength": 500,
"serverChatHistory": 50,
diff --git a/res/admin-register.html b/res/admin-register.html
new file mode 100644
index 0000000..4515cfd
--- /dev/null
+++ b/res/admin-register.html
@@ -0,0 +1,89 @@
+<!-- localization-template -->
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+ <meta name="mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="manifest" href="manifest.json">
+ <title>SyncTube</title>
+ <link rel="icon" href="img/favicon.svg" type="image/svg+xml">
+ <link id="usertheme" href="css/des.css" rel="stylesheet">
+ <link id="usertheme" href="css/setup.css" rel="stylesheet">
+ <link id="customcss" href="css/custom.css" rel="stylesheet">
+</head>
+
+<body>
+ <main class="setup">
+ <h1 class="setup-title">Admin Registration</h1>
+ <p>Enter the admin token to register your account</p>
+
+ <form id="admin-form" class="setup-form">
+ <input type="text" name="name" placeholder="Username">
+ <input type="password" name="password" placeholder="Password">
+ <input type="password" name="confirmation" placeholder="Repeat password">
+ <input type="password" name="token" placeholder="Admin Token">
+
+ <div id="form-errors" class="form-errors"></div>
+
+ <button type="submit">Register</button>
+ </form>
+ </main>
+
+ <script>
+ const formElement = document.getElementById("admin-form");
+ const errorsElement = document.getElementById("form-errors");
+
+ formElement.addEventListener("submit", function (e) {
+ e.preventDefault();
+ const { name, password, confirmation, token } = formElement.elements;
+
+ const payload = {
+ name: name.value,
+ password: password.value,
+ passwordConfirmation: confirmation.value,
+ token: token.value
+ };
+
+ fetch("/admin-register", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload)
+ })
+ .then(res => res.json())
+ .then(response => handleResponse(response))
+ .catch(() => handleResponse(null));
+ }, true);
+
+ function handleResponse(response) {
+ if (response && response.success === true) {
+ errorsElement.innerHTML = "";
+ const successEl = document.createElement("div");
+ successEl.innerText = "Admin account created! You can now log in.";
+ successEl.style.color = "green";
+ errorsElement.appendChild(successEl);
+
+ Array.from(formElement.elements).forEach(el => { el.disabled = true; });
+ return;
+ }
+
+ const message = (response && response.error) ? response.error : "Registration failed";
+ showErrors(errorsElement, [message]);
+ }
+
+ function showErrors(container, errors) {
+ container.innerHTML = "";
+
+ errors.forEach(function (message) {
+ const errorEl = document.createElement("div");
+ errorEl.innerText = message;
+ container.appendChild(errorEl);
+ });
+ }
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/res/langs/en.json b/res/langs/en.json
index 91d7135..02f91ea 100644
--- a/res/langs/en.json
+++ b/res/langs/en.json
@@ -42,9 +42,9 @@
"exportSettings": "Export Settings",
"importSettings": "Import Settings",
"login": "Login",
- "exit": "Exit",
+ "exit": "Logout",
"settings": "Settings",
- "synchThreshold": "Synch Threshold",
+ "synchThreshold": "Sync Threshold",
"general": "General",
"hotkeys": "Hotkeys",
"video": "Video",
@@ -61,9 +61,9 @@
"leaderDesc": "Request video control permissions",
"mobileViewBtn": "Mobile View",
"leader": "Leader",
- "enterAsGuest": "Enter As Guest:",
- "yourName": "Your Name",
- "enterUserPassword": "Enter User Password",
+ "enterAsGuest": "Login/Register",
+ "yourName": "Username",
+ "enterUserPassword": "Welcome back! Enter Password",
"yourPassword": "Your Password",
"emotes": "Emotes",
"chat": "Chat",
diff --git a/src/Types.hx b/src/Types.hx
index 6fbe55b..1b1997a 100644
--- a/src/Types.hx
+++ b/src/Types.hx
@@ -32,6 +32,7 @@ typedef VideoData = {
typedef ServerConfig = Config & {
serverChatHistory:Int,
gatePassword:String,
+ adminToken:String,
localAdmins:Bool,
allowProxyIps:Bool,
localNetworkOnly:Bool,
diff --git a/src/server/HttpServer.hx b/src/server/HttpServer.hx
index 5917ee0..f0f565b 100644
--- a/src/server/HttpServer.hx
+++ b/src/server/HttpServer.hx
@@ -29,6 +29,13 @@ typedef GateRequest = {
password:String,
}
+typedef AdminRegisterRequest = {
+ name:String,
+ password:String,
+ passwordConfirmation:String,
+ token:String,
+}
+
class HttpServer {
static final mimeTypes = [
"html" => "text/html",
@@ -101,6 +108,8 @@ class HttpServer {
switch url.pathname {
case "/gate":
verifyGate(req, res);
+ case "/admin-register":
+ registerAdmin(req, res);
}
return;
}
@@ -134,6 +143,23 @@ class HttpServer {
return;
}
+ if (url.pathname == "/admin-register") {
+ if (!hasAdminToken()) {
+ res.redirect("/");
+ return;
+ }
+ Fs.readFile('$dir/admin-register.html', (err:Dynamic, data:Buffer) -> {
+ if (err != null) {
+ readFileError(err, res, '$dir/admin-register.html');
+ return;
+ }
+ data = Buffer.from(localizeHtml(data.toString(), req.headers["accept-language"]));
+ res.setHeader("content-type", getMimeType("html"));
+ res.end(data);
+ });
+ return;
+ }
+
if (url.pathname == "/proxy") {
if (!proxyUrl(req, res)) res.end('Proxy error: ${req.url}');
return;
@@ -293,6 +319,55 @@ class HttpServer {
return Sha256.encode('gate_${main.config.gatePassword}_${main.config.salt}');
}
+ function hasAdminToken():Bool {
+ final t = main.config.adminToken;
+ return t != null && t.length > 0;
+ }
+
+ function registerAdmin(req:IncomingMessage, res:ServerResponse) {
+ if (!hasAdminToken()) {
+ res.status(403).json({success: false, error: "Admin registration is disabled"});
+ return;
+ }
+
+ final bodyChunks:Array<Buffer> = [];
+ req.on("data", chunk -> bodyChunks.push(chunk));
+ req.on("end", () -> {
+ final body = Buffer.concat(bodyChunks).toString();
+ final jsonParser = new JsonParser<AdminRegisterRequest>();
+ final jsonData = jsonParser.fromJson(body);
+ if (jsonParser.errors.length > 0) {
+ res.status(400).json({success: false, error: "Invalid request"});
+ return;
+ }
+ final name = jsonData.name.trim();
+ final password = jsonData.password;
+ final passwordConfirmation = jsonData.passwordConfirmation;
+ final token = jsonData.token;
+
+ if (token != main.config.adminToken) {
+ res.status(401).json({success: false, error: "Invalid admin token"});
+ return;
+ }
+ if (main.isBadClientName(name)) {
+ res.status(400).json({success: false, error: "Invalid username"});
+ return;
+ }
+ final min = Main.MIN_PASSWORD_LENGTH;
+ final max = Main.MAX_PASSWORD_LENGTH;
+ if (password.length < min || password.length > max) {
+ res.status(400).json({success: false, error: 'Password must be $min-$max characters'});
+ return;
+ }
+ if (password != passwordConfirmation) {
+ res.status(400).json({success: false, error: "Passwords do not match"});
+ return;
+ }
+ main.addAdmin(name, password);
+ res.status(200).json({success: true});
+ });
+ }
+
function getPath(dir:String, url:URL):String {
final filePath = dir.urlDecode() + decodeURIComponent(url.pathname);
if (!FileSystem.isDirectory(filePath)) return filePath;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage