const ctxRBAC = new ContextMenu();
//////////////////////////////
// 🌐 API LAYER
//////////////////////////////
const api = async (url, method = 'GET', body) => {
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined
});
if(res.status >= 400) {
const text = await res.text();
sendUserEvent('RBAC', `Hoppla, da ist etwas schief gelaufen:\r\n${text}`, null, 4);
}
return res.json();
};
//////////////////////////////
// 🧠 RBAC SERVICE LAYER
//////////////////////////////
const RBAC = {
// 👤 USERS
loadUsers: async () => (await api('/api/rbac/auth/get', 'POST'))
.map(({ active, online, ...rest }) => ({
...rest,
Aktiv: active
}))
.sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)),
createUser: (data) => api('/api/rbac/auth/create', 'POST', data),
syncUsersFromAD: () => api('/api/rbac/auth/syncFromAD', 'POST', { }),
deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'),
// 👥 GROUPS
loadGroups: () => api('/api/rbac/group/get', 'POST'),
createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }),
syncGroupsFromAD: () => api('/api/rbac/group/syncFromAD', 'POST', { }),
deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'),
// 🎭 ROLES
loadRoles: () => api('/api/rbac/role/get', 'POST'),
createRole: (name) => api('/api/rbac/role/create', 'POST', { name }),
deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'),
// 🎭 PERMISSIONS
loadPermissions: () => api('/api/rbac/permission/get', 'POST'),
createPermission: (data) => api('/api/rbac/permission/create', 'POST', data),
deletePermission: (id) => api(`/api/rbac/permission/${id}`, 'DELETE'),
// 🔗 ASSIGNMENTS
addUserToGroup: (authGuid, groupGuid) => api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }),
addUserToRole: (authGuid, roleId) => api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }),
addGroupToRole: (groupGuid, roleId) => api('/api/rbac/role/add-group', 'POST', { groupGuid, roleId }),
addPermissionToRole: (roleId, permissionId) => api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId })
};
//////////////////////////////
// 🖱️ DRAG & DROP
//////////////////////////////
function createDragZone(el, data, type) {
el.draggable = true;
el.addEventListener('dragstart', (evt) => {
evt.dataTransfer.setData('application/json', JSON.stringify( { ...data, type }));
});
}
function createDropZone(el, type, target) {
const targetValue = target.ObjectGUID || target.ID || target.Role_ID || target.Permission_ID;
let process = { action: null, response: null, failure: false };
el.addEventListener('dragover', e => e.preventDefault());
el.addEventListener('drop', async (e) => {
e.preventDefault();
const data = JSON.parse(
e.dataTransfer.getData('application/json')
);
if(!targetValue || !data.type) {
sendUserEvent('RBAC', 'Deinem Drop-Ziel wurde kein ID-Attribut zugewiesen. Frag einfach Manuel ', null, 2);
return;
}
// NOCH NICHT UNTER ADMINROUTES EDITIERT: WENN BENUTZER/GRUPPE BEREITS IN ROLLE ENTHALTEN IST
switch (type) {
case 'group':
if(data.type === 'user') {
process.action = `Du hast den Benutzer der Gruppe hinzugefügt`;
process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue);
}
break;
case 'role':
if(data.type === 'user') {
process.action = `Du hast den Benutzer der Rolle hinzugefügt`;
process.response = await RBAC.addUserToRole(data.ObjectGUID, targetValue);
break;
}
if(data.type === 'group') {
process.action = `Du hast die Gruppe der Rolle hinzugefügt`;
process.response = await RBAC.addGroupToRole(data.ObjectGUID, targetValue);
break;
}
case 'permission':
process.action = `Du hast die Berechtigung der Rolle hinzugefügt`;
process.response = await RBAC.addPermissionToRole(targetValue, data.ID);
break;
}
if(process.failure || !process.action) return;
loadUsers();
loadGroups();
loadRoles();
sendUserEvent('RBAC', process.action, null, 0);
});
}
//////////////////////////////
// 📋 TABLE (USERS)
//////////////////////////////
const rbacUsersVT = virtualTable({
tableEl: document.querySelector('#rbacUsersTable'),
data: [],
rowHeight: 20,
buffer: 5,
groupKey: 'ObjectSourceName',
rowKey: 'ObjectGUID',
filterConfig: {
hideCounter: true,
exceptedColumns: ['', 'Rollen', 'Gruppen'],
columnModes: {
Aktiv: 'dropdown',
Online: 'dropdown'
}
},
customRender: (row, tr) => {
createDragZone(tr, row, 'user');
tr.addEventListener('contextmenu', (evt) => {
evt.preventDefault();
ctxRBAC.setItems([
{
label: "Details",
onClick: () => showAuthDetails(row.ObjectGUID)
}
]);
ctxRBAC.show(evt.pageX + 5, { y: evt.pageY + 5 });
});
createTd(tr,
``, {
styles: {
'position': 'sticky',
'left': '0px',
'max-width': '20px',
'z-index': '2'
}, classes: [
'text-align:left'
], onclick: () => {
if(row['ObjectGUID'] === '00000000-0000-0000-0000-000000000001') return;
deleteUser(row['ObjectGUID'], row['sAMAccountName']);
}
});
createTd(tr, row['ObjectGUID'], { classes: [ 'text-align:left' ], styles: { 'max-width': '100px' }, attributes: { 'data-tooltip': row['ObjectGUID'] } });
createTd(tr, row['sAMAccountName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sAMAccountName'] } });
createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ] });
createTd(tr, row['GroupCount'], { classes: [ 'text-align:center' ] });
createTd(tr, row['sn'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sn'] } });
createTd(tr, row['givenName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['givenName'] } });
createTd(tr, row['mail'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['mail'] } });
createTd(tr, row['Aktiv'], { classes: [ 'text-align:center' ] });
}
});
//////////////////////////////
// 📋 TABLE (PERMISSIONS)
//////////////////////////////
const rbacPermissionsVT = virtualTable({
tableEl: document.querySelector('#rbacPermissionsTable'),
data: [],
rowHeight: 20,
buffer: 5,
groupKey: null,
rowKey: 'ID',
filterConfig: {
hideCounter: true,
exceptedColumns: ['', 'ID']
},
customRender: (row, tr) => {
createDragZone(tr, row, 'user');
tr.addEventListener('contextmenu', (evt) => {
evt.preventDefault();
ctxRBAC.setItems([
{
label: "Details",
onClick: () => showAuthDetails(row.ObjectGUID)
}
]);
ctxRBAC.show(evt.pageX + 5, { y: evt.pageY + 5 });
});
createTd(tr,
``, {
styles: {
'position': 'sticky',
'left': '0px',
'max-width': '20px',
'z-index': '2'
}, classes: [
'text-align:left'
], onclick: () => {
if(row['ID'] === 1) return;
deletePermission(row['ID'], `${row['Scope']}.${row['Resource']}.${row['Action']}`);
}
});
createTd(tr, row['Permission_ID'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } } );
createTd(tr, row['GroupUserCount'], { classes: [ 'text-align:center' ] });
createTd(tr, row['TotalUserCount'], { classes: [ 'text-align:center' ] });
createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } });
createTd(tr, row['Scope'], { classes: [ 'text-align:right' ], styles: { 'width': '100px' } });
createTd(tr, row['Resource'], { classes: [ 'text-align:center' ], styles: { 'width': '100px' } });
createTd(tr, row['Action'], { classes: [ 'text-align:left' ], styles: { 'width': '100px' } });
}
});
//////////////////////////////
// 📥 LOADERS
//////////////////////////////
async function loadUsers() {
try {
rbacUsersVT.source(await RBAC.loadUsers());
} catch (err) {
console.error(err);
}
}
async function loadPermissions() {
try {
rbacPermissionsVT.source(await RBAC.loadPermissions());
} catch (err) {
console.error(err);
}
}
async function loadGroups() {
const container = document.getElementById('rbacGroupContainer');
container.innerHTML = '';
const groups = await RBAC.loadGroups();
const fragment = document.createDocumentFragment();
groups.forEach(group => {
const section = document.createElement('section');
const groupName = document.createElement('span');
groupName.innerHTML = `[${group.UserCount}] ${group.Name}`;
groupName.dataset.tooltip = group.Name;
groupName.dataset.tooltipMode = 'ellipsis';
section.append(groupName);
const removeButton = document.createElement('button');
if(group.ObjectGUID === '00000000-0000-0000-0000-000000000001') {
removeButton.disabled = true;
removeButton.dataset.tooltip = `Die Gruppe ${group.Name} kann nicht gelöscht werden`;
}
removeButton.className = 'removeButton';
removeButton.innerHTML = 'X';
removeButton.onclick = async () => {
await deleteGroup(group.ObjectGUID, group.Name);
};
section.append(removeButton);
createDragZone(section, group, 'group');
createDropZone(section, 'group', group);
fragment.appendChild(section);
});
container.appendChild(fragment);
}
async function createGroup() {
const name = document.getElementById('newGroupName').value;
const group = await api('/api/rbac/group/create', 'POST', {
name
});
if(group) {
sendUserEvent('RBAC', `Gruppe ${name} angelegt`, null, 0);
loadGroups();
}
}
async function loadRoles() {
const container = document.getElementById('rbacRoleContainer');
container.innerHTML = '';
const roles = await RBAC.loadRoles();
const fragment = document.createDocumentFragment();
roles.forEach(role => {
const section = document.createElement('section');
const roleName = document.createElement('span');
roleName.innerHTML = `[${role.UserCount}][${role.GroupCount}] ${role.Name}`;
roleName.dataset.tooltip = role.Name;
section.append(roleName);
const removeButton = document.createElement('button');
removeButton.className = 'removeButton';
removeButton.innerHTML = 'X';
removeButton.onclick = async () => {
await deleteRole(role.ID, role.Name);
};
section.append(removeButton);
createDropZone(section, 'role', role);
fragment.appendChild(section);
});
container.appendChild(fragment);
}
//////////////////////////////
// 👤 USER ACTIONS
//////////////////////////////
async function createUser() {
const name = document.getElementById('newUserName').value;
if (!name || !name.includes('.')) return;
const [givenName, sn] = name.split('.');
const user = await RBAC.createUser({
sAMAccountName: name,
mail: `${name}@test.com`,
givenName: givenName[0].toUpperCase() + givenName.slice(1),
sn: sn[0].toUpperCase() + sn.slice(1)
});
sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName} [${user.ObjectGUID}] angelegt`, null, 0);
loadUsers();
}
async function syncUsersFromAD() {
const users = await RBAC.syncUsersFromAD();
sendUserEvent('RBAC', `${users.length} Benutzer aus dem AD synchronisiert`, null, 0);
loadUsers();
}
async function deleteUser(guid, name) {
feedbox({
title: `Benutzer löschen`,
message: `Soll der Benutzer ${name} und
alle seine Zugehörigkeiten entfernt werden`,
buttons: {
no: { text: 'Nein' },
yes: {
text: 'Ja',
onClick: async () => {
const user = await RBAC.deleteUser(guid);
sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName || ''} [${user.ObjectGUID}] gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
//////////////////////////////
// 👥 GROUP ACTIONS
//////////////////////////////
async function deleteGroup(guid, name) {
feedbox({
title: `Gruppe löschen`,
message: `Möchtest du die Gruppe ${name} [${guid}] wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: 'Ja',
onClick: async () => {
const group = await RBAC.deleteGroup(guid);
sendUserEvent('RBAC', `Du hast die Gruppe ${name || ''} [${group.ObjectGUID}] gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
async function syncGroupsFromAD() {
const group = await RBAC.syncGroupsFromAD();
sendUserEvent('RBAC', `${group.length} Gruppen aus dem AD synchronisiert`, null, 0);
loadGroups();
loadUsers();
}
//////////////////////////////
// 🎭 ROLE ACTIONS
//////////////////////////////
async function createRole() {
const name = document.getElementById('newRoleName').value;
if (!name) return;
const role = await RBAC.createRole(name);
sendUserEvent('RBAC', `Rolle ${name} angelegt`, null, 0);
loadRoles();
}
async function deleteRole(id, name) {
feedbox({
title: `Rolle löschen`,
message: `Möchtest du die Rolle ${name} wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: 'Ja',
onClick: async () => {
await RBAC.deleteRole(id);
sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions(); // optional, falls RoleCount betroffen
}
}
}
});
}
//////////////////////////////
// 🎭 PERMISSION ACTIONS
//////////////////////////////
async function createPermission() {
const scope = document.getElementById('permScope').value;
const resource = document.getElementById('permResource').value;
const action = document.getElementById('permAction').value;
if (!scope || !resource || !action) return;
const permission = await RBAC.createPermission( { scope, resource, action } );
sendUserEvent('RBAC', `Berechtigung ${scope}.${resource}.${action} angelegt`, null, 0);
loadPermissions();
}
async function deletePermission(id, name) {
feedbox({
title: `Berechtigung löschen`,
message: `Möchtest du die Berechtigung ${name} wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: 'Ja',
onClick: async () => {
await RBAC.deletePermission(id);
sendUserEvent('RBAC', `Berechtigung ${name} gelöscht`, null, 0);
loadUsers();
loadGroups();
loadRoles();
loadPermissions();
}
}
}
});
}
//////////////////////////////
// 🚀 INIT
//////////////////////////////
loadUsers();
loadGroups();
loadRoles();
loadPermissions();