mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-07 07:50:26 +00:00
Add success and error message feedback for user profile operations (#8716)
* mws authentication * add more tests and permission checkers * add logic to ensure that only authenticated users' requests are handled * add custom login page * Implement user authentication as well as session handling * work on user operations authorization * add middleware to route handlers for bags & tiddlers routes * add feature that only returns the tiddlers and bags which the user has permission to access on index page * refactor auth routes & added user management page * fix Ci Test failure issue * fix users list page, add manage roles page * add commands and scripts to create new user & assign roles and permissions * resolved ci-test failure * add ACL permissions to bags & tiddlers on creation * fix comments and access control list bug * fix indentation issues * working on user profile edit * remove list users command & added support for database in server options * implement user profile update and password change feature * update plugin readme * implement command which triggers protected mode on the server * revert server-wide auth flag. Implement selective authorization * ACL management feature * Complete Access control list implementation * Added support to manage users' assigned role by admin * fix comments * fix comment * Add user profile management and account deletion functionality * add success and error message feedback for user profile operations * fix indentation issues * Add command to create admin user if none exists when the start command is executed * refactor annonymous user flow with create admin implementation * remove mws-add-user from start command
This commit is contained in:
parent
3a5f67d4f5
commit
316bd65296
@ -11,15 +11,6 @@
|
||||
"tiddlywiki/snowwhite"
|
||||
],
|
||||
"build": {
|
||||
"mws-add-user": [
|
||||
"--mws-add-permission", "READ", "Allows user to create tiddlers",
|
||||
"--mws-add-permission", "WRITE", "Gives the user the permission to edit and delete tiddlers",
|
||||
"--mws-add-role", "ADMIN", "System Administrator",
|
||||
"--mws-assign-role-permission", "ADMIN", "READ",
|
||||
"--mws-assign-role-permission", "ADMIN", "WRITE",
|
||||
"--mws-add-user", "user", "pass123",
|
||||
"--mws-assign-user-role", "user", "ADMIN"
|
||||
],
|
||||
"load-mws-demo-data": [
|
||||
"--mws-load-wiki-folder","./editions/tw5.com","docs", "TiddlyWiki Documentation from https://tiddlywiki.com","docs","TiddlyWiki Documentation from https://tiddlywiki.com",
|
||||
"--mws-load-wiki-folder","./editions/dev","dev","TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev","dev-docs", "TiddlyWiki Developer Documentation from https://tiddlywiki.com/dev",
|
||||
|
@ -38,8 +38,6 @@ Command.prototype.execute = function() {
|
||||
var description = this.params[1];
|
||||
|
||||
$tw.mws.store.sqlTiddlerDatabase.createPermission(permission_name, description);
|
||||
|
||||
console.log(permission_name+" Permission Created Successfully!")
|
||||
self.callback();
|
||||
return null;
|
||||
};
|
||||
|
@ -38,8 +38,6 @@ Command.prototype.execute = function() {
|
||||
var description = this.params[1];
|
||||
|
||||
$tw.mws.store.sqlTiddlerDatabase.createRole(role_name, description);
|
||||
|
||||
console.log(role_name+" Role Created Successfully!")
|
||||
self.callback(null, "Role Created Successfully!");
|
||||
return null;
|
||||
};
|
||||
|
@ -43,11 +43,9 @@ Command.prototype.execute = function() {
|
||||
|
||||
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);
|
||||
|
||||
if(user) {
|
||||
self.callback("WARNING: An account with the username (" + username + ") already exists");
|
||||
} else {
|
||||
if(!user) {
|
||||
$tw.mws.store.sqlTiddlerDatabase.createUser(username, email, hashedPassword);
|
||||
console.log("User Account Created Successfully!")
|
||||
console.log("User Account Created Successfully with username: " + username + " and password: " + password);
|
||||
self.callback();
|
||||
}
|
||||
return null;
|
||||
|
@ -51,8 +51,6 @@ Command.prototype.execute = function() {
|
||||
|
||||
|
||||
$tw.mws.store.sqlTiddlerDatabase.addPermissionToRole(role.role_id, permission.permission_id);
|
||||
|
||||
console.log(permission_name+" permission assigned to "+role_name+" role successfully!")
|
||||
self.callback();
|
||||
return null;
|
||||
};
|
||||
|
@ -119,20 +119,6 @@ TestRunner.prototype.runTest = function(testSpec,callback) {
|
||||
};
|
||||
|
||||
const testSpecs = [
|
||||
{
|
||||
description: "Login Test User",
|
||||
method: "POST",
|
||||
path: "/login",
|
||||
headers: {
|
||||
"Accept": 'application/json',
|
||||
"Content-Type": 'application/x-www-form-urlencoded',
|
||||
"User-Agent": 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
|
||||
},
|
||||
data: "username=user&password=pass123",
|
||||
expectedResult: (jsonData,data,headers) => {
|
||||
return !!jsonData.sessionId;
|
||||
}
|
||||
},
|
||||
{
|
||||
description: "Check index page",
|
||||
method: "GET",
|
||||
|
@ -441,6 +441,8 @@ Server.prototype.requestHandler = function(request,response,options) {
|
||||
// Check whether anonymous access is granted
|
||||
state.allowAnon = false; //this.isAuthorized(state.authorizationType,null);
|
||||
|
||||
state.firstGuestUser = this.sqlTiddlerDatabase.listUsers().length === 0 && !state.authenticatedUser;
|
||||
|
||||
// Authorize with the authenticated username
|
||||
if(!this.isAuthorized(state.authorizationType,state.authenticatedUsername) && !response.headersSent) {
|
||||
response.writeHead(403,"'" + state.authenticatedUsername + "' is not authorized to access '" + this.servername + "'");
|
||||
|
@ -22,28 +22,44 @@ exports.bodyFormat = "www-form-urlencoded";
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function (request, response, state) {
|
||||
var userId = state.data.userId;
|
||||
// Clean up any existing error/success messages
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/change-password/" + userId + "/error");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/change-password/" + userId + "/success");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/login/error");
|
||||
|
||||
if(!state.authenticatedUser) {
|
||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||
response.end("Unauthorized");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/login/error",
|
||||
text: "You must be logged in to change passwords"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/login" });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
var auth = authenticator(state.server.sqlTiddlerDatabase);
|
||||
|
||||
var userId = state.data.userId;
|
||||
var auth = authenticator(state.server.sqlTiddlerDatabase);
|
||||
var newPassword = state.data.newPassword;
|
||||
var confirmPassword = state.data.confirmPassword;
|
||||
var currentUserId = state.authenticatedUser.user_id;
|
||||
|
||||
var hasPermission = ($tw.utils.parseInt(userId, 10) === currentUserId) || state.authenticatedUser.isAdmin;
|
||||
var hasPermission = ($tw.utils.parseInt(userId) === currentUserId) || state.authenticatedUser.isAdmin;
|
||||
|
||||
if(!hasPermission) {
|
||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||
response.end("Forbidden");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/change-password/" + userId + "/error",
|
||||
text: "You don't have permission to change this user's password"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if(newPassword !== confirmPassword) {
|
||||
response.setHeader("Set-Cookie", "flashMessage=New passwords do not match; Path=/; HttpOnly; Max-Age=5");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/change-password/" + userId + "/error",
|
||||
text: "New passwords do not match"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
return;
|
||||
@ -52,7 +68,10 @@ exports.handler = function (request, response, state) {
|
||||
var userData = state.server.sqlTiddlerDatabase.getUser(userId);
|
||||
|
||||
if(!userData) {
|
||||
response.setHeader("Set-Cookie", "flashMessage=User not found; Path=/; HttpOnly; Max-Age=5");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/change-password/" + userId + "/error",
|
||||
text: "User not found"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
return;
|
||||
@ -61,7 +80,10 @@ exports.handler = function (request, response, state) {
|
||||
var newHash = auth.hashPassword(newPassword);
|
||||
var result = state.server.sqlTiddlerDatabase.updateUserPassword(userId, newHash);
|
||||
|
||||
response.setHeader("Set-Cookie", `flashMessage=${result.message}; Path=/; HttpOnly; Max-Age=5`);
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/change-password/" + userId + "/success",
|
||||
text: result.message
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
};
|
||||
|
@ -8,51 +8,86 @@ POST /delete-user-account
|
||||
\*/
|
||||
(function () {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "POST";
|
||||
exports.method = "POST";
|
||||
|
||||
exports.path = /^\/delete-user-account\/?$/;
|
||||
exports.path = /^\/delete-user-account\/?$/;
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
|
||||
exports.csrfDisable = true;
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var userId = state.data.userId;
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var userId = state.data.userId;
|
||||
|
||||
// Check if user is admin
|
||||
if(!state.authenticatedUser || !state.authenticatedUser.isAdmin) {
|
||||
response.writeHead(403, "Forbidden");
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent admin from deleting their own account
|
||||
if(state.authenticatedUser.user_id === userId) {
|
||||
response.writeHead(400, "Bad Request");
|
||||
response.end("Cannot delete your own account");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user exists
|
||||
var user = sqlTiddlerDatabase.getUser(userId);
|
||||
if(!user) {
|
||||
response.writeHead(404, "Not Found");
|
||||
response.end("User not found");
|
||||
return;
|
||||
}
|
||||
|
||||
sqlTiddlerDatabase.deleteUserRolesByUserId(userId);
|
||||
sqlTiddlerDatabase.deleteUserSessions(userId);
|
||||
sqlTiddlerDatabase.deleteUser(userId);
|
||||
|
||||
// Redirect back to the users management page
|
||||
response.writeHead(302, { "Location": "/admin/users" });
|
||||
// Check if user is admin
|
||||
if(!state.authenticatedUser || !state.authenticatedUser.isAdmin) {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/delete-user/error",
|
||||
text: "You must be an administrator to delete user accounts"
|
||||
}));
|
||||
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||
response.end();
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent admin from deleting their own account
|
||||
if(state.authenticatedUser.user_id === userId) {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/delete-user/error",
|
||||
text: "Cannot delete your own account"
|
||||
}));
|
||||
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user exists
|
||||
var user = sqlTiddlerDatabase.getUser(userId);
|
||||
if(!user) {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/delete-user/error",
|
||||
text: "User not found"
|
||||
}));
|
||||
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the last admin account
|
||||
var adminRole = sqlTiddlerDatabase.getRoleByName("ADMIN");
|
||||
if(!adminRole) {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/delete-user/error",
|
||||
text: "Admin role not found"
|
||||
}));
|
||||
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var adminUsers = sqlTiddlerDatabase.listUsersByRoleId(adminRole.role_id);
|
||||
if(adminUsers.length <= 1 && adminUsers.some(admin => admin.user_id === parseInt(userId))) {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/delete-user/error",
|
||||
text: "Cannot delete the last admin account"
|
||||
}));
|
||||
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
sqlTiddlerDatabase.deleteUserRolesByUserId(userId);
|
||||
sqlTiddlerDatabase.deleteUserSessions(userId);
|
||||
sqlTiddlerDatabase.deleteUser(userId);
|
||||
|
||||
// Redirect back to the users management page
|
||||
response.writeHead(302, { "Location": "/admin/users" });
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -7,91 +7,91 @@ GET /admin/acl
|
||||
|
||||
\*/
|
||||
(function () {
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/admin\/acl\/(.+)$/;
|
||||
exports.path = /^\/admin\/acl\/(.+)$/;
|
||||
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var params = state.params[0].split("/")
|
||||
var recipeName = params[0];
|
||||
var bagName = params[params.length - 1];
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var params = state.params[0].split("/")
|
||||
var recipeName = params[0];
|
||||
var bagName = params[params.length - 1];
|
||||
|
||||
var recipes = sqlTiddlerDatabase.listRecipes()
|
||||
var bags = sqlTiddlerDatabase.listBags()
|
||||
var recipes = sqlTiddlerDatabase.listRecipes()
|
||||
var bags = sqlTiddlerDatabase.listBags()
|
||||
|
||||
var recipe = recipes.find((entry) => entry.recipe_name === recipeName && entry.bag_names.includes(bagName))
|
||||
var bag = bags.find((entry) => entry.bag_name === bagName);
|
||||
var recipe = recipes.find((entry) => entry.recipe_name === recipeName && entry.bag_names.includes(bagName))
|
||||
var bag = bags.find((entry) => entry.bag_name === bagName);
|
||||
|
||||
if (!recipe || !bag) {
|
||||
response.writeHead(500, "Unable to handle request", { "Content-Type": "text/html" });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
var recipeAclRecords = sqlTiddlerDatabase.getEntityAclRecords(recipe.recipe_name);
|
||||
var bagAclRecords = sqlTiddlerDatabase.getEntityAclRecords(bag.bag_name);
|
||||
var roles = state.server.sqlTiddlerDatabase.listRoles();
|
||||
var permissions = state.server.sqlTiddlerDatabase.listPermissions();
|
||||
|
||||
// This ensures that the user attempting to view the ACL management page has permission to do so
|
||||
if(!state.authenticatedUser || (recipeAclRecords.length > 0 && !sqlTiddlerDatabase.hasRecipePermission(state.authenticatedUser.user_id, recipeName, 'WRITE'))){
|
||||
response.writeHead(403, "Forbidden");
|
||||
response.end();
|
||||
return
|
||||
}
|
||||
|
||||
// Enhance ACL records with role and permission details
|
||||
recipeAclRecords = recipeAclRecords.map(record => {
|
||||
var role = roles.find(role => role.role_id === record.role_id);
|
||||
var permission = permissions.find(perm => perm.permission_id === record.permission_id);
|
||||
return ({
|
||||
...record,
|
||||
role,
|
||||
permission,
|
||||
role_name: role.role_name,
|
||||
role_description: role.description,
|
||||
permission_name: permission.permission_name,
|
||||
permission_description: permission.description
|
||||
})
|
||||
});
|
||||
|
||||
bagAclRecords = bagAclRecords.map(record => {
|
||||
var role = roles.find(role => role.role_id === record.role_id);
|
||||
var permission = permissions.find(perm => perm.permission_id === record.permission_id);
|
||||
return ({
|
||||
...record,
|
||||
role,
|
||||
permission,
|
||||
role_name: role.role_name,
|
||||
role_description: role.description,
|
||||
permission_name: permission.permission_name,
|
||||
permission_description: permission.description
|
||||
})
|
||||
});
|
||||
|
||||
response.writeHead(200, "OK", { "Content-Type": "text/html" });
|
||||
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-acl",
|
||||
"roles-list": JSON.stringify(roles),
|
||||
"permissions-list": JSON.stringify(permissions),
|
||||
"bag": JSON.stringify(bag),
|
||||
"recipe": JSON.stringify(recipe),
|
||||
"recipe-acl-records": JSON.stringify(recipeAclRecords),
|
||||
"bag-acl-records": JSON.stringify(bagAclRecords),
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
}
|
||||
});
|
||||
|
||||
response.write(html);
|
||||
if (!recipe || !bag) {
|
||||
response.writeHead(500, "Unable to handle request", { "Content-Type": "text/html" });
|
||||
response.end();
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
var recipeAclRecords = sqlTiddlerDatabase.getEntityAclRecords(recipe.recipe_name);
|
||||
var bagAclRecords = sqlTiddlerDatabase.getEntityAclRecords(bag.bag_name);
|
||||
var roles = state.server.sqlTiddlerDatabase.listRoles();
|
||||
var permissions = state.server.sqlTiddlerDatabase.listPermissions();
|
||||
|
||||
// This ensures that the user attempting to view the ACL management page has permission to do so
|
||||
if(!state.authenticatedUser || (recipeAclRecords.length > 0 && !sqlTiddlerDatabase.hasRecipePermission(state.authenticatedUser.user_id, recipeName, 'WRITE'))){
|
||||
response.writeHead(403, "Forbidden");
|
||||
response.end();
|
||||
return
|
||||
}
|
||||
|
||||
// Enhance ACL records with role and permission details
|
||||
recipeAclRecords = recipeAclRecords.map(record => {
|
||||
var role = roles.find(role => role.role_id === record.role_id);
|
||||
var permission = permissions.find(perm => perm.permission_id === record.permission_id);
|
||||
return ({
|
||||
...record,
|
||||
role,
|
||||
permission,
|
||||
role_name: role.role_name,
|
||||
role_description: role.description,
|
||||
permission_name: permission.permission_name,
|
||||
permission_description: permission.description
|
||||
})
|
||||
});
|
||||
|
||||
bagAclRecords = bagAclRecords.map(record => {
|
||||
var role = roles.find(role => role.role_id === record.role_id);
|
||||
var permission = permissions.find(perm => perm.permission_id === record.permission_id);
|
||||
return ({
|
||||
...record,
|
||||
role,
|
||||
permission,
|
||||
role_name: role.role_name,
|
||||
role_description: role.description,
|
||||
permission_name: permission.permission_name,
|
||||
permission_description: permission.description
|
||||
})
|
||||
});
|
||||
|
||||
response.writeHead(200, "OK", { "Content-Type": "text/html" });
|
||||
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-acl",
|
||||
"roles-list": JSON.stringify(roles),
|
||||
"permissions-list": JSON.stringify(permissions),
|
||||
"bag": JSON.stringify(bag),
|
||||
"recipe": JSON.stringify(recipe),
|
||||
"recipe-acl-records": JSON.stringify(recipeAclRecords),
|
||||
"bag-acl-records": JSON.stringify(bagAclRecords),
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : state.firstGuestUser ? "Annonymous User" : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
}
|
||||
});
|
||||
|
||||
response.write(html);
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -41,10 +41,10 @@ exports.handler = function(request,response,state) {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-index",
|
||||
"bag-list": JSON.stringify(allowedBags),
|
||||
"recipe-list": JSON.stringify(allowedRecipes),
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
}
|
||||
});
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : state.firstGuestUser ? "Annonymous User" : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no",
|
||||
"first-guest-user": state.firstGuestUser ? "yes" : "no"
|
||||
}});
|
||||
response.write(html);
|
||||
response.end();
|
||||
}
|
||||
|
@ -18,19 +18,19 @@ exports.path = /^\/admin\/users$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var userList = state.server.sqlTiddlerDatabase.listUsers();
|
||||
|
||||
|
||||
// Ensure userList is an array
|
||||
if (!Array.isArray(userList)) {
|
||||
userList = [];
|
||||
console.error("userList is not an array");
|
||||
}
|
||||
|
||||
if(!state.authenticatedUser.isAdmin) {
|
||||
if(!state.authenticatedUser.isAdmin && !state.firstGuestUser) {
|
||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||
response.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Convert dates to strings and ensure all necessary fields are present
|
||||
userList = userList.map(user => ({
|
||||
user_id: user.user_id || '',
|
||||
@ -49,12 +49,13 @@ exports.handler = function(request,response,state) {
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-users",
|
||||
"user-list": JSON.stringify(userList),
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : state.firstGuestUser ? "Annonymous User" : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no",
|
||||
"first-guest-user": state.firstGuestUser ? "yes" : "no"
|
||||
}
|
||||
});
|
||||
response.write(html);
|
||||
response.end();
|
||||
};
|
||||
|
||||
|
||||
}());
|
@ -31,7 +31,7 @@ exports.handler = function(request, response, state) {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles",
|
||||
"roles-list": JSON.stringify(roles),
|
||||
"edit-role": editRole ? JSON.stringify(editRole) : "",
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : state.firstGuestUser ? "Annonymous User" : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
}
|
||||
});
|
||||
|
@ -8,73 +8,92 @@ GET /admin/users/:user_id
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||
var userData = state.server.sqlTiddlerDatabase.getUser(user_id);
|
||||
|
||||
if(!userData) {
|
||||
response.writeHead(404, "Not Found", {"Content-Type": "text/html"});
|
||||
var errorHtml = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/error", {
|
||||
variables: {
|
||||
"error-message": "User not found"
|
||||
}
|
||||
});
|
||||
response.write(errorHtml);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user is trying to access their own profile or is an admin
|
||||
var hasPermission = ($tw.utils.parseInt(user_id, 10) === state.authenticatedUser.user_id) || state.authenticatedUser.isAdmin;
|
||||
if(!hasPermission) {
|
||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||
response.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
// Convert dates to strings and ensure all necessary fields are present
|
||||
var user = {
|
||||
user_id: userData.user_id || "",
|
||||
username: userData.username || "",
|
||||
email: userData.email || "",
|
||||
created_at: userData.created_at ? new Date(userData.created_at).toISOString() : "",
|
||||
last_login: userData.last_login ? new Date(userData.last_login).toISOString() : ""
|
||||
};
|
||||
|
||||
// Get all roles which the user has been assigned
|
||||
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(user_id);
|
||||
var allRoles = state.server.sqlTiddlerDatabase.listRoles();
|
||||
exports.method = "GET";
|
||||
|
||||
// sort allRoles by placing the user's role at the top of the list
|
||||
allRoles.sort(function(a, b){ (a.role_id === userRole.role_id ? -1 : 1) });
|
||||
|
||||
response.writeHead(200, "OK", {
|
||||
"Content-Type": "text/html"
|
||||
});
|
||||
|
||||
// Render the html
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
|
||||
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||
var userData = state.server.sqlTiddlerDatabase.getUser(user_id);
|
||||
|
||||
// Clean up any existing error/success messages if the user_id is different from the "$:/temp/mws/user-info/preview-user-id"
|
||||
var lastPreviewedUser = $tw.wiki.getTiddlerText("$:/temp/mws/user-info/" + user_id + "/preview-user-id");
|
||||
|
||||
if(user_id !== lastPreviewedUser) {
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/change-password/" + user_id + "/ error");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/change-password/" + user_id + "/success");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/login/error");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/delete-user/" + user_id + "/error");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/delete-user/" + user_id + "/success");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/update-profile/" + user_id + "/error");
|
||||
$tw.mws.store.adminWiki.deleteTiddler("$:/temp/mws/update-profile/" + user_id + "/success");
|
||||
}
|
||||
|
||||
if(!userData) {
|
||||
response.writeHead(404, "Not Found", {"Content-Type": "text/html"});
|
||||
var errorHtml = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/error", {
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user",
|
||||
"user": JSON.stringify(user),
|
||||
"user-initials": user.username.split(" ").map(name => name[0]).join(""),
|
||||
"user-role": JSON.stringify(userRole),
|
||||
"all-roles": JSON.stringify(allRoles),
|
||||
"is-current-user-profile": state.authenticatedUser && state.authenticatedUser.user_id === $tw.utils.parseInt(user_id, 10) ? "yes" : "no",
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||
"error-message": "User not found"
|
||||
}
|
||||
});
|
||||
response.write(html);
|
||||
response.write(errorHtml);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user is trying to access their own profile or is an admin
|
||||
var hasPermission = ($tw.utils.parseInt(user_id) === state.authenticatedUser.user_id) || state.authenticatedUser.isAdmin;
|
||||
if(!hasPermission) {
|
||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||
response.end("Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert dates to strings and ensure all necessary fields are present
|
||||
var user = {
|
||||
user_id: userData.user_id || "",
|
||||
username: userData.username || "",
|
||||
email: userData.email || "",
|
||||
created_at: userData.created_at ? new Date(userData.created_at).toISOString() : "",
|
||||
last_login: userData.last_login ? new Date(userData.last_login).toISOString() : ""
|
||||
};
|
||||
|
||||
}());
|
||||
|
||||
// Get all roles which the user has been assigned
|
||||
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(user_id);
|
||||
var allRoles = state.server.sqlTiddlerDatabase.listRoles();
|
||||
|
||||
// sort allRoles by placing the user's role at the top of the list
|
||||
allRoles.sort(function(a, b){ return (a.role_id === userRole?.role_id ? -1 : 1) });
|
||||
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/user-info/" + user_id + "/preview-user-id",
|
||||
text: user_id
|
||||
}));
|
||||
|
||||
response.writeHead(200, "OK", {
|
||||
"Content-Type": "text/html"
|
||||
});
|
||||
|
||||
// Render the html
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user",
|
||||
"user": JSON.stringify(user),
|
||||
"user-initials": user.username.split(" ").map(name => name[0]).join(""),
|
||||
"user-role": JSON.stringify(userRole),
|
||||
"all-roles": JSON.stringify(allRoles),
|
||||
"is-current-user-profile": state.authenticatedUser && state.authenticatedUser.user_id === $tw.utils.parseInt(user_id, 10) ? "yes" : "no",
|
||||
"username": state.authenticatedUser ? state.authenticatedUser.username : state.firstGuestUser ? "Annonymous User" : "Guest",
|
||||
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no",
|
||||
"user-id": user_id,
|
||||
}
|
||||
});
|
||||
response.write(html);
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -8,57 +8,56 @@ POST /admin/post-acl
|
||||
\*/
|
||||
(function () {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "POST";
|
||||
exports.method = "POST";
|
||||
|
||||
exports.path = /^\/admin\/post-acl\/?$/;
|
||||
exports.path = /^\/admin\/post-acl\/?$/;
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.csrfDisable = true;
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var entity_type = state.data.entity_type;
|
||||
var recipe_name = state.data.recipe_name;
|
||||
var bag_name = state.data.bag_name;
|
||||
var role_id = state.data.role_id;
|
||||
var permission_id = state.data.permission_id;
|
||||
var isRecipe = entity_type === "recipe"
|
||||
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var entity_type = state.data.entity_type;
|
||||
var recipe_name = state.data.recipe_name;
|
||||
var bag_name = state.data.bag_name;
|
||||
var role_id = state.data.role_id;
|
||||
var permission_id = state.data.permission_id;
|
||||
var isRecipe = entity_type === "recipe"
|
||||
var entityAclRecords = sqlTiddlerDatabase.getACLByName(entity_type, isRecipe ? recipe_name : bag_name, true);
|
||||
|
||||
var entityAclRecords = sqlTiddlerDatabase.getACLByName(entity_type, isRecipe ? recipe_name : bag_name, true);
|
||||
var aclExists = entityAclRecords.some((record) => (
|
||||
record.role_id == role_id && record.permission_id == permission_id
|
||||
))
|
||||
|
||||
var aclExists = entityAclRecords.some((record) => (
|
||||
record.role_id == role_id && record.permission_id == permission_id
|
||||
))
|
||||
|
||||
// This ensures that the user attempting to modify the ACL has permission to do so
|
||||
// if(!state.authenticatedUser || (entityAclRecords.length > 0 && !sqlTiddlerDatabase[isRecipe ? 'hasRecipePermission' : 'hasBagPermission'](state.authenticatedUser.user_id, isRecipe ? recipe_name : bag_name, 'WRITE'))){
|
||||
// response.writeHead(403, "Forbidden");
|
||||
// response.end();
|
||||
// return
|
||||
// }
|
||||
|
||||
if (aclExists) {
|
||||
// do nothing, return the user back to the form
|
||||
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
||||
response.end();
|
||||
return
|
||||
}
|
||||
|
||||
sqlTiddlerDatabase.createACL(
|
||||
isRecipe ? recipe_name : bag_name,
|
||||
entity_type,
|
||||
role_id,
|
||||
permission_id
|
||||
)
|
||||
// This ensures that the user attempting to modify the ACL has permission to do so
|
||||
// if(!state.authenticatedUser || (entityAclRecords.length > 0 && !sqlTiddlerDatabase[isRecipe ? 'hasRecipePermission' : 'hasBagPermission'](state.authenticatedUser.user_id, isRecipe ? recipe_name : bag_name, 'WRITE'))){
|
||||
// response.writeHead(403, "Forbidden");
|
||||
// response.end();
|
||||
// return
|
||||
// }
|
||||
|
||||
if (aclExists) {
|
||||
// do nothing, return the user back to the form
|
||||
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
||||
response.end();
|
||||
};
|
||||
return
|
||||
}
|
||||
|
||||
sqlTiddlerDatabase.createACL(
|
||||
isRecipe ? recipe_name : bag_name,
|
||||
entity_type,
|
||||
role_id,
|
||||
permission_id
|
||||
)
|
||||
|
||||
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -8,29 +8,29 @@ POST /admin/post-role
|
||||
\*/
|
||||
(function () {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "POST";
|
||||
exports.method = "POST";
|
||||
|
||||
exports.path = /^\/admin\/post-role\/?$/;
|
||||
exports.path = /^\/admin\/post-role\/?$/;
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
|
||||
exports.csrfDisable = true;
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var role_name = state.data.role_name;
|
||||
var role_description = state.data.role_description;
|
||||
exports.handler = function (request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var role_name = state.data.role_name;
|
||||
var role_description = state.data.role_description;
|
||||
|
||||
// Add your authentication check here if needed
|
||||
// Add your authentication check here if needed
|
||||
|
||||
sqlTiddlerDatabase.createRole(role_name, role_description);
|
||||
sqlTiddlerDatabase.createRole(role_name, role_description);
|
||||
|
||||
response.writeHead(302, { "Location": "/admin/roles" });
|
||||
response.end();
|
||||
};
|
||||
response.writeHead(302, { "Location": "/admin/roles" });
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -8,56 +8,79 @@ POST /admin/post-user
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
if($tw.node) {
|
||||
var crypto = require("crypto");
|
||||
}
|
||||
exports.method = "POST";
|
||||
|
||||
exports.method = "POST";
|
||||
exports.path = /^\/admin\/post-user\/?$/;
|
||||
|
||||
exports.path = /^\/admin\/post-user\/?$/;
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.csrfDisable = true;
|
||||
exports.handler = function(request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var username = state.data.username;
|
||||
var email = state.data.email;
|
||||
var password = state.data.password;
|
||||
var confirmPassword = state.data.confirmPassword;
|
||||
|
||||
exports.handler = function(request, response, state) {
|
||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||
var username = state.data.username;
|
||||
var email = state.data.email;
|
||||
var password = state.data.password;
|
||||
var confirmPassword = state.data.confirmPassword;
|
||||
if(!state.authenticatedUser && !state.firstGuestUser) {
|
||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||
response.end("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!state.authenticatedUser) {
|
||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||
response.end("Unauthorized");
|
||||
return;
|
||||
}
|
||||
if(!username || !email || !password || !confirmPassword) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "All fields are required"}));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!username || !email || !password || !confirmPassword) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "All fields are required"}));
|
||||
return;
|
||||
}
|
||||
if(password !== confirmPassword) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "Passwords do not match"}));
|
||||
return;
|
||||
}
|
||||
|
||||
if(password !== confirmPassword) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "Passwords do not match"}));
|
||||
return;
|
||||
}
|
||||
// Check if user already exists
|
||||
var existingUser = sqlTiddlerDatabase.getUser(username);
|
||||
if(existingUser) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "Username already exists"}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
var existingUser = sqlTiddlerDatabase.getUser(username);
|
||||
if(existingUser) {
|
||||
response.writeHead(400, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify({error: "Username already exists"}));
|
||||
return;
|
||||
}
|
||||
var hasUsers = sqlTiddlerDatabase.listUsers().length > 0;
|
||||
var hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
|
||||
|
||||
// Create new user
|
||||
var userId = sqlTiddlerDatabase.createUser(username, email, password);
|
||||
// Create new user
|
||||
var userId = sqlTiddlerDatabase.createUser(username, email, hashedPassword);
|
||||
|
||||
if(!hasUsers) {
|
||||
// If this is the first guest user, assign admin privileges
|
||||
sqlTiddlerDatabase.setUserAdmin(userId, true);
|
||||
|
||||
// Create a session for the new admin user
|
||||
var auth = require('$:/plugins/tiddlywiki/multiwikiserver/auth/authentication.js').Authenticator;
|
||||
var authenticator = auth(sqlTiddlerDatabase);
|
||||
var sessionId = authenticator.createSession(userId);
|
||||
|
||||
// Set the session cookie and redirect
|
||||
response.setHeader('Set-Cookie', `session=${sessionId}; HttpOnly; Path=/`);
|
||||
response.writeHead(302, {
|
||||
'Location': '/'
|
||||
});
|
||||
response.end();
|
||||
return;
|
||||
} else {
|
||||
response.writeHead(302, {"Location": "/admin/users/"+userId});
|
||||
response.end();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
@ -22,8 +22,12 @@ exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function (request,response,state) {
|
||||
if(!state.authenticatedUser) {
|
||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||
response.end("Unauthorized");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/login/error",
|
||||
text: "You must be logged in to update profiles"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/login" });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -32,29 +36,39 @@ exports.handler = function (request,response,state) {
|
||||
var email = state.data.email;
|
||||
var roleId = state.data.role;
|
||||
var currentUserId = state.authenticatedUser.user_id;
|
||||
|
||||
var hasPermission = ($tw.utils.parseInt(userId, 10) === currentUserId) || state.authenticatedUser.isAdmin;
|
||||
|
||||
var hasPermission = ($tw.utils.parseInt(userId) === currentUserId) || state.authenticatedUser.isAdmin;
|
||||
|
||||
if(!hasPermission) {
|
||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||
response.end("Forbidden");
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/update-profile/" + userId + "/error",
|
||||
text: "You don't have permission to update this profile"
|
||||
}));
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!state.authenticatedUser.isAdmin) {
|
||||
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(userId);
|
||||
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(userId);
|
||||
roleId = userRole.role_id;
|
||||
}
|
||||
|
||||
var result = state.server.sqlTiddlerDatabase.updateUser(userId, username, email, roleId);
|
||||
|
||||
if(result.success) {
|
||||
response.setHeader("Set-Cookie", "flashMessage="+result.message+"; Path=/; HttpOnly; Max-Age=5");
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/update-profile/" + userId + "/success",
|
||||
text: result.message
|
||||
}));
|
||||
} else {
|
||||
response.setHeader("Set-Cookie", "flashMessage="+result.message+"; Path=/; HttpOnly; Max-Age=5");
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/update-profile/" + userId + "/error",
|
||||
text: result.message
|
||||
}));
|
||||
}
|
||||
|
||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||
response.end();
|
||||
};
|
||||
|
||||
|
@ -42,7 +42,7 @@ exports.middleware = function (request, response, state, entityType, permissionN
|
||||
entityName = state.data ? (state.data[entityType+"_name"] || state.params[0]) : state.params[0];
|
||||
|
||||
// First, replace '%3A' with ':' to handle TiddlyWiki's system tiddlers
|
||||
var partiallyDecoded = entityName.replace(/%3A/g, ":");
|
||||
var partiallyDecoded = entityName?.replace(/%3A/g, ":");
|
||||
// Then use decodeURIComponent for the rest
|
||||
var decodedEntityName = decodeURIComponent(partiallyDecoded);
|
||||
var aclRecord = sqlTiddlerDatabase.getACLByName(entityType, decodedEntityName);
|
||||
|
@ -224,14 +224,6 @@ SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscon
|
||||
$accesscontrol: accesscontrol,
|
||||
$description: description
|
||||
});
|
||||
|
||||
const admin = this.getRoleByName("ADMIN");
|
||||
if(admin) {
|
||||
const readPermission = this.getPermissionByName("READ");
|
||||
const writePermission = this.getPermissionByName("WRITE");
|
||||
// this.createACL(bag_name, "bag", admin.role_id, readPermission.permission_id);
|
||||
// this.createACL(bag_name, "bag", admin.role_id, writePermission.permission_id);
|
||||
}
|
||||
return updateBags.lastInsertRowid;
|
||||
};
|
||||
|
||||
@ -296,15 +288,6 @@ SqlTiddlerDatabase.prototype.createRecipe = function(recipe_name,bag_names,descr
|
||||
$bag_names: JSON.stringify(bag_names)
|
||||
});
|
||||
|
||||
|
||||
// update the permissions on ACL records
|
||||
const admin = this.getRoleByName("ADMIN");
|
||||
if(admin) {
|
||||
const readPermission = this.getPermissionByName("READ");
|
||||
const writePermission = this.getPermissionByName("WRITE");
|
||||
// this.createACL(recipe_name, "recipe", admin.role_id, readPermission.permission_id);
|
||||
// this.createACL(recipe_name, "recipe", admin.role_id, writePermission.permission_id);
|
||||
}
|
||||
return updateRecipes.lastInsertRowid;
|
||||
};
|
||||
|
||||
@ -825,6 +808,18 @@ SqlTiddlerDatabase.prototype.getUserByUsername = function(username) {
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.listUsersByRoleId = function(roleId) {
|
||||
return this.engine.runStatementGetAll(`
|
||||
SELECT u.*
|
||||
FROM users u
|
||||
JOIN user_roles ur ON u.user_id = ur.user_id
|
||||
WHERE ur.role_id = $roleId
|
||||
ORDER BY u.username
|
||||
`, {
|
||||
$roleId: roleId
|
||||
});
|
||||
};
|
||||
|
||||
SqlTiddlerDatabase.prototype.updateUser = function (userId, username, email, roleId) {
|
||||
const existingUser = this.engine.runStatement(`
|
||||
SELECT user_id FROM users
|
||||
@ -1018,6 +1013,14 @@ SqlTiddlerDatabase.prototype.deleteUserSessions = function(userId) {
|
||||
});
|
||||
};
|
||||
|
||||
// Set the user as an admin
|
||||
SqlTiddlerDatabase.prototype.setUserAdmin = function(userId) {
|
||||
var admin = this.getRoleByName("ADMIN");
|
||||
if(admin) {
|
||||
this.addRoleToUser(userId, admin.role_id);
|
||||
}
|
||||
};
|
||||
|
||||
// Group CRUD operations
|
||||
SqlTiddlerDatabase.prototype.createGroup = function(groupName, description) {
|
||||
const result = this.engine.runStatement(`
|
||||
|
@ -27,6 +27,20 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
</$set>
|
||||
</$tiddler>
|
||||
|
||||
<$list filter="[<first-guest-user>match[yes]]">
|
||||
<div class="mws-security-warning">
|
||||
<div class="mws-security-warning-content">
|
||||
<div class="mws-security-warning-icon">⚠️</div>
|
||||
<div class="mws-security-warning-text">
|
||||
<strong>Warning:</strong> TiddlyWiki is currently running in anonymous access mode which allows anyone with access to the server to read and modify data.
|
||||
</div>
|
||||
<div class="mws-security-warning-action">
|
||||
<a href="/admin/users" class="mws-security-warning-button">Add Admin Account</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</$list>
|
||||
|
||||
<ul class="mws-vertical-list">
|
||||
<$list filter="[<recipe-list>jsonindexes[]] :sort[<currentTiddler>jsonget[recipe_name]]" variable="recipe-index">
|
||||
<li>
|
||||
@ -226,4 +240,48 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
||||
.mws-admin-dropdown:hover .mws-admin-dropdown-content {display: block;}
|
||||
|
||||
.mws-admin-dropdown:hover {background-color: #2980B9;}
|
||||
|
||||
.mws-security-warning {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mws-security-warning-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mws-security-warning-icon {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.mws-security-warning-text {
|
||||
flex-grow: 1;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.mws-security-warning-button {
|
||||
display: flex;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #856404;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.2s;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.mws-security-warning-button:hover {
|
||||
background-color: #6d5204;
|
||||
}
|
||||
</style>
|
||||
|
@ -18,34 +18,36 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
||||
</$tiddler>
|
||||
|
||||
<div class="users-container">
|
||||
<div class="users-list">
|
||||
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
||||
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
||||
<$set name="user-id" value={{{ [<currentUser>jsonget[user_id]] }}}>
|
||||
<a href={{{ [[/admin/users/]addsuffix<user-id>] }}} class="user-item">
|
||||
<div class="user-info">
|
||||
<span class="user-name">
|
||||
<$text text={{{ [<currentUser>jsonget[username]] }}}/>
|
||||
</span>
|
||||
<span class="user-email">
|
||||
<$text text={{{ [<currentUser>jsonget[email]] }}}/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<span class="user-created">
|
||||
Created: <$text text={{{ [<currentUser>jsonget[created_at]] }}}/>
|
||||
</span>
|
||||
<span class="user-last-login">
|
||||
Last Login: <$text text={{{ [<currentUser>jsonget[last_login]] }}}/>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</$set>
|
||||
</$let>
|
||||
</$list>
|
||||
</div>
|
||||
<$list filter="[<user-list>jsonindexes[]count[]!match[0]]">
|
||||
<div class="users-list">
|
||||
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
||||
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
||||
<$set name="user-id" value={{{ [<currentUser>jsonget[user_id]] }}}>
|
||||
<a href={{{ [[/admin/users/]addsuffix<user-id>] }}} class="user-item">
|
||||
<div class="user-info">
|
||||
<span class="user-name">
|
||||
<$text text={{{ [<currentUser>jsonget[username]] }}}/>
|
||||
</span>
|
||||
<span class="user-email">
|
||||
<$text text={{{ [<currentUser>jsonget[email]] }}}/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<span class="user-created">
|
||||
Created: <$text text={{{ [<currentUser>jsonget[created_at]] }}}/>
|
||||
</span>
|
||||
<span class="user-last-login">
|
||||
Last Login: <$text text={{{ [<currentUser>jsonget[last_login]] }}}/>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</$set>
|
||||
</$let>
|
||||
</$list>
|
||||
</div>
|
||||
</$list>
|
||||
|
||||
<$list filter="[<user-is-admin>match[yes]]">
|
||||
<$list filter="[<user-is-admin>match[yes]][<first-guest-user>match[yes]]">
|
||||
<div class="add-user-card">
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-form" mode="inline"/>
|
||||
</div>
|
||||
@ -73,6 +75,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: 2rem;
|
||||
margin: auto;
|
||||
}
|
||||
.user-item {
|
||||
display: block;
|
||||
@ -121,4 +124,9 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
||||
.tc-btn-big-green:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.no-users-message {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
@ -28,6 +28,17 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
||||
</div>
|
||||
<% endif %>
|
||||
<button type="submit" class="update-profile-btn">Update Profile</button>
|
||||
<$list filter="[[$:/temp/mws/update-profile/]addsuffix<user-id>addsuffix[/error]!is[missing]]" variable="errorTiddler">
|
||||
<div class="tc-error-message">
|
||||
<$text text={{{[<errorTiddler>get[text]]}}}/>
|
||||
</div>
|
||||
</$list>
|
||||
|
||||
<$list filter="[[$:/temp/mws/update-profile/]addsuffix<user-id>addsuffix[/success]!is[missing]]" variable="successTiddler">
|
||||
<div class="tc-success-message">
|
||||
<$text text={{{[<successTiddler>get[text]]}}}/>
|
||||
</div>
|
||||
</$list>
|
||||
</form>
|
||||
</$set>
|
||||
<% if [<user-is-admin>match[yes]] && [<is-current-user-profile>match[no]] %>
|
||||
@ -35,6 +46,11 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
||||
<form class="user-profile-form" action="/delete-user-account" method="POST" onsubmit="return confirm('Are you sure you want to delete this user account? This action cannot be undone.');">
|
||||
<input type="hidden" name="userId" value={{{ [<user>jsonget[user_id]] }}}>
|
||||
<button type="submit" class="delete-account-btn">Delete User Account</button>
|
||||
<$list filter="[[$:/temp/mws/delete-user/]addsuffix<user-id>addsuffix[/error]!is[missing]]" variable="deleteErrorTiddler">
|
||||
<div class="tc-error-message">
|
||||
<$text text={{{[<deleteErrorTiddler>get[text]]}}}/>
|
||||
</div>
|
||||
</$list>
|
||||
</form>
|
||||
<% endif %>
|
||||
<% if [<is-current-user-profile>match[yes]] %>
|
||||
@ -51,6 +67,16 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
||||
<input type="password" id="confirm-password" name="confirmPassword" required />
|
||||
</div>
|
||||
<button type="submit" class="update-password-btn">Change Password</button>
|
||||
<$list filter="[[$:/temp/mws/change-password/]addsuffix<user-id>addsuffix[/error]!is[missing]]" variable="errorTiddler">
|
||||
<div class="tc-error-message">
|
||||
<$text text={{{[<errorTiddler>get[text]]}}}/>
|
||||
</div>
|
||||
</$list>
|
||||
<$list filter="[[$:/temp/mws/change-password/]addsuffix<user-id>addsuffix[/success]!is[missing]]" variable="successTiddler">
|
||||
<div class="tc-success-message">
|
||||
<$text text={{{[<successTiddler>get[text]]}}}/>
|
||||
</div>
|
||||
</$list>
|
||||
</form>
|
||||
<% endif %>
|
||||
</div>
|
||||
@ -58,6 +84,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
||||
<style>
|
||||
.user-profile-management {
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-profile-management h2 {
|
||||
@ -133,4 +160,14 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
||||
.delete-account-btn:hover {
|
||||
background: #c0392b;
|
||||
}
|
||||
|
||||
.tc-error-message {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tc-success-message {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
@ -14,7 +14,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
||||
<h1 class="user-profile-name"><$text text={{{ [<user>jsonget[username]] }}}/></h1>
|
||||
<p class="user-profile-email"><$text text={{{ [<user>jsonget[email]] }}}/></p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="user-profile-details">
|
||||
<div class="user-profile-item">
|
||||
<span class="user-profile-label">User ID:</span>
|
||||
@ -39,7 +39,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<% if [<user-is-admin>match[yes]] %>
|
||||
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account">
|
||||
<$transclude/>
|
||||
@ -70,7 +70,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
||||
}
|
||||
|
||||
.user-profile-container {
|
||||
flex: 1;
|
||||
flex: 4;
|
||||
margin: 2rem auto;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
|
@ -12,7 +12,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/mws-header
|
||||
<a href="/admin/roles">Manage Roles</a>
|
||||
</div>
|
||||
</div>
|
||||
<% elseif [<username>!match[Guest]] %>
|
||||
<% elseif [<username>!match[Guest]]+[<first-guest-user>match[no]] %>
|
||||
<a href={{{ [<user>jsonget[user_id]addprefix[/admin/users/]] }}}>
|
||||
<button class="mws-profile-btn">Profile</button>
|
||||
</a>
|
||||
|
Loading…
Reference in New Issue
Block a user