continued

implemented HTMX
implemented ORM (sequelize)
This commit is contained in:
Kai Waggeling 2025-11-29 21:56:21 +01:00
parent 2a9bd4e81b
commit d756a192e4
71 changed files with 3822 additions and 694 deletions

View file

@ -0,0 +1,59 @@
import {
Group,
User
} from "../../../../lib/database/connect.mjs";
import {
getConfig
} from "../../../../lib/config.mjs";
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
const dbGroup = await Group.findByPk(request.params.groupid);
const config = await getConfig();
const userList = await User.findAll({
attributes: [
'id',
'uidnumber',
'name',
'othergroups',
'primarygroup'
]
}).filter((user) => {
// check if users primary group is the group being edited
if (user.primarygroup == dbGroup.gidnumber) {
return true;
}
// check if users other groups include the group being edited
if (user.othergroups.split(',').map(gidnumber => parseInt(gidnumber)).includes(dbGroup.gidnumber)) {
return true;
}
// else exclude user
return false;
}).map((user) => {
return {
id: user.id,
name: user.name,
uidnumber: user.uidnumber,
};
});
response.render(`views/htmx/admin/editGroup.njk`, {
group: {
id: dbGroup.id,
name: dbGroup.name,
gidnumber: dbGroup.gidnumber,
},
ldap: {
baseDN: config.ldap.baseDN,
},
userList: userList
});
}

View file

@ -0,0 +1,18 @@
import {
Group
} from "../../../../lib/database/connect.mjs";
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
let groupList = await Group.findAll();
response.render(`views/htmx/admin/groupTable.njk`, {
groupList: groupList
});
}

View file

@ -0,0 +1,53 @@
import {
Group,
User
} from "../../../../lib/database/connect.mjs";
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
const dbUser = await User.findByPk(request.params.userid);
if (!dbUser) {
response.status(404).end('User not found');
return;
}
const userGroups = await (async () => {
if (!dbUser.othergroups || dbUser.othergroups.length == 0) {
return [];
} else {
return dbUser.othergroups.split(',').map(gid => parseInt(gid));
}
})();
const groupList = (await Group.findAll()).map((dbGroup) => {
return {
id: dbGroup.id,
gidnumber: dbGroup.gidnumber,
name: dbGroup.name,
isPrimaryGroup: dbUser.primarygroup == dbGroup.gidnumber,
isOtherGroup: userGroups.includes(dbGroup.gidnumber)
};
});
console.log(groupList);
response.render(`views/htmx/admin/editUser.njk`, {
user: {
id: dbUser.id,
username: dbUser.name,
uidnumber: dbUser.uidnumber,
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
disabled: dbUser.disabled,
},
groupList: groupList
});
}

View file

@ -0,0 +1,18 @@
import {
User
} from "../../../../lib/database/connect.mjs";
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
let userList = await User.findAll();
response.render(`views/htmx/admin/userTable.njk`, {
userList: userList
});
}

75
routes/htmx/authForm.mjs Normal file
View file

@ -0,0 +1,75 @@
import crypto from "crypto";
import {
User
} from "../../lib/database/connect.mjs";
function sendAuthForm(response, errors=[]) {
response.render(`views/htmx/authForm.njk`, {
errors: errors
});
}
export const get = async function(request, response) {
sendAuthForm(response);
// response.set('HX-Redirect', '/profile').status(200).end();
}
export const post = async function(request, response) {
if (!request.body.username || !request.body.password) {
sendAuthForm(response, [{
title: 'Username and password are required',
detail: 'Username or Password was not received.'
}]);
return;
}
if (typeof request.body.username != 'string' || typeof request.body.password != 'string') {
sendAuthForm(response, [{
title: 'Invalid input types',
detail: 'Username and Password must be strings.'
}]);
return;
}
let username = request.body.username;
let password = crypto.createHash('sha256').update(request.body.password).digest('hex')
// let loginResult = await login(username, password)
let loginUser = await User.findOne({
where: {
name: username,
passsha256: password
}
});
if (loginUser == null) {
sendAuthForm(response, [{
title: 'Login failed',
detail: 'Invalid Username or Password.'
}]);
return;
}
if (loginUser.disabled == 1) {
sendAuthForm(response, [{
title: 'User disabled',
detail: 'This user account is disabled.'
}]);
return;
}
request.session.userid = loginUser.id;
request.session.save();
if (loginUser.otpsecret == '' || loginUser.otpsecret == null) {
request.setAuthState('authenticated');
response.set('HX-Redirect', '/profile').status(200).end();
} else {
request.setAuthState('totp-verfication');
response.redirect('/htmx/totpForm');
}
}

View file

@ -0,0 +1,27 @@
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
let dbUser = await request.getUser();
response.render(`views/htmx/profile/editData.njk`, {
user: {
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
},
data: {
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
}
});
}
export const post = async function (request, response) {
console.log(request.body);
response.redirect("/login");
}

View file

@ -0,0 +1,29 @@
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
let dbUser = await request.getUser();
response.render(`views/htmx/profile/showData.njk`, {
user: {
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
},
data: {
uidnumber: dbUser.uidnumber,
username: dbUser.name,
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
}
});
}
export const post = async function (request, response) {
console.log(request.body);
response.redirect("/login");
}

View file

@ -0,0 +1,30 @@
import {
generateOTPQRCode
} from "../../../../lib/otp.mjs";
export const get = async function (request, response) {
if (request.getAuthState() != 'authenticated') {
response.set('HX-Redirect', '/login').status(401).end();
return;
}
let dbUser = await request.getUser();
response.render(`views/htmx/profile/showMFA.njk`, {
user: {
firstName: dbUser.givenname,
lastName: dbUser.sn,
mail: dbUser.mail,
},
data: {
otpsecret: dbUser.otpsecret,
yubikey: dbUser.yubikey,
qrcode: await generateOTPQRCode(dbUser.mail, dbUser.otpsecret)
}
});
}
export const post = async function (request, response) {
console.log(request.body);
response.redirect("/login");
}

53
routes/htmx/totpForm.mjs Normal file
View file

@ -0,0 +1,53 @@
import {
validateOTPCode
} from "../../lib/otp.mjs";
function sendTOTPForm(response, errors=[]) {
response.render(`views/htmx/totpForm.njk`, {
errors: errors
});
}
export const get = async function(request, response) {
if (request.getAuthState() != 'totp-verfication') {
response.redirect('/htmx/authForm');
return;
}
sendTOTPForm(response);
}
export const post = async function(request, response) {
// redirect if not in TOTP verification state
if (request.getAuthState() != 'totp-verfication') {
response.redirect('/htmx/authForm');
return;
}
// validate input
if (!request.body.otpToken || typeof request.body.otpToken != 'string') {
sendTOTPForm(response, [{
title: 'OTP token is required',
detail: 'no OTP token was received.'
}]);
return;
}
let otpToken = request.body.otpToken;
let dbUser = await request.getUser();
let validationResult = await validateOTPCode(dbUser.mail, dbUser.otpsecret, otpToken);
if (validationResult != null) {
request.setAuthState('authenticated');
response.set('HX-Redirect', '/profile').status(200).end();
} else {
sendTOTPForm(response, [{
title: 'OTP validation failed',
detail: 'the provided OTP token is invalid. Please try again.'
}]);
}
}