code invite avec 30 minutes de sécurité
This commit is contained in:
parent
a763e6e5b1
commit
b6b25ed90d
27
api/refresh_invite_code.php
Normal file
27
api/refresh_invite_code.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../auth/session.php';
|
||||
require_once __DIR__ . '/../includes/utils.php';
|
||||
requireLogin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$server_id = $_POST['server_id'] ?? 0;
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
require_once __DIR__ . '/../includes/permissions.php';
|
||||
if (Permissions::hasPermission($user_id, $server_id, Permissions::MANAGE_SERVER) || Permissions::hasPermission($user_id, $server_id, Permissions::ADMINISTRATOR)) {
|
||||
$new_invite_code = generateInviteCode();
|
||||
$expires_at = date('c', time() + 1800); // ISO 8601 format
|
||||
|
||||
$stmt = db()->prepare("UPDATE servers SET invite_code = ?, invite_code_expires_at = ? WHERE id = ?");
|
||||
$stmt->execute([$new_invite_code, date('Y-m-d H:i:s', time() + 1800), $server_id]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'invite_code' => $new_invite_code,
|
||||
'expires_at' => $expires_at
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['success' => false, 'error' => 'Permission denied']);
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
require_once 'auth/session.php';
|
||||
require_once 'includes/utils.php';
|
||||
requireLogin();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
@ -8,11 +9,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
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 ($server['invite_code_expires_at'] && strtotime($server['invite_code_expires_at']) < time()) {
|
||||
die("This 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 +60,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 = generateInviteCode();
|
||||
$expires_at = date('Y-m-d H:i:s', time() + 1800); // 30 minutes
|
||||
$stmt = $db->prepare("INSERT INTO servers (name, owner_id, invite_code, icon_url, invite_code_expires_at) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$name, $user_id, $invite_code, $icon_url, $expires_at]);
|
||||
$server_id = $db->lastInsertId();
|
||||
|
||||
// Add owner as member
|
||||
|
||||
@ -2796,4 +2796,61 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
};
|
||||
restoreCollapsedStates();
|
||||
|
||||
// Invite code refresh and timer
|
||||
const refreshBtn = document.getElementById('refresh-invite-code-btn');
|
||||
const inviteInput = document.getElementById('server-invite-code');
|
||||
const timerContainer = document.getElementById('invite-code-timer');
|
||||
|
||||
if (refreshBtn) {
|
||||
refreshBtn.addEventListener('click', async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('server_id', window.activeServerId);
|
||||
|
||||
try {
|
||||
const resp = await fetch('api/refresh_invite_code.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.success) {
|
||||
if (inviteInput) inviteInput.value = data.invite_code;
|
||||
if (timerContainer) {
|
||||
timerContainer.dataset.expires = data.expires_at;
|
||||
timerContainer.innerHTML = 'Expires in: <span id="invite-timer-display">30:00</span>';
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
alert('Failed to refresh invite code.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateInviteTimer() {
|
||||
const display = document.getElementById('invite-timer-display');
|
||||
const container = document.getElementById('invite-code-timer');
|
||||
if (!display || !container || !container.dataset.expires) return;
|
||||
|
||||
const expiresAt = new Date(container.dataset.expires).getTime();
|
||||
const now = new Date().getTime();
|
||||
const diff = expiresAt - now;
|
||||
|
||||
if (diff <= 0) {
|
||||
container.innerHTML = '<span class="text-danger">Expired</span>';
|
||||
return;
|
||||
}
|
||||
|
||||
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
||||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
||||
|
||||
display.innerText = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
if (timerContainer) {
|
||||
setInterval(updateInviteTimer, 1000);
|
||||
updateInviteTimer();
|
||||
}
|
||||
});
|
||||
|
||||
@ -14,11 +14,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (empty($invite_code)) {
|
||||
$error = "An invitation code is required.";
|
||||
} else {
|
||||
$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) {
|
||||
$error = "Invalid invitation code.";
|
||||
} elseif ($server['invite_code_expires_at'] && strtotime($server['invite_code_expires_at']) < time()) {
|
||||
$error = "This invitation code has expired.";
|
||||
} else {
|
||||
$server_id = $server['id'];
|
||||
}
|
||||
@ -89,7 +91,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<?php if (PRIVATE_REGISTRATION): ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Invite Code</label>
|
||||
<input type="text" name="invite_code" class="form-control" placeholder="Enter invitation code" required>
|
||||
<input type="text" name="invite_code" class="form-control" placeholder="Ex: aB1!c2D3@4" required>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<button type="submit" class="btn btn-blurple">Continue</button>
|
||||
|
||||
2
db/migrations/20260218_make_invite_code_binary.sql
Normal file
2
db/migrations/20260218_make_invite_code_binary.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Migration to make invite_code case-sensitive
|
||||
ALTER TABLE servers MODIFY invite_code VARCHAR(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
|
||||
44
includes/utils.php
Normal file
44
includes/utils.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Generates a secure random invite code.
|
||||
* Requirements: lowercase, uppercase, digits, special characters.
|
||||
* Length: 10 to 12 characters.
|
||||
*/
|
||||
function generateInviteCode($length = null) {
|
||||
if ($length === null) {
|
||||
$length = rand(10, 12);
|
||||
}
|
||||
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
|
||||
$code = '';
|
||||
$max = strlen($chars) - 1;
|
||||
|
||||
// Ensure at least one of each required type if possible,
|
||||
// but a simple random selection from the full set is usually sufficient
|
||||
// if the set is diverse enough and the length is 10-12.
|
||||
// However, to be strictly compliant with "must have...", let's ensure it.
|
||||
|
||||
$sets = [
|
||||
'abcdefghijklmnopqrstuvwxyz',
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||
'0123456789',
|
||||
'!@#$%^&*()-_=+'
|
||||
];
|
||||
|
||||
// Pick one from each set
|
||||
foreach ($sets as $set) {
|
||||
$code .= $set[random_int(0, strlen($set) - 1)];
|
||||
}
|
||||
|
||||
// Fill the rest
|
||||
while (strlen($code) < $length) {
|
||||
$code .= $chars[random_int(0, $max)];
|
||||
}
|
||||
|
||||
// Shuffle to avoid predictable pattern
|
||||
$codeArray = str_split($code);
|
||||
shuffle($codeArray);
|
||||
|
||||
return implode('', $codeArray);
|
||||
}
|
||||
24
index.php
24
index.php
@ -1518,11 +1518,25 @@ async function handleSaveUserSettings(btn) {
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
|
||||
<?php
|
||||
$invite = '';
|
||||
foreach($servers as $s) if($s['id'] == $active_server_id) $invite = $s['invite_code'];
|
||||
$expires_at = '';
|
||||
foreach($servers as $s) {
|
||||
if($s['id'] == $active_server_id) {
|
||||
$invite = $s['invite_code'];
|
||||
$expires_at = $s['invite_code_expires_at'] ? date('c', strtotime($s['invite_code_expires_at'])) : '';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control bg-dark text-white border-0" value="<?php echo $invite; ?>" readonly>
|
||||
<button class="btn btn-secondary" type="button" onclick="navigator.clipboard.writeText('<?php echo $invite; ?>')">Copy</button>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" id="server-invite-code" class="form-control bg-dark text-white border-0" value="<?php echo $invite; ?>" readonly>
|
||||
<button class="btn btn-secondary" type="button" onclick="navigator.clipboard.writeText(document.getElementById('server-invite-code').value)">Copy</button>
|
||||
<button class="btn btn-primary" type="button" id="refresh-invite-code-btn">Refresh</button>
|
||||
</div>
|
||||
<div id="invite-code-timer" class="small text-muted" data-expires="<?php echo $expires_at; ?>">
|
||||
<?php if ($expires_at): ?>
|
||||
Expires in: <span id="invite-timer-display">--:--</span>
|
||||
<?php else: ?>
|
||||
No expiration set.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1648,7 +1662,7 @@ async function handleSaveUserSettings(btn) {
|
||||
<p style="color: var(--text-muted); font-size: 0.9em;">Enter an invite code to join an existing server.</p>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Invite Code</label>
|
||||
<input type="text" name="invite_code" class="form-control" placeholder="GEN-123" required>
|
||||
<input type="text" name="invite_code" class="form-control" placeholder="Ex: aB1!c2D3@4" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success w-100" style="background-color: #23a559; border: none;">Join Server</button>
|
||||
</form>
|
||||
|
||||
12
requests.log
12
requests.log
@ -642,3 +642,15 @@
|
||||
2026-02-18 16:17:10 - GET / - POST: []
|
||||
2026-02-18 16:20:19 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:21:39 - GET /index.php - POST: []
|
||||
2026-02-18 16:27:32 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:32:55 - GET / - POST: []
|
||||
2026-02-18 16:33:31 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:33:40 - GET /index.php - POST: []
|
||||
2026-02-18 16:37:19 - GET / - POST: []
|
||||
2026-02-18 16:37:47 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:39:21 - GET /index.php - POST: []
|
||||
2026-02-18 16:40:32 - GET /index.php - POST: []
|
||||
2026-02-18 16:43:32 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:45:36 - GET / - POST: []
|
||||
2026-02-18 16:45:55 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-18 16:47:17 - GET /index.php - POST: []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user