add rbac + db-create script

This commit is contained in:
2026-04-25 13:31:02 +00:00
parent 44f8ecdc85
commit 84c3d9f9ba
21 changed files with 908 additions and 336 deletions

View File

@@ -1,31 +1,27 @@
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { fn, col, where } = require('sequelize');
/**
* Authentication class for login method, token validation and password setting
*/
class AuthenticationManager {
constructor(model, secretKey) {
constructor(model, secretKey, databaseModel) {
this.Authentication = model;
this.SECRET_KEY = secretKey;
this.databaseModel = databaseModel;
}
/**
* Helper: Case-insensitive User Lookup
*/
// =========================================================
// USER
// =========================================================
async findUser(sAMAccountName) {
return await this.Authentication.findOne({
where: where(
fn('LOWER', col('sAMAccountName')),
sAMAccountName.toLowerCase()
)
return this.Authentication.findOne({
where: { sAMAccountName }
});
}
/**
* Set or reset password of user
*/
// =========================================================
// PASSWORD
// =========================================================
async setPassword(sAMAccountName, password) {
const user = await this.findUser(sAMAccountName);
@@ -33,16 +29,88 @@ class AuthenticationManager {
return { token: null, levelId: 2, message: 'Unbekannter User' };
}
const hashedPassword = await bcrypt.hash(password, 10);
user.password = hashedPassword;
user.password = await bcrypt.hash(password, 10);
await user.save();
return { token: null, levelId: 0, message: 'Passwort gesetzt' };
}
/**
* Login
*/
// =========================================================
// RBAC RESOLVER (LIVE - WICHTIG!)
// =========================================================
async resolvePermissions(objectGuid) {
const AuthenticationGroups = this.databaseModel.get('authenticationGroupsModel');
const GroupClosure = this.databaseModel.get('groupClosureModel');
const AuthenticationRoles = this.databaseModel.get('authenticationRolesModel');
const GroupRoles = this.databaseModel.get('groupRolesModel');
const RolePermissions = this.databaseModel.get('rolePermissionsModel');
const Permission = this.databaseModel.get('permissionModel');
// 1. USER GROUPS
const userGroups = await AuthenticationGroups.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const directGroupIds = userGroups.map(g => g.Group_ObjectGUID);
// 2. NESTED GROUPS
let allGroupIds = [...directGroupIds];
if (directGroupIds.length) {
const closure = await GroupClosure.findAll({
where: { ParentGroup_ObjectGUID: directGroupIds }
});
allGroupIds.push(...closure.map(c => c.ChildGroup_ObjectGUID));
}
allGroupIds = [...new Set(allGroupIds)];
// 3. ROLES
const userRoles = await AuthenticationRoles.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const groupRoles = await GroupRoles.findAll({
where: { Group_ObjectGUID: allGroupIds }
});
const roleIds = [
...new Set([
...userRoles.map(r => r.Role_ID),
...groupRoles.map(r => r.Role_ID)
])
];
// 4. PERMISSIONS
const rolePerms = await RolePermissions.findAll({
where: { Role_ID: roleIds }
});
const permissionIds = rolePerms.map(r => r.Permission_ID);
const permissions = await Permission.findAll({
where: { ID: permissionIds }
});
// 🔥 HIER DIE ÄNDERUNG
return {
groups: allGroupIds,
roles: roleIds,
permissions: permissions.map(p => ({
id: p.ID,
scope: p.Scope,
resource: p.Resource,
action: p.Action
}))
};
}
// =========================================================
// LOGIN (minimal change)
// =========================================================
async login(sAMAccountName, password) {
const user = await this.findUser(sAMAccountName);
@@ -55,20 +123,20 @@ class AuthenticationManager {
return { token: null, levelId: 1, message: 'Benutzer nicht registriert' };
}
const passwordMatch = await bcrypt.compare(password, user.password);
const ok = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
if (!ok) {
return { token: null, levelId: 2, message: 'Falsches Passwort' };
}
const payload = {
sAMAccountName: user.sAMAccountName,
mail: user.mail,
givenName: user.givenName,
sn: user.sn
};
const token = jwt.sign(payload, this.SECRET_KEY, { expiresIn: '100y' });
const token = jwt.sign(
{
ObjectGUID: user.ObjectGUID,
sAMAccountName: user.sAMAccountName
},
this.SECRET_KEY,
{ expiresIn: '10s' }
);
user.refreshtoken = token;
user.online = true;
@@ -81,9 +149,10 @@ class AuthenticationManager {
};
}
/**
* Logout
*/
// =========================================================
// LOGOUT
// =========================================================
async logout(sAMAccountName) {
const user = await this.findUser(sAMAccountName);
@@ -98,14 +167,15 @@ class AuthenticationManager {
return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' };
}
/**
* Token prüfen
*/
// =========================================================
// VERIFY TOKEN (unchanged)
// =========================================================
async verifyUserToken(sAMAccountName) {
const user = await this.findUser(sAMAccountName);
if (!user || !user.refreshtoken) {
return { valid: false, levelId: 1, message: 'Kein gültiger Token' };
return { valid: false, levelId: 1 };
}
try {
@@ -113,42 +183,45 @@ class AuthenticationManager {
return {
valid: true,
payload,
user,
levelId: 0,
message: 'User verifiziert'
payload
};
} catch {
return {
valid: false,
levelId: 4,
message: 'Ungültiger Token'
};
return { valid: false, levelId: 4 };
}
}
/**
* Middleware
*/
// =========================================================
// 🔥 AUTH MIDDLEWARE (HIER PASSIERT DIE MAGIE)
// =========================================================
authenticate() {
return async (req, res, next) => {
try {
const sAMAccountName = req.cookies?.sAMAccountName;
const objectGUID = req.cookies?.ObjectGUID;
if (!sAMAccountName || !objectGUID) {
if (!sAMAccountName) {
return res.redirect('/login');
}
const user = await this.findUser(sAMAccountName);
if (!user || !user.refreshtoken || user.active === false) {
if (!user || !user.refreshtoken || !user.active) {
return res.redirect('/login');
}
jwt.verify(user.refreshtoken, this.SECRET_KEY);
// jwt.verify(user.refreshtoken, this.SECRET_KEY);
this.verifyUserToken(sAMAccountName)
// 🔥 LIVE RBAC RESOLUTION (bei JEDEM REQUEST)
const rbac = await this.resolvePermissions(user.ObjectGUID);
req.user = user;
req.user = {
...user.toJSON(),
groups: rbac.groups,
roles: rbac.roles,
permissions: rbac.permissions
};
console.log(req.user)
next();
} catch (err) {
console.error(err);
@@ -158,177 +231,4 @@ class AuthenticationManager {
}
}
module.exports = AuthenticationManager;
// const jwt = require('jsonwebtoken');
// const bcrypt = require('bcryptjs');
// let { levelId, message } = '';
// /**
// * Authentication class for login method, token validation and password setting
// */
// class AuthenticationManager {
// /**
// *
// * @param {object} model - Use the authentication database model for interact with the database
// * @param {string} secretKey - Defines the server secret for token validation
// */
// constructor(model, secretKey, eventManager) {
// this.eventManager = eventManager;
// // if (!model) throw new Error('Sequelize Model wird benötigt');
// // if (!secretKey) throw new Error('Secret Key wird benötigt');
// this.Authentication = model;
// this.SECRET_KEY = secretKey;
// }
// /**
// * Set or reset password of user
// * @param {string} sAMAccountName - Windows account name
// * @param {string} password - Set the new password
// */
// async setPassword(sAMAccountName, password) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user) {
// // this.eventManager.write(null, 2, 0, { aboveLevel: 1 }, `User nicht gefunden`);
// levelId = 2;
// message = `Unbekannter User`
// return {token: null, levelId: levelId };
// // throw new Error(`User ${sAMAccountName} nicht gefunden`);
// }
// // if (user.password) throw new Error('Passwort bereits gesetzt');
// const hashedPassword = await bcrypt.hash(password, 10);
// user.password = hashedPassword;
// await user.save();
// }
// /**
// * Login mit Speicherung des Tokens in der Datenbank
// */
// async login(sAMAccountName, password) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user) {
// //this.eventManager.write(null, 2, null, null, `User ${sAMAccountName} nicht geufunden`)
// levelId = 2;
// message = `Unbekannter Benutzer`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('Unkown user');
// }
// if (!user.password) {
// this.setPassword(sAMAccountName, password);
// // this.eventManager.write(user.ObjectGUID, 2, null, null, 'User registration initialized')
// levelId = 1;
// message = `Benutzer nicht registiert`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('User not registered');
// }
// const passwordMatch = await bcrypt.compare(password, user.password);
// if (!passwordMatch) {
// // this.eventManager.write(user.ObjectGUID, 2, null, null, 'Password doesn\'t match');
// levelId = 2;
// message = `Falsches Passwort`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('Wrong password');
// }
// // Token erzeugen
// const payload = {
// sAMAccountName: user.sAMAccountName,
// mail: user.mail,
// givenName: user.givenName,
// sn: user.sn
// };
// const token = jwt.sign(payload, this.SECRET_KEY, { expiresIn: '100y' });
// // Token in DB speichern
// user.refreshtoken = token;
// user.online = true;
// await user.save();
// // this.eventManager.write(user.ObjectGUID, 1, null, null, 'Erfolgreich angemeldet');
// levelId = 0;
// message = `Erfolgreich angemeldet`;
// return { token: token, levelId: levelId, message: message };
// }
// /**
// * Logout löscht Token aus der DB
// */
// async logout(sAMAccountName) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (user) {
// user.refreshtoken = null;
// user.online = false;
// await user.save();
// levelId = 0;
// message = `Erfolgreich abgemeldet`;
// return { token: null, levelId: levelId, message: message };
// }
// }
// /**
// * Token-Prüfung (über DB)
// */
// async verifyUserToken(sAMAccountName) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user || !user.refreshtoken) {
// levelId = 1,
// message = `Kein gültiger Token`;
// // throw new Error('Kein gespeicherter Token gefunden');
// }
// try {
// const payload = jwt.verify(user.refreshtoken, this.SECRET_KEY);
// levelId = 0;
// message = `User verifiziert`;
// return { valid: true, payload, user, levelId: levelId, message: message }
// } catch {
// levelId = 4;
// message = `Ungültiger Token`;
// return { valid: false, payload, user, levelId: levelId, message: message }
// }
// }
// /**
// * Express Middleware prüft Token direkt aus DB anhand sAMAccountNamec
// */
// authenticate() {
// return async (req, res, next) => {
// try {
// const sAMAccountName = req.cookies?.sAMAccountName;
// const objectGUID = req.cookies?.ObjectGUID;
// if (!sAMAccountName || !objectGUID) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Kein Benutzer-Cookie gefunden' });
// }
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user || !user.refreshtoken) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Benutzer oder Token nicht gefunden' });
// }
// if (user.active === false) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Benutzer ist nicht aktiv' });
// }
// // Token aus DB prüfen
// const payload = jwt.verify(user.refreshtoken, this.SECRET_KEY);
// req.user = user;
// next();
// } catch (err) {
// console.error(err);
// return res.redirect('/login');
// // res.status(401).json({ message: 'Authentifizierung fehlgeschlagen' });
// }
// };
// }
// }
// module.exports = AuthenticationManager;
module.exports = AuthenticationManager;