Yrityskohtaiset käyttäjäroolit + IP-rajoitus bugikorjaus
- Lisää role-sarake user_companies-tauluun (admin/user per yritys) - Migraatio: kopioi vanhat admin-roolit user_companies-tauluun, muuta globaali admin → user - Päivitä dbSaveUser/dbLoadUsers/dbGetUser/dbGetUserByUsername käsittelemään company_roles - isCompanyAdmin() tarkistaa nyt yrityskohtaisen roolin (session company_role) - requireAdmin() käyttää isCompanyAdmin():ia - requireCompany() tarkistaa IP-rajoituksen (siirretty login/check_auth:sta) - Login ei enää estä kirjautumista IP:n perusteella, vaan merkitsee ip_blocked - check_auth näyttää kaikki yritykset, IP-estetyt merkitään ip_blocked:lla - company_switch palauttaa company_role ja päivittää session - Frontend: käyttäjälomakkeessa yrityskohtaiset rooli-dropdownit (admin/käyttäjä) - Frontend: yritysvaihto päivittää admin-näkyvyyden company_rolen mukaan - Frontend: yritysvalitsimessa IP-estetyt yritykset näkyvät "(IP-rajoitus)" -tekstillä Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
122
api.php
122
api.php
@@ -38,8 +38,7 @@ function requireAuth() {
|
||||
|
||||
function requireAdmin() {
|
||||
requireAuth();
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
if ($role !== 'admin' && $role !== 'superadmin') {
|
||||
if (!isCompanyAdmin()) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'Vain ylläpitäjä voi tehdä tämän']);
|
||||
exit;
|
||||
@@ -60,8 +59,8 @@ function isSuperAdmin(): bool {
|
||||
}
|
||||
|
||||
function isCompanyAdmin(): bool {
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
return $role === 'admin' || $role === 'superadmin';
|
||||
if (($_SESSION['role'] ?? '') === 'superadmin') return true;
|
||||
return ($_SESSION['company_role'] ?? '') === 'admin';
|
||||
}
|
||||
|
||||
function currentUser(): string {
|
||||
@@ -103,6 +102,20 @@ function requireCompany(): string {
|
||||
echo json_encode(['error' => 'Ei oikeutta tähän yritykseen']);
|
||||
exit;
|
||||
}
|
||||
// IP-rajoitus: tarkista vasta kun yrityksen dataa käytetään (superadmin ohittaa)
|
||||
if (($_SESSION['role'] ?? '') !== 'superadmin') {
|
||||
$allCompanies = dbLoadCompanies();
|
||||
foreach ($allCompanies as $comp) {
|
||||
if ($comp['id'] === $companyId) {
|
||||
if (!isIpAllowed(getClientIp(), $comp['allowed_ips'] ?? '')) {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'IP-osoitteesi ei ole sallittu tälle yritykselle.']);
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $companyId;
|
||||
}
|
||||
|
||||
@@ -1329,45 +1342,33 @@ switch ($action) {
|
||||
echo json_encode(['error' => 'Sinulla ei ole oikeutta kirjautua tälle sivustolle.']);
|
||||
break;
|
||||
}
|
||||
// IP-rajoitus: superadmin ohittaa aina
|
||||
// IP-rajoitus EI enää estä kirjautumista — tarkistetaan vasta requireCompany():ssa
|
||||
$allCompanies = dbLoadCompanies();
|
||||
if ($u['role'] !== 'superadmin') {
|
||||
$allowedCompanies = [];
|
||||
foreach ($userCompanies as $ucId) {
|
||||
foreach ($allCompanies as $comp) {
|
||||
if ($comp['id'] === $ucId) {
|
||||
if (isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
|
||||
$allowedCompanies[] = $ucId;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (empty($allowedCompanies)) {
|
||||
dbRecordLoginAttempt($ip);
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => 'IP-osoitteesi (' . $ip . ') ei ole sallittu.']);
|
||||
break;
|
||||
}
|
||||
$userCompanies = $allowedCompanies;
|
||||
}
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_id'] = $u['id'];
|
||||
$_SESSION['username'] = $u['username'];
|
||||
$_SESSION['nimi'] = $u['nimi'];
|
||||
$_SESSION['role'] = $u['role'];
|
||||
$_SESSION['companies'] = $userCompanies;
|
||||
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
|
||||
// Jos domain matchaa ja käyttäjällä on oikeus -> käytä sitä
|
||||
if ($domainCompanyId && in_array($domainCompanyId, $userCompanies)) {
|
||||
$_SESSION['company_id'] = $domainCompanyId;
|
||||
} else {
|
||||
$_SESSION['company_id'] = !empty($userCompanies) ? $userCompanies[0] : '';
|
||||
}
|
||||
// Hae yritysten nimet
|
||||
// Aseta aktiivisen yrityksen rooli
|
||||
$_SESSION['company_role'] = $_SESSION['company_roles'][$_SESSION['company_id']] ?? 'user';
|
||||
// Hae yritysten nimet + IP-status
|
||||
$companyList = [];
|
||||
foreach ($allCompanies as $comp) {
|
||||
if (in_array($comp['id'], $userCompanies)) {
|
||||
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||
$entry = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||
// Merkitse IP-estetyt yritykset (superadmin ohittaa)
|
||||
if ($u['role'] !== 'superadmin' && !isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
|
||||
$entry['ip_blocked'] = true;
|
||||
}
|
||||
$companyList[] = $entry;
|
||||
}
|
||||
}
|
||||
echo json_encode([
|
||||
@@ -1375,6 +1376,7 @@ switch ($action) {
|
||||
'username' => $u['username'],
|
||||
'nimi' => $u['nimi'],
|
||||
'role' => $u['role'],
|
||||
'company_role' => $_SESSION['company_role'],
|
||||
'companies' => $companyList,
|
||||
'company_id' => $_SESSION['company_id'],
|
||||
'signatures' => buildSignaturesWithDefaults($u, $u['companies'] ?? []),
|
||||
@@ -1397,37 +1399,29 @@ switch ($action) {
|
||||
$u = dbGetUser($_SESSION['user_id']);
|
||||
if ($u) {
|
||||
$_SESSION['companies'] = $u['companies'] ?? [];
|
||||
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
|
||||
// Varmista aktiivinen yritys on sallittu
|
||||
if (!in_array($_SESSION['company_id'] ?? '', $_SESSION['companies'])) {
|
||||
$_SESSION['company_id'] = !empty($_SESSION['companies']) ? $_SESSION['companies'][0] : '';
|
||||
}
|
||||
// Päivitä aktiivisen yrityksen rooli
|
||||
$_SESSION['company_role'] = $_SESSION['company_roles'][$_SESSION['company_id'] ?? ''] ?? 'user';
|
||||
}
|
||||
// Hae yritysten nimet + IP-rajoitus
|
||||
// Hae yritysten nimet — EI suodata IP:n perusteella pois, vaan merkitään ip_blocked
|
||||
$userCompanyIds = $_SESSION['companies'] ?? [];
|
||||
$allCompanies = dbLoadCompanies();
|
||||
$ip = getClientIp();
|
||||
$companyList = [];
|
||||
foreach ($allCompanies as $comp) {
|
||||
if (in_array($comp['id'], $userCompanyIds)) {
|
||||
// IP-rajoitus: superadmin ohittaa aina
|
||||
$entry = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||
// Merkitse IP-estetyt yritykset (superadmin ohittaa)
|
||||
if (($_SESSION['role'] ?? '') !== 'superadmin' && !isIpAllowed($ip, $comp['allowed_ips'] ?? '')) {
|
||||
continue;
|
||||
$entry['ip_blocked'] = true;
|
||||
}
|
||||
$companyList[] = ['id' => $comp['id'], 'nimi' => $comp['nimi']];
|
||||
$companyList[] = $entry;
|
||||
}
|
||||
}
|
||||
// Jos IP-rajoitus poistaa kaikki yritykset (ei superadmin) → kirjaa ulos
|
||||
if (empty($companyList) && ($_SESSION['role'] ?? '') !== 'superadmin') {
|
||||
session_destroy();
|
||||
echo json_encode(['authenticated' => false]);
|
||||
break;
|
||||
}
|
||||
// Päivitä session companies IP-suodatuksen mukaan
|
||||
$allowedIds = array_column($companyList, 'id');
|
||||
$_SESSION['companies'] = $allowedIds;
|
||||
if (!in_array($_SESSION['company_id'] ?? '', $allowedIds) && !empty($allowedIds)) {
|
||||
$_SESSION['company_id'] = $allowedIds[0];
|
||||
}
|
||||
// Hae allekirjoitukset (oletus generoituna jos omaa ei ole)
|
||||
$userSignatures = $u ? buildSignaturesWithDefaults($u, $u['companies'] ?? []) : [];
|
||||
// Brändäystiedot domain-pohjaisesti (sama kuin branding-endpoint)
|
||||
@@ -1449,6 +1443,7 @@ switch ($action) {
|
||||
'nimi' => $_SESSION['nimi'],
|
||||
'email' => $u['email'] ?? '',
|
||||
'role' => $_SESSION['role'],
|
||||
'company_role' => $_SESSION['company_role'] ?? 'user',
|
||||
'companies' => $companyList,
|
||||
'company_id' => $_SESSION['company_id'] ?? '',
|
||||
'signatures' => $userSignatures,
|
||||
@@ -1549,7 +1544,8 @@ switch ($action) {
|
||||
$nimi = trim($input['nimi'] ?? '');
|
||||
$email = trim($input['email'] ?? '');
|
||||
$isSA = ($_SESSION['role'] ?? '') === 'superadmin';
|
||||
$validRoles = $isSA ? ['superadmin', 'admin', 'user'] : ['admin', 'user'];
|
||||
// Globaali rooli: user tai superadmin (admin on nyt yrityskohtainen)
|
||||
$validRoles = $isSA ? ['superadmin', 'user'] : ['user'];
|
||||
$role = in_array($input['role'] ?? '', $validRoles) ? $input['role'] : 'user';
|
||||
if (empty($username) || empty($password)) {
|
||||
http_response_code(400);
|
||||
@@ -1583,6 +1579,19 @@ switch ($action) {
|
||||
$signatures[(string)$mbId] = (string)$sig;
|
||||
}
|
||||
}
|
||||
// Yrityskohtaiset roolit
|
||||
$companyRoles = [];
|
||||
if ($isSA && isset($input['company_roles']) && is_array($input['company_roles'])) {
|
||||
foreach ($input['company_roles'] as $cid => $crole) {
|
||||
if (in_array($cid, $companies) && in_array($crole, ['admin', 'user'])) {
|
||||
$companyRoles[$cid] = $crole;
|
||||
}
|
||||
}
|
||||
} elseif (!$isSA) {
|
||||
// Admin luo käyttäjiä vain omaan yritykseensä -> oletusrooli user
|
||||
$myCompanyId = $_SESSION['company_id'] ?? '';
|
||||
$companyRoles[$myCompanyId] = 'user';
|
||||
}
|
||||
$newUser = [
|
||||
'id' => generateId(),
|
||||
'username' => $username,
|
||||
@@ -1591,6 +1600,7 @@ switch ($action) {
|
||||
'email' => $email,
|
||||
'role' => $role,
|
||||
'companies' => $companies,
|
||||
'company_roles' => $companyRoles,
|
||||
'signatures' => $signatures,
|
||||
'luotu' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
@@ -1623,7 +1633,8 @@ switch ($action) {
|
||||
if (isset($input['nimi'])) $u['nimi'] = trim($input['nimi']);
|
||||
if (isset($input['email'])) $u['email'] = trim($input['email']);
|
||||
if (isset($input['role'])) {
|
||||
$validRoles = $isSA ? ['superadmin', 'admin', 'user'] : ['admin', 'user'];
|
||||
// Globaali rooli: user tai superadmin (admin on nyt yrityskohtainen)
|
||||
$validRoles = $isSA ? ['superadmin', 'user'] : ['user'];
|
||||
// Admin ei voi muuttaa superadminia
|
||||
if (!$isSA && ($u['role'] === 'superadmin')) {
|
||||
// Älä muuta roolia
|
||||
@@ -1636,6 +1647,16 @@ switch ($action) {
|
||||
$validIds = array_column($allCompanies, 'id');
|
||||
$u['companies'] = array_values(array_filter($input['companies'], fn($c) => in_array($c, $validIds)));
|
||||
}
|
||||
// Yrityskohtaiset roolit
|
||||
if (isset($input['company_roles']) && is_array($input['company_roles'])) {
|
||||
$companyRoles = $u['company_roles'] ?? [];
|
||||
foreach ($input['company_roles'] as $cid => $crole) {
|
||||
if (in_array($cid, $u['companies'] ?? []) && in_array($crole, ['admin', 'user'])) {
|
||||
$companyRoles[$cid] = $crole;
|
||||
}
|
||||
}
|
||||
$u['company_roles'] = $companyRoles;
|
||||
}
|
||||
if (!empty($input['password'])) {
|
||||
$u['password_hash'] = password_hash($input['password'], PASSWORD_DEFAULT);
|
||||
}
|
||||
@@ -1652,12 +1673,15 @@ switch ($action) {
|
||||
// Päivitä sessio jos muokattiin kirjautunutta käyttäjää
|
||||
if ($u['id'] === $_SESSION['user_id']) {
|
||||
$_SESSION['companies'] = $u['companies'] ?? [];
|
||||
$_SESSION['company_roles'] = $u['company_roles'] ?? [];
|
||||
if (!empty($u['companies']) && !in_array($_SESSION['company_id'] ?? '', $u['companies'])) {
|
||||
$_SESSION['company_id'] = $u['companies'][0];
|
||||
}
|
||||
if (empty($u['companies'])) {
|
||||
$_SESSION['company_id'] = '';
|
||||
}
|
||||
// Päivitä aktiivisen yrityksen rooli
|
||||
$_SESSION['company_role'] = ($_SESSION['company_roles'][$_SESSION['company_id'] ?? '']) ?? 'user';
|
||||
}
|
||||
$safe = $u;
|
||||
unset($safe['password_hash']);
|
||||
@@ -3755,7 +3779,13 @@ switch ($action) {
|
||||
}
|
||||
}
|
||||
$_SESSION['company_id'] = $companyId;
|
||||
echo json_encode(['success' => true, 'company_id' => $companyId]);
|
||||
// Päivitä aktiivisen yrityksen rooli
|
||||
$_SESSION['company_role'] = ($_SESSION['company_roles'] ?? [])[$companyId] ?? 'user';
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'company_id' => $companyId,
|
||||
'company_role' => $_SESSION['company_role'],
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'company_config':
|
||||
|
||||
34
db.php
34
db.php
@@ -591,6 +591,7 @@ function initDatabase(): void {
|
||||
"ALTER TABLE mailboxes ADD COLUMN auto_reply_body TEXT AFTER auto_reply_enabled",
|
||||
"ALTER TABLE companies ADD COLUMN allowed_ips TEXT DEFAULT '' AFTER enabled_modules",
|
||||
"ALTER TABLE todos ADD COLUMN category VARCHAR(30) DEFAULT '' AFTER priority",
|
||||
"ALTER TABLE user_companies ADD COLUMN role VARCHAR(20) DEFAULT 'user' AFTER company_id",
|
||||
];
|
||||
foreach ($alters as $sql) {
|
||||
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
|
||||
@@ -604,6 +605,17 @@ function initDatabase(): void {
|
||||
$db->query("UPDATE users SET role = 'superadmin' WHERE role = 'admin'");
|
||||
}
|
||||
} catch (\Throwable $e) { /* ohitetaan */ }
|
||||
|
||||
// Migraatio: kopioi admin-käyttäjien rooli user_companies-tauluun
|
||||
// (kun role-sarake lisätty, olemassa olevat admin-käyttäjät saavat admin-roolin kaikkiin yrityksiinsä)
|
||||
try {
|
||||
$db->query("UPDATE user_companies uc JOIN users u ON u.id = uc.user_id SET uc.role = 'admin' WHERE u.role = 'admin' AND uc.role = 'user'");
|
||||
} catch (\Throwable $e) { /* ohitetaan */ }
|
||||
|
||||
// Migraatio: muuta globaali 'admin' → 'user' (admin on nyt yrityskohtainen user_companies.role)
|
||||
try {
|
||||
$db->query("UPDATE users SET role = 'user' WHERE role = 'admin'");
|
||||
} catch (\Throwable $e) { /* ohitetaan */ }
|
||||
}
|
||||
|
||||
// ==================== YRITYKSET ====================
|
||||
@@ -730,6 +742,12 @@ function dbLoadUsers(): array {
|
||||
foreach ($users as &$u) {
|
||||
$u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$u['id']]);
|
||||
|
||||
// Yrityskohtaiset roolit
|
||||
$roleRows = _dbFetchAll("SELECT company_id, role FROM user_companies WHERE user_id = ?", [$u['id']]);
|
||||
$companyRoles = [];
|
||||
foreach ($roleRows as $rr) { $companyRoles[$rr['company_id']] = $rr['role'] ?? 'user'; }
|
||||
$u['company_roles'] = $companyRoles;
|
||||
|
||||
$sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$u['id']]);
|
||||
$sigs = [];
|
||||
foreach ($sigRows as $row) {
|
||||
@@ -746,6 +764,11 @@ function dbGetUser(string $id): ?array {
|
||||
|
||||
$u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$id]);
|
||||
|
||||
$roleRows = _dbFetchAll("SELECT company_id, role FROM user_companies WHERE user_id = ?", [$id]);
|
||||
$companyRoles = [];
|
||||
foreach ($roleRows as $rr) { $companyRoles[$rr['company_id']] = $rr['role'] ?? 'user'; }
|
||||
$u['company_roles'] = $companyRoles;
|
||||
|
||||
$sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$id]);
|
||||
$sigs = [];
|
||||
foreach ($sigRows as $row) {
|
||||
@@ -762,6 +785,11 @@ function dbGetUserByUsername(string $username): ?array {
|
||||
|
||||
$u['companies'] = _dbFetchColumn("SELECT company_id FROM user_companies WHERE user_id = ?", [$u['id']]);
|
||||
|
||||
$roleRows = _dbFetchAll("SELECT company_id, role FROM user_companies WHERE user_id = ?", [$u['id']]);
|
||||
$companyRoles = [];
|
||||
foreach ($roleRows as $rr) { $companyRoles[$rr['company_id']] = $rr['role'] ?? 'user'; }
|
||||
$u['company_roles'] = $companyRoles;
|
||||
|
||||
$sigRows = _dbFetchAll("SELECT mailbox_id, signature FROM user_signatures WHERE user_id = ?", [$u['id']]);
|
||||
$sigs = [];
|
||||
foreach ($sigRows as $row) {
|
||||
@@ -792,11 +820,13 @@ function dbSaveUser(array $user): void {
|
||||
'luotu' => $user['luotu'] ?? date('Y-m-d H:i:s'),
|
||||
]);
|
||||
|
||||
// Yritykset
|
||||
// Yritykset + yrityskohtaiset roolit
|
||||
_dbExecute("DELETE FROM user_companies WHERE user_id = ?", [$user['id']]);
|
||||
if (!empty($user['companies'])) {
|
||||
$companyRoles = $user['company_roles'] ?? [];
|
||||
foreach ($user['companies'] as $cid) {
|
||||
_dbExecute("INSERT IGNORE INTO user_companies (user_id, company_id) VALUES (?, ?)", [$user['id'], $cid]);
|
||||
$role = $companyRoles[$cid] ?? 'user';
|
||||
_dbExecute("INSERT IGNORE INTO user_companies (user_id, company_id, role) VALUES (?, ?, ?)", [$user['id'], $cid, $role]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
index.html
13
index.html
@@ -1943,17 +1943,16 @@
|
||||
<label for="user-form-password">Salasana <span id="user-pw-hint"></span></label>
|
||||
<input type="password" id="user-form-password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user-form-role">Rooli</label>
|
||||
<div class="form-group" id="user-role-group">
|
||||
<label for="user-form-role">Pääkäyttäjä</label>
|
||||
<select id="user-form-role">
|
||||
<option value="user">Käyttäjä</option>
|
||||
<option value="admin">Yritysadmin</option>
|
||||
<option value="superadmin">Pääkäyttäjä</option>
|
||||
<option value="user">Ei</option>
|
||||
<option value="superadmin">Kyllä (Superadmin)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label>Yritysoikeudet</label>
|
||||
<div id="user-company-checkboxes" style="display:flex;flex-wrap:wrap;gap:0.75rem;margin-top:0.25rem;"></div>
|
||||
<label>Yritykset ja roolit</label>
|
||||
<div id="user-company-checkboxes" style="display:flex;flex-direction:column;gap:0.5rem;margin-top:0.25rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="user-signatures-section" style="display:none;margin-top:1rem;border-top:1px solid #e5e7eb;padding-top:1rem;">
|
||||
|
||||
114
script.js
114
script.js
@@ -3,7 +3,7 @@ let customers = [];
|
||||
let sortField = 'yritys';
|
||||
let sortAsc = true;
|
||||
let currentDetailId = null;
|
||||
let currentUser = { username: '', nimi: '', role: '' };
|
||||
let currentUser = { username: '', nimi: '', role: '', company_role: '' };
|
||||
let currentCompany = null; // {id, nimi}
|
||||
let availableCompanies = []; // [{id, nimi}, ...]
|
||||
let currentTicketCompanyId = ''; // Avatun tiketin yritys (cross-company tuki)
|
||||
@@ -138,7 +138,7 @@ async function checkAuth() {
|
||||
try {
|
||||
const data = await apiCall('check_auth');
|
||||
if (data.authenticated) {
|
||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id };
|
||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role, company_role: data.company_role || '', id: data.user_id };
|
||||
availableCompanies = data.companies || [];
|
||||
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
||||
currentUserSignatures = data.signatures || {};
|
||||
@@ -160,7 +160,7 @@ loginForm.addEventListener('submit', async (e) => {
|
||||
try {
|
||||
const data = await apiCall('login', 'POST', { username, password, captcha: parseInt(captcha) });
|
||||
loginError.style.display = 'none';
|
||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role, id: data.user_id };
|
||||
currentUser = { username: data.username, nimi: data.nimi, role: data.role, company_role: data.company_role || '', id: data.user_id };
|
||||
availableCompanies = data.companies || [];
|
||||
currentCompany = availableCompanies.find(c => c.id === data.company_id) || availableCompanies[0] || null;
|
||||
currentUserSignatures = data.signatures || {};
|
||||
@@ -185,16 +185,23 @@ document.getElementById('btn-logout').addEventListener('click', async () => {
|
||||
loadBranding(); // Domain-pohjainen brändäys uudelleen
|
||||
});
|
||||
|
||||
function isCurrentUserAdmin() {
|
||||
if (currentUser.role === 'superadmin') return true;
|
||||
return currentUser.company_role === 'admin';
|
||||
}
|
||||
|
||||
function updateAdminVisibility() {
|
||||
const isAdmin = isCurrentUserAdmin();
|
||||
document.getElementById('btn-users').style.display = isAdmin ? '' : 'none';
|
||||
document.getElementById('tab-settings').style.display = isAdmin ? '' : 'none';
|
||||
document.getElementById('btn-companies').style.display = isAdmin ? '' : 'none';
|
||||
}
|
||||
|
||||
async function showDashboard() {
|
||||
loginScreen.style.display = 'none';
|
||||
dashboard.style.display = 'block';
|
||||
document.getElementById('user-info').textContent = currentUser.nimi || currentUser.username;
|
||||
const isSuperAdmin = currentUser.role === 'superadmin';
|
||||
const isAdmin = currentUser.role === 'admin' || isSuperAdmin;
|
||||
// Näytä admin-toiminnot roolin mukaan
|
||||
document.getElementById('btn-users').style.display = isAdmin ? '' : 'none';
|
||||
document.getElementById('tab-settings').style.display = isAdmin ? '' : 'none';
|
||||
document.getElementById('btn-companies').style.display = isAdmin ? '' : 'none';
|
||||
updateAdminVisibility();
|
||||
// Yritysvalitsin
|
||||
populateCompanySelector();
|
||||
// Avaa oikea tabi URL-hashin perusteella (tai customers oletuks)
|
||||
@@ -212,21 +219,30 @@ function populateCompanySelector() {
|
||||
return;
|
||||
}
|
||||
sel.style.display = '';
|
||||
sel.innerHTML = availableCompanies.map(c =>
|
||||
`<option value="${c.id}" ${currentCompany && c.id === currentCompany.id ? 'selected' : ''}>${esc(c.nimi)}</option>`
|
||||
).join('');
|
||||
sel.innerHTML = availableCompanies.map(c => {
|
||||
const blocked = c.ip_blocked ? ' (IP-rajoitus)' : '';
|
||||
const disabled = c.ip_blocked ? ' disabled' : '';
|
||||
return `<option value="${c.id}" ${currentCompany && c.id === currentCompany.id ? 'selected' : ''}${disabled}>${esc(c.nimi)}${blocked}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function switchCompany(companyId) {
|
||||
try {
|
||||
await apiCall('company_switch', 'POST', { company_id: companyId });
|
||||
const result = await apiCall('company_switch', 'POST', { company_id: companyId });
|
||||
currentCompany = availableCompanies.find(c => c.id === companyId) || null;
|
||||
// Päivitä yrityskohtainen rooli
|
||||
if (result.company_role) {
|
||||
currentUser.company_role = result.company_role;
|
||||
}
|
||||
// Päivitä brändäys vaihdetun yrityksen mukaan
|
||||
try {
|
||||
const auth = await apiCall('check_auth');
|
||||
if (auth.branding) applyBranding(auth.branding);
|
||||
applyModules(auth.enabled_modules || []);
|
||||
currentUser.company_role = auth.company_role || '';
|
||||
} catch (e2) {}
|
||||
// Päivitä admin-näkyvyys yritysroolin mukaan
|
||||
updateAdminVisibility();
|
||||
// Lataa uudelleen aktiivinen tab
|
||||
const hash = window.location.hash.replace('#', '') || 'customers';
|
||||
const [mainTab, subTab] = hash.split('/');
|
||||
@@ -966,7 +982,7 @@ async function loadArchive() {
|
||||
<td>${esc(c.arkistoija || '')}</td>
|
||||
<td class="actions-cell">
|
||||
<button onclick="restoreCustomer('${c.id}')" class="btn-small btn-restore" title="Palauta">↺ Palauta</button>
|
||||
${currentUser.role === 'admin' ? `<button onclick="permanentDelete('${c.id}','${esc(c.yritys)}')" class="btn-small btn-perm-delete" title="Poista pysyvästi">✕ Poista</button>` : ''}
|
||||
${isCurrentUserAdmin() ? `<button onclick="permanentDelete('${c.id}','${esc(c.yritys)}')" class="btn-small btn-perm-delete" title="Poista pysyvästi">✕ Poista</button>` : ''}
|
||||
</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
@@ -1045,7 +1061,10 @@ async function loadUsers() {
|
||||
<td><strong>${esc(u.username)}</strong></td>
|
||||
<td>${esc(u.nimi)}</td>
|
||||
<td>${esc(u.email || '')}</td>
|
||||
<td><span class="role-badge role-${u.role}">${u.role === 'superadmin' ? 'Pääkäyttäjä' : (u.role === 'admin' ? 'Yritysadmin' : 'Käyttäjä')}</span></td>
|
||||
<td>${u.role === 'superadmin' ? '<span class="role-badge role-superadmin">Pääkäyttäjä</span>' :
|
||||
Object.entries(u.company_roles || {}).filter(([,r]) => r === 'admin').length > 0
|
||||
? Object.entries(u.company_roles).map(([cid, r]) => r === 'admin' ? `<span class="role-badge role-admin" title="${cid}">Admin</span>` : '').filter(Boolean).join(' ')
|
||||
: '<span class="role-badge role-user">Käyttäjä</span>'}</td>
|
||||
<td>${esc(u.luotu)}</td>
|
||||
<td class="actions-cell">
|
||||
<button onclick="editUser('${u.id}')" title="Muokkaa">✎</button>
|
||||
@@ -1069,32 +1088,50 @@ function openUserForm(user = null) {
|
||||
document.getElementById('user-form-email').value = user ? (user.email || '') : '';
|
||||
document.getElementById('user-form-password').value = '';
|
||||
document.getElementById('user-pw-hint').textContent = user ? '(jätä tyhjäksi jos ei muuteta)' : '*';
|
||||
document.getElementById('user-form-role').value = user ? user.role : 'user';
|
||||
// Piilota superadmin-vaihtoehto ellei ole superadmin
|
||||
const saOption = document.querySelector('#user-form-role option[value="superadmin"]');
|
||||
if (saOption) saOption.style.display = currentUser?.role === 'superadmin' ? '' : 'none';
|
||||
// Globaali rooli: user vs superadmin
|
||||
document.getElementById('user-form-role').value = (user && user.role === 'superadmin') ? 'superadmin' : 'user';
|
||||
// Piilota superadmin-kenttä ellei ole superadmin
|
||||
const roleGroup = document.getElementById('user-role-group');
|
||||
if (roleGroup) roleGroup.style.display = currentUser?.role === 'superadmin' ? '' : 'none';
|
||||
// Piilota yrityscheckboxit adminilta (näkee vain oman yrityksen)
|
||||
const compSection = document.getElementById('user-company-checkboxes')?.closest('.form-group');
|
||||
if (compSection) compSection.style.display = currentUser?.role === 'superadmin' ? '' : 'none';
|
||||
// Yrityscheckboxit
|
||||
// Yrityscheckboxit + yrityskohtaiset roolit
|
||||
const allComps = availableCompanies.length > 0 ? availableCompanies : [];
|
||||
const userComps = user ? (user.companies || []) : [];
|
||||
const companyRoles = user ? (user.company_roles || {}) : {};
|
||||
const container = document.getElementById('user-company-checkboxes');
|
||||
function renderCompanyCheckboxes(companies) {
|
||||
container.innerHTML = companies.map(c => {
|
||||
const checked = userComps.includes(c.id);
|
||||
const role = companyRoles[c.id] || 'user';
|
||||
return `<div style="display:flex;align-items:center;gap:0.5rem;">
|
||||
<label style="display:flex;align-items:center;gap:0.3rem;cursor:pointer;min-width:150px;">
|
||||
<input type="checkbox" class="user-company-cb" value="${c.id}" ${checked ? 'checked' : ''}>
|
||||
${esc(c.nimi)}
|
||||
</label>
|
||||
<select class="user-company-role" data-company-id="${c.id}" style="padding:4px 8px;border:1px solid #ddd;border-radius:4px;font-size:0.85rem;${checked ? '' : 'opacity:0.4;pointer-events:none;'}">
|
||||
<option value="user" ${role === 'user' ? 'selected' : ''}>Käyttäjä</option>
|
||||
<option value="admin" ${role === 'admin' ? 'selected' : ''}>Admin</option>
|
||||
</select>
|
||||
</div>`;
|
||||
}).join('');
|
||||
// Checkbox toggle: näytä/piilota rooli-dropdown
|
||||
container.querySelectorAll('.user-company-cb').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
const sel = container.querySelector(`.user-company-role[data-company-id="${cb.value}"]`);
|
||||
if (sel) {
|
||||
sel.style.opacity = cb.checked ? '1' : '0.4';
|
||||
sel.style.pointerEvents = cb.checked ? '' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// Hae kaikki yritykset admin-näkymää varten
|
||||
apiCall('companies_all').then(companies => {
|
||||
container.innerHTML = companies.map(c =>
|
||||
`<label style="display:flex;align-items:center;gap:0.3rem;cursor:pointer;">
|
||||
<input type="checkbox" class="user-company-cb" value="${c.id}" ${userComps.includes(c.id) ? 'checked' : ''}>
|
||||
${esc(c.nimi)}
|
||||
</label>`
|
||||
).join('');
|
||||
renderCompanyCheckboxes(companies);
|
||||
}).catch(() => {
|
||||
container.innerHTML = allComps.map(c =>
|
||||
`<label style="display:flex;align-items:center;gap:0.3rem;cursor:pointer;">
|
||||
<input type="checkbox" class="user-company-cb" value="${c.id}" ${userComps.includes(c.id) ? 'checked' : ''}>
|
||||
${esc(c.nimi)}
|
||||
</label>`
|
||||
).join('');
|
||||
renderCompanyCheckboxes(allComps);
|
||||
});
|
||||
// Allekirjoitukset per postilaatikko
|
||||
const sigSection = document.getElementById('user-signatures-section');
|
||||
@@ -1140,6 +1177,14 @@ document.getElementById('user-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const id = document.getElementById('user-form-id').value;
|
||||
const companies = [...document.querySelectorAll('.user-company-cb:checked')].map(cb => cb.value);
|
||||
// Kerää yrityskohtaiset roolit
|
||||
const company_roles = {};
|
||||
document.querySelectorAll('.user-company-role').forEach(sel => {
|
||||
const cid = sel.dataset.companyId;
|
||||
if (companies.includes(cid)) {
|
||||
company_roles[cid] = sel.value;
|
||||
}
|
||||
});
|
||||
// Kerää allekirjoitukset
|
||||
const signatures = {};
|
||||
document.querySelectorAll('.sig-textarea').forEach(ta => {
|
||||
@@ -1153,6 +1198,7 @@ document.getElementById('user-form').addEventListener('submit', async (e) => {
|
||||
email: document.getElementById('user-form-email').value,
|
||||
role: document.getElementById('user-form-role').value,
|
||||
companies,
|
||||
company_roles,
|
||||
signatures,
|
||||
};
|
||||
const pw = document.getElementById('user-form-password').value;
|
||||
@@ -1166,7 +1212,7 @@ document.getElementById('user-form').addEventListener('submit', async (e) => {
|
||||
// Päivitä omat allekirjoitukset (check_auth palauttaa tuoreet)
|
||||
const auth = await apiCall('check_auth');
|
||||
if (auth.authenticated) {
|
||||
currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, id: auth.user_id };
|
||||
currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, company_role: auth.company_role || '', id: auth.user_id };
|
||||
currentUserSignatures = auth.signatures || {};
|
||||
}
|
||||
} catch (e) { alert(e.message); }
|
||||
@@ -1233,7 +1279,7 @@ document.getElementById('profile-form').addEventListener('submit', async (e) =>
|
||||
// Päivitä UI
|
||||
const auth = await apiCall('check_auth');
|
||||
if (auth.authenticated) {
|
||||
currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, id: auth.user_id };
|
||||
currentUser = { username: auth.username, nimi: auth.nimi, role: auth.role, company_role: auth.company_role || '', id: auth.user_id };
|
||||
currentUserSignatures = auth.signatures || {};
|
||||
document.getElementById('user-info').textContent = auth.nimi || auth.username;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user