diff --git a/api_v1_servers.php b/api_v1_servers.php
index 05b064d..a857f4b 100644
--- a/api_v1_servers.php
+++ b/api_v1_servers.php
@@ -6,13 +6,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? 'create';
$user_id = $_SESSION['user_id'];
+ if ($action === 'refresh_invite_code') {
+ header('Content-Type: application/json');
+ $server_id = $_POST['server_id'] ?? 0;
+ require_once 'includes/permissions.php';
+ if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_SERVER)) {
+ $new_code = generateSecureInviteCode();
+ $expiry_ts = time() + 1800; // 30 minutes
+ $expires_at = date('Y-m-d H:i:s', $expiry_ts);
+ $stmt = db()->prepare("UPDATE servers SET invite_code = ?, invite_code_expires_at = ? WHERE id = ?");
+ $stmt->execute([$new_code, $expires_at, $server_id]);
+ echo json_encode(['success' => true, 'invite_code' => $new_code, 'expires_at' => $expires_at, 'expiry_timestamp' => $expiry_ts]);
+ } else {
+ echo json_encode(['success' => false, 'error' => 'Permission denied']);
+ }
+ exit;
+ }
+
if ($action === 'join') {
$invite_code = $_POST['invite_code'] ?? '';
- $stmt = db()->prepare("SELECT id FROM servers WHERE invite_code = ?");
+ $stmt = db()->prepare("SELECT id, invite_code_expires_at FROM servers WHERE invite_code = ?");
$stmt->execute([$invite_code]);
$server = $stmt->fetch();
if ($server) {
+ if (!empty($server['invite_code_expires_at']) && strtotime($server['invite_code_expires_at']) < time()) {
+ die("Invite code has expired.");
+ }
$stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (?, ?)");
$stmt->execute([$server['id'], $user_id]);
header('Location: index.php?server_id=' . $server['id']);
@@ -56,9 +76,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$db->beginTransaction();
// Create server
- $invite_code = substr(strtoupper(md5(uniqid())), 0, 8);
- $stmt = $db->prepare("INSERT INTO servers (name, owner_id, invite_code, icon_url) VALUES (?, ?, ?, ?)");
- $stmt->execute([$name, $user_id, $invite_code, $icon_url]);
+ $invite_code = generateSecureInviteCode();
+ $expires_at = date('Y-m-d H:i:s', time() + 1800);
+ $stmt = $db->prepare("INSERT INTO servers (name, owner_id, invite_code, invite_code_expires_at, icon_url) VALUES (?, ?, ?, ?, ?)");
+ $stmt->execute([$name, $user_id, $invite_code, $expires_at, $icon_url]);
$server_id = $db->lastInsertId();
// Add owner as member
diff --git a/assets/pasted-20260218-081927-02a8e8f5.png b/assets/pasted-20260218-081927-02a8e8f5.png
new file mode 100644
index 0000000..c435be2
Binary files /dev/null and b/assets/pasted-20260218-081927-02a8e8f5.png differ
diff --git a/assets/pasted-20260218-090941-11404470.png b/assets/pasted-20260218-090941-11404470.png
new file mode 100644
index 0000000..63a04bc
Binary files /dev/null and b/assets/pasted-20260218-090941-11404470.png differ
diff --git a/auth/register.php b/auth/register.php
index c0ba2b4..2a1b5c0 100644
--- a/auth/register.php
+++ b/auth/register.php
@@ -6,23 +6,49 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
+ $invite_code = $_POST['invite_code'] ?? '';
if ($username && $email && $password) {
- $hash = password_hash($password, PASSWORD_DEFAULT);
- try {
- $stmt = db()->prepare("INSERT INTO users (username, display_name, email, password_hash) VALUES (?, ?, ?, ?)");
- $stmt->execute([$username, $username, $email, $hash]);
- $userId = db()->lastInsertId();
-
- // Add to default server
- $stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (1, ?)");
- $stmt->execute([$userId]);
+ $target_server_id = 1;
+ $can_register = true;
- $_SESSION['user_id'] = $userId;
- header('Location: ../index.php');
- exit;
- } catch (Exception $e) {
- $error = "Registration failed: " . $e->getMessage();
+ if (defined('PRIVATE_REGISTRATION') && PRIVATE_REGISTRATION && empty($invite_code)) {
+ $can_register = false;
+ $error = "Invite code is required for private registration.";
+ }
+
+ if ($can_register && !empty($invite_code)) {
+ $stmt = db()->prepare("SELECT id, invite_code_expires_at FROM servers WHERE invite_code = ?");
+ $stmt->execute([$invite_code]);
+ $server = $stmt->fetch();
+ if (!$server) {
+ $can_register = false;
+ $error = "Invalid invite code.";
+ } elseif (!empty($server['invite_code_expires_at']) && strtotime($server['invite_code_expires_at']) < time()) {
+ $can_register = false;
+ $error = "Invite code has expired.";
+ } else {
+ $target_server_id = $server['id'];
+ }
+ }
+
+ if ($can_register) {
+ $hash = password_hash($password, PASSWORD_DEFAULT);
+ try {
+ $stmt = db()->prepare("INSERT INTO users (username, display_name, email, password_hash) VALUES (?, ?, ?, ?)");
+ $stmt->execute([$username, $username, $email, $hash]);
+ $userId = db()->lastInsertId();
+
+ // Add to target server
+ $stmt = db()->prepare("INSERT IGNORE INTO server_members (server_id, user_id) VALUES (?, ?)");
+ $stmt->execute([$target_server_id, $userId]);
+
+ $_SESSION['user_id'] = $userId;
+ header('Location: ../index.php');
+ exit;
+ } catch (Exception $e) {
+ $error = "Registration failed: " . $e->getMessage();
+ }
}
} else {
$error = "Please fill all fields.";
@@ -67,6 +93,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+
+
+ >
+
@@ -1997,15 +2085,16 @@ async function handleSaveUserSettings(btn) {
-
-
Roles / Members
-
-
-
+
@@ -2075,11 +2164,6 @@ async function handleSaveUserSettings(btn) {
-
-
-
-
-
diff --git a/requests.log b/requests.log
index d8b5db0..5c85d2d 100644
--- a/requests.log
+++ b/requests.log
@@ -632,3 +632,135 @@
{"date":"2026-02-17 15:47:36","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
{"date":"2026-02-17 15:48:00","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
{"date":"2026-02-17 15:48:41","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-17 15:52:14 - GET /index.php - POST: []
+2026-02-17 15:52:18 - GET /?fl_project=38443 - POST: []
+2026-02-17 17:00:36 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-17 17:00:43 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-17 17:09:01 - GET /?fl_project=38443 - POST: []
+2026-02-17 17:47:44 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-17 17:52:41 - GET /index.php?server_id=1&channel_id=15 - POST: []
+2026-02-17 17:52:44 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 17:54:04 - GET /index.php - POST: []
+2026-02-17 17:54:25 - GET /index.php - POST: []
+{"date":"2026-02-17 17:55:17","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-17 17:55:43","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.5","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-17 17:55:49","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.5","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-17 17:56:46","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.16","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+{"date":"2026-02-17 17:56:59","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.11","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+{"date":"2026-02-17 17:57:07","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-17 17:57:42 - GET /index.php - POST: []
+2026-02-17 17:59:06 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 18:00:37 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 18:00:49 - GET /index.php - POST: []
+2026-02-17 18:29:50 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 18:30:16 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 18:30:54 - GET /index.php - POST: []
+2026-02-17 18:30:59 - GET /index.php?server_id=1&channel_id=1 - POST: []
+{"date":"2026-02-17 18:31:34","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.3","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+2026-02-17 18:32:20 - GET /index.php?server_id=1&channel_id=1 - POST: []
+{"date":"2026-02-17 18:32:53","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.3","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+2026-02-17 18:33:29 - GET /index.php?server_id=1&channel_id=6 - POST: []
+{"date":"2026-02-17 18:33:54","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-17 18:53:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 18:53:18 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 18:53:28 - GET /index.php?server_id=1&channel_id=6 - POST: []
+{"date":"2026-02-17 18:53:57","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.2","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-17 18:54:39 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 18:54:59 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 19:10:20 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 19:10:33 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 19:10:34 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 19:11:03 - GET /index.php?server_id=1&channel_id=1 - POST: []
+2026-02-17 19:11:17 - GET /index.php?server_id=1&channel_id=1 - POST: []
+{"date":"2026-02-17 19:11:38","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.3","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-17 19:11:53","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.3","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+2026-02-17 19:12:20 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 19:12:26 - GET /index.php?server_id=1&channel_id=6 - POST: []
+2026-02-17 19:35:39 - GET /index.php?server_id=1&channel_id=6 - POST: []
+{"date":"2026-02-17 19:35:57","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.12","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-17 19:36:43 - GET /index.php?server_id=1&channel_id=17 - POST: []
+2026-02-17 19:36:45 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-17 23:03:09 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-17 23:03:27 - GET /index.php - POST: []
+2026-02-17 23:03:34 - GET /index.php - POST: []
+2026-02-17 23:04:21 - GET /index.php - POST: []
+2026-02-17 23:38:37 - GET /index.php - POST: []
+2026-02-17 23:43:09 - GET /index.php - POST: []
+2026-02-17 23:59:28 - GET /index.php - POST: []
+2026-02-18 00:23:11 - GET / - POST: []
+2026-02-18 07:48:39 - GET / - POST: []
+2026-02-18 07:49:09 - GET / - POST: []
+2026-02-18 07:49:44 - GET /?fl_project=38443 - POST: []
+2026-02-18 07:50:57 - GET /index.php - POST: []
+2026-02-18 07:52:10 - GET /?fl_project=38443 - POST: []
+2026-02-18 07:52:18 - GET /index.php - POST: []
+2026-02-18 07:53:28 - GET / - POST: []
+2026-02-18 07:53:36 - GET /index.php - POST: []
+2026-02-18 07:58:29 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:18:05 - GET /index.php - POST: []
+2026-02-18 08:18:31 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:19:40 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:24:12 - GET /index.php - POST: []
+2026-02-18 08:24:26 - GET /index.php - POST: []
+2026-02-18 08:24:29 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:25:32 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:25:40 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:39:54 - GET /index.php - POST: []
+2026-02-18 08:45:21 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:49:08 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:49:18 - GET /index.php - POST: []
+2026-02-18 08:49:36 - GET /index.php - POST: []
+2026-02-18 08:49:55 - GET /index.php - POST: []
+2026-02-18 08:52:31 - GET /?fl_project=38443 - POST: []
+2026-02-18 08:53:44 - GET /index.php - POST: []
+2026-02-18 08:54:25 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:54:27 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:54:30 - GET /index.php?server_id=1 - POST: []
+2026-02-18 08:54:31 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:54:33 - GET /index.php?server_id=1 - POST: []
+2026-02-18 08:54:36 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:54:44 - GET /index.php?server_id=1 - POST: []
+2026-02-18 08:54:53 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:55:06 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:55:12 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:55:27 - GET /index.php?server_id=1 - POST: []
+2026-02-18 08:55:44 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:55:49 - GET /index.php?server_id=1 - POST: []
+2026-02-18 08:57:02 - GET /index.php?server_id=1&channel_id=17 - POST: []
+2026-02-18 08:57:05 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-18 08:57:15 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-18 08:57:17 - GET /index.php - POST: []
+2026-02-18 08:57:55 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:58:06 - GET /index.php?server_id=3 - POST: []
+2026-02-18 08:59:26 - GET /index.php?server_id=1 - POST: []
+2026-02-18 09:00:27 - GET /index.php - POST: []
+2026-02-18 09:02:33 - GET /index.php - POST: []
+{"date":"2026-02-18 09:03:05","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.16","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-18 09:03:34","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+{"date":"2026-02-18 09:04:11","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.12","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-18 09:04:26 - GET /index.php?server_id=1 - POST: []
+{"date":"2026-02-18 09:04:42","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.12","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+{"date":"2026-02-18 09:04:51","method":"POST","post":{"avatar_url":"","display_name":"swefheim","theme":"light","voice_mode":"ptt","voice_ptt_key":"v","voice_vox_threshold":"0.06","dnd_mode":"0","sound_notifications":"0"},"session":{"user_id":3},"user_id":3,"db_success":true}
+2026-02-18 09:06:29 - GET /index.php?server_id=3 - POST: []
+2026-02-18 09:06:32 - GET /index.php?server_id=1 - POST: []
+2026-02-18 09:06:58 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-18 09:07:01 - GET /index.php?server_id=1&channel_id=17 - POST: []
+2026-02-18 09:07:04 - GET /index.php?server_id=1&channel_id=11 - POST: []
+2026-02-18 09:07:07 - GET /index.php?server_id=1&channel_id=21 - POST: []
+2026-02-18 09:08:05 - GET /index.php?server_id=3 - POST: []
+2026-02-18 09:08:22 - GET /index.php?server_id=1 - POST: []
+2026-02-18 09:16:08 - GET /?fl_project=38443 - POST: []
+2026-02-18 09:16:53 - GET /index.php?server_id=1 - POST: []
+2026-02-18 09:17:08 - GET /index.php?server_id=3 - POST: []
+2026-02-18 09:17:12 - GET /index.php?server_id=3 - POST: []
+2026-02-18 09:17:26 - GET /index.php?server_id=3 - POST: []
+2026-02-18 13:33:59 - GET /index.php?server_id=1 - POST: []
+2026-02-18 13:41:53 - GET /index.php?server_id=1 - POST: []
+{"date":"2026-02-18 13:52:05","method":"POST","post":{"avatar_url":"","display_name":"swefpifh \u1d47\u02b0\u1da0\u02b3","theme":"dark","voice_mode":"vox","voice_ptt_key":"v","voice_vox_threshold":"0.12","dnd_mode":"1","sound_notifications":"1"},"session":{"user_id":2},"user_id":2,"db_success":true}
+2026-02-18 13:52:08 - GET /index.php?server_id=1 - POST: []
+2026-02-18 13:52:20 - GET /index.php?server_id=1 - POST: []
+2026-02-18 13:53:16 - GET /index.php - POST: []
+2026-02-18 13:53:21 - GET /index.php - POST: []
+2026-02-18 13:54:26 - GET /index.php?server_id=1 - POST: []
+2026-02-18 13:54:43 - GET /index.php?server_id=1 - POST: []
+2026-02-18 14:13:01 - GET /?fl_project=38443 - POST: []