Added username to accounts
This commit is contained in:
22
index.php
22
index.php
@@ -440,8 +440,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Email</label>
|
<label class="form-label">Email or Username</label>
|
||||||
<input type="email" class="form-control" id="loginEmail" required>
|
<input type="text" class="form-control" id="loginEmail" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Password</label>
|
<label class="form-label">Password</label>
|
||||||
@@ -470,6 +470,10 @@
|
|||||||
<label class="form-label">Email</label>
|
<label class="form-label">Email</label>
|
||||||
<input type="email" class="form-control" id="registerEmail" required>
|
<input type="email" class="form-control" id="registerEmail" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Username (optional)</label>
|
||||||
|
<input type="text" class="form-control" id="registerUsername">
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Password (8+ chars)</label>
|
<label class="form-label">Password (8+ chars)</label>
|
||||||
<input type="password" class="form-control" id="registerPassword" required minlength="8">
|
<input type="password" class="form-control" id="registerPassword" required minlength="8">
|
||||||
@@ -949,10 +953,12 @@
|
|||||||
document.querySelectorAll('.settings-btn').forEach(b => b.style.display = '');
|
document.querySelectorAll('.settings-btn').forEach(b => b.style.display = '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update user email in the dropdown and refresh profiles + projects
|
// Update user email or username if exists in the dropdown and refresh profiles + projects
|
||||||
if (data.user && data.user.email) {
|
if (data.user) {
|
||||||
const userEl = document.getElementById('dropdownUserEmail');
|
const userEl = document.getElementById('dropdownUserEmail');
|
||||||
if (userEl) userEl.textContent = data.user.email;
|
if (userEl) userEl.textContent = (data.user.username && data.user.username.trim())
|
||||||
|
? data.user.username
|
||||||
|
: data.user.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadProfiles();
|
loadProfiles();
|
||||||
@@ -968,6 +974,7 @@
|
|||||||
document.getElementById('registerForm').addEventListener('submit', (e) => {
|
document.getElementById('registerForm').addEventListener('submit', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const email = document.getElementById('registerEmail').value.trim();
|
const email = document.getElementById('registerEmail').value.trim();
|
||||||
|
const username = document.getElementById('registerUsername').value.trim();
|
||||||
const password = document.getElementById('registerPassword').value;
|
const password = document.getElementById('registerPassword').value;
|
||||||
|
|
||||||
const err = document.getElementById('registerError');
|
const err = document.getElementById('registerError');
|
||||||
@@ -978,7 +985,7 @@
|
|||||||
fetch('register.php', {
|
fetch('register.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, password })
|
body: JSON.stringify({ email, username: username || null, password })
|
||||||
})
|
})
|
||||||
.then(async r => {
|
.then(async r => {
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
@@ -1025,7 +1032,8 @@
|
|||||||
|
|
||||||
// Add user name to user menu
|
// Add user name to user menu
|
||||||
if (me.logged_in) {
|
if (me.logged_in) {
|
||||||
document.getElementById('dropdownUserEmail').textContent = me.user.email;
|
document.getElementById('dropdownUserEmail').textContent =
|
||||||
|
(me.user.username && me.user.username.trim()) ? me.user.username : me.user.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide settings button if user can't manage settings
|
// Hide settings button if user can't manage settings
|
||||||
|
|||||||
33
login.php
33
login.php
@@ -5,19 +5,32 @@ require 'auth.php';
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$email = strtolower(trim($data['email'] ?? ''));
|
|
||||||
|
// Keep front-end field name "email" for compatibility; it can now be email OR username
|
||||||
|
$identifierRaw = trim($data['email'] ?? '');
|
||||||
|
$identifier = strtolower($identifierRaw);
|
||||||
$password = strval($data['password'] ?? '');
|
$password = strval($data['password'] ?? '');
|
||||||
|
|
||||||
$stmt = $pdo->prepare("
|
if ($identifier === '') {
|
||||||
SELECT u.id, u.email, u.password_hash,
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Email or username is required']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isEmail = filter_var($identifier, FILTER_VALIDATE_EMAIL) !== false;
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT u.id, u.email, u.username, u.password_hash,
|
||||||
r.name AS role_name,
|
r.name AS role_name,
|
||||||
r.can_manage_settings
|
r.can_manage_settings
|
||||||
FROM users u
|
FROM users u
|
||||||
JOIN roles r ON r.id = u.role_id
|
JOIN roles r ON r.id = u.role_id
|
||||||
WHERE u.email = ?
|
WHERE " . ($isEmail ? "u.email = ?" : "u.username = ?") . "
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
");
|
";
|
||||||
$stmt->execute([$email]);
|
|
||||||
|
$stmt = $pdo->prepare($sql);
|
||||||
|
$stmt->execute([$identifier]);
|
||||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||||
@@ -27,10 +40,11 @@ if (!$user || !password_verify($password, $user['password_hash'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$_SESSION['user'] = [
|
$_SESSION['user'] = [
|
||||||
'id' => intval($user['id']),
|
'id' => (int)$user['id'],
|
||||||
'email' => $user['email'],
|
'email' => $user['email'],
|
||||||
|
'username' => $user['username'], // NEW
|
||||||
'role' => $user['role_name'],
|
'role' => $user['role_name'],
|
||||||
'can_manage_settings' => intval($user['can_manage_settings']),
|
'can_manage_settings' => (int)$user['can_manage_settings'],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Set active profile for this session (default profile if available)
|
// Set active profile for this session (default profile if available)
|
||||||
@@ -39,14 +53,12 @@ $stmt->execute([$_SESSION['user']['id']]);
|
|||||||
$profileId = $stmt->fetchColumn();
|
$profileId = $stmt->fetchColumn();
|
||||||
|
|
||||||
if (!$profileId) {
|
if (!$profileId) {
|
||||||
// Fallback to first profile
|
|
||||||
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? ORDER BY id ASC LIMIT 1");
|
$stmt = $pdo->prepare("SELECT id FROM profiles WHERE user_id = ? ORDER BY id ASC LIMIT 1");
|
||||||
$stmt->execute([$_SESSION['user']['id']]);
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
$profileId = $stmt->fetchColumn();
|
$profileId = $stmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$profileId) {
|
if (!$profileId) {
|
||||||
// Last-resort: create a default profile if none exist (useful for new users)
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)");
|
$stmt = $pdo->prepare("INSERT INTO profiles (user_id, name, is_default) VALUES (?, 'Default', 1)");
|
||||||
$stmt->execute([$_SESSION['user']['id']]);
|
$stmt->execute([$_SESSION['user']['id']]);
|
||||||
$profileId = $pdo->lastInsertId();
|
$profileId = $pdo->lastInsertId();
|
||||||
@@ -59,6 +71,7 @@ echo json_encode([
|
|||||||
'user' => [
|
'user' => [
|
||||||
'id' => $_SESSION['user']['id'],
|
'id' => $_SESSION['user']['id'],
|
||||||
'email' => $_SESSION['user']['email'],
|
'email' => $_SESSION['user']['email'],
|
||||||
|
'username' => $_SESSION['user']['username'], // NEW
|
||||||
'role' => $_SESSION['user']['role'],
|
'role' => $_SESSION['user']['role'],
|
||||||
'can_manage_settings' => $_SESSION['user']['can_manage_settings'],
|
'can_manage_settings' => $_SESSION['user']['can_manage_settings'],
|
||||||
],
|
],
|
||||||
|
|||||||
50
register.php
50
register.php
@@ -6,6 +6,8 @@ header('Content-Type: application/json');
|
|||||||
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$email = strtolower(trim($data['email'] ?? ''));
|
$email = strtolower(trim($data['email'] ?? ''));
|
||||||
|
$usernameRaw = trim($data['username'] ?? '');
|
||||||
|
$username = $usernameRaw === '' ? null : strtolower($usernameRaw);
|
||||||
$password = strval($data['password'] ?? '');
|
$password = strval($data['password'] ?? '');
|
||||||
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
@@ -19,12 +21,31 @@ if (strlen($password) < 8) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional username validation
|
||||||
|
if ($username !== null) {
|
||||||
|
if (strlen($username) < 3 || strlen($username) > 50) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Username must be between 3 and 50 characters']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// letters/numbers + . _ - ; must start/end with letter/number
|
||||||
|
if (!preg_match('/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/', $username)) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode([
|
||||||
|
'success' => false,
|
||||||
|
'error' => 'Username may contain letters, numbers, underscore, hyphen, dot, and must start/end with a letter or number'
|
||||||
|
]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
|
||||||
// Standard role id from roles table
|
// Standard role id from roles table
|
||||||
$stmt = $pdo->prepare("SELECT id FROM roles WHERE name = 'standard' LIMIT 1");
|
$stmt = $pdo->prepare("SELECT id FROM roles WHERE name = 'standard' LIMIT 1");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$role_id = intval($stmt->fetchColumn());
|
$role_id = (int)$stmt->fetchColumn();
|
||||||
|
|
||||||
if ($role_id <= 0) {
|
if ($role_id <= 0) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
@@ -35,9 +56,30 @@ if ($role_id <= 0) {
|
|||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
// Create user
|
// Friendly conflict checks (DB unique constraints should still exist)
|
||||||
$stmt = $pdo->prepare("INSERT INTO users (email, password_hash, role_id) VALUES (?, ?, ?)");
|
$stmt = $pdo->prepare('SELECT 1 FROM users WHERE email = ? LIMIT 1');
|
||||||
$stmt->execute([$email, $hash, $role_id]);
|
$stmt->execute([$email]);
|
||||||
|
if ($stmt->fetchColumn()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Email already in use']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($username !== null) {
|
||||||
|
$stmt = $pdo->prepare('SELECT 1 FROM users WHERE username = ? LIMIT 1');
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
if ($stmt->fetchColumn()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
http_response_code(409);
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Username already in use']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user (username is nullable)
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (email, username, password_hash, role_id) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$email, $username, $hash, $role_id]);
|
||||||
$userId = (int)$pdo->lastInsertId();
|
$userId = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
// Create default profile for this user
|
// Create default profile for this user
|
||||||
|
|||||||
Reference in New Issue
Block a user