mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-08 08:20:27 +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"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"build": {
|
"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": [
|
"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/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",
|
"--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];
|
var description = this.params[1];
|
||||||
|
|
||||||
$tw.mws.store.sqlTiddlerDatabase.createPermission(permission_name, description);
|
$tw.mws.store.sqlTiddlerDatabase.createPermission(permission_name, description);
|
||||||
|
|
||||||
console.log(permission_name+" Permission Created Successfully!")
|
|
||||||
self.callback();
|
self.callback();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -38,8 +38,6 @@ Command.prototype.execute = function() {
|
|||||||
var description = this.params[1];
|
var description = this.params[1];
|
||||||
|
|
||||||
$tw.mws.store.sqlTiddlerDatabase.createRole(role_name, description);
|
$tw.mws.store.sqlTiddlerDatabase.createRole(role_name, description);
|
||||||
|
|
||||||
console.log(role_name+" Role Created Successfully!")
|
|
||||||
self.callback(null, "Role Created Successfully!");
|
self.callback(null, "Role Created Successfully!");
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -43,11 +43,9 @@ Command.prototype.execute = function() {
|
|||||||
|
|
||||||
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);
|
var user = $tw.mws.store.sqlTiddlerDatabase.getUserByUsername(username);
|
||||||
|
|
||||||
if(user) {
|
if(!user) {
|
||||||
self.callback("WARNING: An account with the username (" + username + ") already exists");
|
|
||||||
} else {
|
|
||||||
$tw.mws.store.sqlTiddlerDatabase.createUser(username, email, hashedPassword);
|
$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();
|
self.callback();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -51,8 +51,6 @@ Command.prototype.execute = function() {
|
|||||||
|
|
||||||
|
|
||||||
$tw.mws.store.sqlTiddlerDatabase.addPermissionToRole(role.role_id, permission.permission_id);
|
$tw.mws.store.sqlTiddlerDatabase.addPermissionToRole(role.role_id, permission.permission_id);
|
||||||
|
|
||||||
console.log(permission_name+" permission assigned to "+role_name+" role successfully!")
|
|
||||||
self.callback();
|
self.callback();
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -119,20 +119,6 @@ TestRunner.prototype.runTest = function(testSpec,callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const testSpecs = [
|
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",
|
description: "Check index page",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@ -441,6 +441,8 @@ Server.prototype.requestHandler = function(request,response,options) {
|
|||||||
// Check whether anonymous access is granted
|
// Check whether anonymous access is granted
|
||||||
state.allowAnon = false; //this.isAuthorized(state.authorizationType,null);
|
state.allowAnon = false; //this.isAuthorized(state.authorizationType,null);
|
||||||
|
|
||||||
|
state.firstGuestUser = this.sqlTiddlerDatabase.listUsers().length === 0 && !state.authenticatedUser;
|
||||||
|
|
||||||
// Authorize with the authenticated username
|
// Authorize with the authenticated username
|
||||||
if(!this.isAuthorized(state.authorizationType,state.authenticatedUsername) && !response.headersSent) {
|
if(!this.isAuthorized(state.authorizationType,state.authenticatedUsername) && !response.headersSent) {
|
||||||
response.writeHead(403,"'" + state.authenticatedUsername + "' is not authorized to access '" + this.servername + "'");
|
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.csrfDisable = true;
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
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) {
|
if(!state.authenticatedUser) {
|
||||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("Unauthorized");
|
title: "$:/temp/mws/login/error",
|
||||||
|
text: "You must be logged in to change passwords"
|
||||||
|
}));
|
||||||
|
response.writeHead(302, { "Location": "/login" });
|
||||||
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var auth = authenticator(state.server.sqlTiddlerDatabase);
|
|
||||||
|
|
||||||
var userId = state.data.userId;
|
var auth = authenticator(state.server.sqlTiddlerDatabase);
|
||||||
var newPassword = state.data.newPassword;
|
var newPassword = state.data.newPassword;
|
||||||
var confirmPassword = state.data.confirmPassword;
|
var confirmPassword = state.data.confirmPassword;
|
||||||
var currentUserId = state.authenticatedUser.user_id;
|
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) {
|
if(!hasPermission) {
|
||||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("Forbidden");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(newPassword !== confirmPassword) {
|
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.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||||
response.end();
|
response.end();
|
||||||
return;
|
return;
|
||||||
@ -52,7 +68,10 @@ exports.handler = function (request, response, state) {
|
|||||||
var userData = state.server.sqlTiddlerDatabase.getUser(userId);
|
var userData = state.server.sqlTiddlerDatabase.getUser(userId);
|
||||||
|
|
||||||
if(!userData) {
|
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.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||||
response.end();
|
response.end();
|
||||||
return;
|
return;
|
||||||
@ -61,7 +80,10 @@ exports.handler = function (request, response, state) {
|
|||||||
var newHash = auth.hashPassword(newPassword);
|
var newHash = auth.hashPassword(newPassword);
|
||||||
var result = state.server.sqlTiddlerDatabase.updateUserPassword(userId, newHash);
|
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.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
@ -8,51 +8,86 @@ POST /delete-user-account
|
|||||||
\*/
|
\*/
|
||||||
(function () {
|
(function () {
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"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) {
|
exports.handler = function (request, response, state) {
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||||
var userId = state.data.userId;
|
var userId = state.data.userId;
|
||||||
|
|
||||||
// Check if user is admin
|
// Check if user is admin
|
||||||
if(!state.authenticatedUser || !state.authenticatedUser.isAdmin) {
|
if(!state.authenticatedUser || !state.authenticatedUser.isAdmin) {
|
||||||
response.writeHead(403, "Forbidden");
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end();
|
title: "$:/temp/mws/delete-user/error",
|
||||||
return;
|
text: "You must be an administrator to delete user accounts"
|
||||||
}
|
}));
|
||||||
|
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||||
// 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" });
|
|
||||||
response.end();
|
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 () {
|
(function () {
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
exports.method = "GET";
|
exports.method = "GET";
|
||||||
|
|
||||||
exports.path = /^\/admin\/acl\/(.+)$/;
|
exports.path = /^\/admin\/acl\/(.+)$/;
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
exports.handler = function (request, response, state) {
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||||
var params = state.params[0].split("/")
|
var params = state.params[0].split("/")
|
||||||
var recipeName = params[0];
|
var recipeName = params[0];
|
||||||
var bagName = params[params.length - 1];
|
var bagName = params[params.length - 1];
|
||||||
|
|
||||||
var recipes = sqlTiddlerDatabase.listRecipes()
|
var recipes = sqlTiddlerDatabase.listRecipes()
|
||||||
var bags = sqlTiddlerDatabase.listBags()
|
var bags = sqlTiddlerDatabase.listBags()
|
||||||
|
|
||||||
var recipe = recipes.find((entry) => entry.recipe_name === recipeName && entry.bag_names.includes(bagName))
|
var recipe = recipes.find((entry) => entry.recipe_name === recipeName && entry.bag_names.includes(bagName))
|
||||||
var bag = bags.find((entry) => entry.bag_name === bagName);
|
var bag = bags.find((entry) => entry.bag_name === bagName);
|
||||||
|
|
||||||
if (!recipe || !bag) {
|
if (!recipe || !bag) {
|
||||||
response.writeHead(500, "Unable to handle request", { "Content-Type": "text/html" });
|
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);
|
|
||||||
response.end();
|
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",
|
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-index",
|
||||||
"bag-list": JSON.stringify(allowedBags),
|
"bag-list": JSON.stringify(allowedBags),
|
||||||
"recipe-list": JSON.stringify(allowedRecipes),
|
"recipe-list": JSON.stringify(allowedRecipes),
|
||||||
"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"
|
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no",
|
||||||
}
|
"first-guest-user": state.firstGuestUser ? "yes" : "no"
|
||||||
});
|
}});
|
||||||
response.write(html);
|
response.write(html);
|
||||||
response.end();
|
response.end();
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,19 @@ exports.path = /^\/admin\/users$/;
|
|||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
var userList = state.server.sqlTiddlerDatabase.listUsers();
|
var userList = state.server.sqlTiddlerDatabase.listUsers();
|
||||||
|
|
||||||
// Ensure userList is an array
|
// Ensure userList is an array
|
||||||
if (!Array.isArray(userList)) {
|
if (!Array.isArray(userList)) {
|
||||||
userList = [];
|
userList = [];
|
||||||
console.error("userList is not an array");
|
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.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||||
response.end("Forbidden");
|
response.end("Forbidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert dates to strings and ensure all necessary fields are present
|
// Convert dates to strings and ensure all necessary fields are present
|
||||||
userList = userList.map(user => ({
|
userList = userList.map(user => ({
|
||||||
user_id: user.user_id || '',
|
user_id: user.user_id || '',
|
||||||
@ -49,12 +49,13 @@ exports.handler = function(request,response,state) {
|
|||||||
variables: {
|
variables: {
|
||||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-users",
|
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-users",
|
||||||
"user-list": JSON.stringify(userList),
|
"user-list": JSON.stringify(userList),
|
||||||
"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"
|
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no",
|
||||||
|
"first-guest-user": state.firstGuestUser ? "yes" : "no"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
response.write(html);
|
response.write(html);
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -31,7 +31,7 @@ exports.handler = function(request, response, state) {
|
|||||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles",
|
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles",
|
||||||
"roles-list": JSON.stringify(roles),
|
"roles-list": JSON.stringify(roles),
|
||||||
"edit-role": editRole ? JSON.stringify(editRole) : "",
|
"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"
|
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -8,73 +8,92 @@ GET /admin/users/:user_id
|
|||||||
\*/
|
\*/
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert dates to strings and ensure all necessary fields are present
|
exports.method = "GET";
|
||||||
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
|
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
|
||||||
allRoles.sort(function(a, b){ (a.role_id === userRole.role_id ? -1 : 1) });
|
|
||||||
|
exports.handler = function(request,response,state) {
|
||||||
response.writeHead(200, "OK", {
|
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||||
"Content-Type": "text/html"
|
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"
|
||||||
// Render the html
|
var lastPreviewedUser = $tw.wiki.getTiddlerText("$:/temp/mws/user-info/" + user_id + "/preview-user-id");
|
||||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
|
|
||||||
|
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: {
|
variables: {
|
||||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user",
|
"error-message": "User not found"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
response.write(html);
|
response.write(errorHtml);
|
||||||
response.end();
|
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 () {
|
(function () {
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"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 entityAclRecords = sqlTiddlerDatabase.getACLByName(entity_type, isRecipe ? recipe_name : bag_name, true);
|
||||||
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 aclExists = entityAclRecords.some((record) => (
|
||||||
|
record.role_id == role_id && record.permission_id == permission_id
|
||||||
|
))
|
||||||
|
|
||||||
var aclExists = entityAclRecords.some((record) => (
|
// This ensures that the user attempting to modify the ACL has permission to do so
|
||||||
record.role_id == role_id && record.permission_id == permission_id
|
// 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();
|
||||||
// This ensures that the user attempting to modify the ACL has permission to do so
|
// return
|
||||||
// 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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if (aclExists) {
|
||||||
|
// do nothing, return the user back to the form
|
||||||
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
||||||
response.end();
|
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 () {
|
(function () {
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"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) {
|
exports.handler = function (request, response, state) {
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||||
var role_name = state.data.role_name;
|
var role_name = state.data.role_name;
|
||||||
var role_description = state.data.role_description;
|
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.writeHead(302, { "Location": "/admin/roles" });
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -8,56 +8,79 @@ POST /admin/post-user
|
|||||||
\*/
|
\*/
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
/*jslint node: true, browser: true */
|
||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"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) {
|
if(!state.authenticatedUser && !state.firstGuestUser) {
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||||
var username = state.data.username;
|
response.end("Unauthorized");
|
||||||
var email = state.data.email;
|
return;
|
||||||
var password = state.data.password;
|
}
|
||||||
var confirmPassword = state.data.confirmPassword;
|
|
||||||
|
|
||||||
if(!state.authenticatedUser) {
|
if(!username || !email || !password || !confirmPassword) {
|
||||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
response.writeHead(400, {"Content-Type": "application/json"});
|
||||||
response.end("Unauthorized");
|
response.end(JSON.stringify({error: "All fields are required"}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!username || !email || !password || !confirmPassword) {
|
if(password !== confirmPassword) {
|
||||||
response.writeHead(400, {"Content-Type": "application/json"});
|
response.writeHead(400, {"Content-Type": "application/json"});
|
||||||
response.end(JSON.stringify({error: "All fields are required"}));
|
response.end(JSON.stringify({error: "Passwords do not match"}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(password !== confirmPassword) {
|
// Check if user already exists
|
||||||
response.writeHead(400, {"Content-Type": "application/json"});
|
var existingUser = sqlTiddlerDatabase.getUser(username);
|
||||||
response.end(JSON.stringify({error: "Passwords do not match"}));
|
if(existingUser) {
|
||||||
return;
|
response.writeHead(400, {"Content-Type": "application/json"});
|
||||||
}
|
response.end(JSON.stringify({error: "Username already exists"}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user already exists
|
var hasUsers = sqlTiddlerDatabase.listUsers().length > 0;
|
||||||
var existingUser = sqlTiddlerDatabase.getUser(username);
|
var hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
|
||||||
if(existingUser) {
|
|
||||||
response.writeHead(400, {"Content-Type": "application/json"});
|
|
||||||
response.end(JSON.stringify({error: "Username already exists"}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new user
|
// Create new user
|
||||||
var userId = sqlTiddlerDatabase.createUser(username, email, password);
|
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.writeHead(302, {"Location": "/admin/users/"+userId});
|
||||||
response.end();
|
response.end();
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -22,8 +22,12 @@ exports.csrfDisable = true;
|
|||||||
|
|
||||||
exports.handler = function (request,response,state) {
|
exports.handler = function (request,response,state) {
|
||||||
if(!state.authenticatedUser) {
|
if(!state.authenticatedUser) {
|
||||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("Unauthorized");
|
title: "$:/temp/mws/login/error",
|
||||||
|
text: "You must be logged in to update profiles"
|
||||||
|
}));
|
||||||
|
response.writeHead(302, { "Location": "/login" });
|
||||||
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,29 +36,39 @@ exports.handler = function (request,response,state) {
|
|||||||
var email = state.data.email;
|
var email = state.data.email;
|
||||||
var roleId = state.data.role;
|
var roleId = state.data.role;
|
||||||
var currentUserId = state.authenticatedUser.user_id;
|
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) {
|
if(!hasPermission) {
|
||||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("Forbidden");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!state.authenticatedUser.isAdmin) {
|
if(!state.authenticatedUser.isAdmin) {
|
||||||
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(userId);
|
var userRole = state.server.sqlTiddlerDatabase.getUserRoles(userId);
|
||||||
roleId = userRole.role_id;
|
roleId = userRole.role_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = state.server.sqlTiddlerDatabase.updateUser(userId, username, email, roleId);
|
var result = state.server.sqlTiddlerDatabase.updateUser(userId, username, email, roleId);
|
||||||
|
|
||||||
if(result.success) {
|
if(result.success) {
|
||||||
response.setHeader("Set-Cookie", "flashMessage="+result.message+"; Path=/; HttpOnly; Max-Age=5");
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
title: "$:/temp/mws/update-profile/" + userId + "/success",
|
||||||
|
text: result.message
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
response.setHeader("Set-Cookie", "flashMessage="+result.message+"; Path=/; HttpOnly; Max-Age=5");
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
title: "$:/temp/mws/update-profile/" + userId + "/error",
|
||||||
|
text: result.message
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response.writeHead(302, { "Location": "/admin/users/" + userId });
|
||||||
response.end();
|
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];
|
entityName = state.data ? (state.data[entityType+"_name"] || state.params[0]) : state.params[0];
|
||||||
|
|
||||||
// First, replace '%3A' with ':' to handle TiddlyWiki's system tiddlers
|
// 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
|
// Then use decodeURIComponent for the rest
|
||||||
var decodedEntityName = decodeURIComponent(partiallyDecoded);
|
var decodedEntityName = decodeURIComponent(partiallyDecoded);
|
||||||
var aclRecord = sqlTiddlerDatabase.getACLByName(entityType, decodedEntityName);
|
var aclRecord = sqlTiddlerDatabase.getACLByName(entityType, decodedEntityName);
|
||||||
|
@ -224,14 +224,6 @@ SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscon
|
|||||||
$accesscontrol: accesscontrol,
|
$accesscontrol: accesscontrol,
|
||||||
$description: description
|
$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;
|
return updateBags.lastInsertRowid;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -296,15 +288,6 @@ SqlTiddlerDatabase.prototype.createRecipe = function(recipe_name,bag_names,descr
|
|||||||
$bag_names: JSON.stringify(bag_names)
|
$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;
|
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) {
|
SqlTiddlerDatabase.prototype.updateUser = function (userId, username, email, roleId) {
|
||||||
const existingUser = this.engine.runStatement(`
|
const existingUser = this.engine.runStatement(`
|
||||||
SELECT user_id FROM users
|
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
|
// Group CRUD operations
|
||||||
SqlTiddlerDatabase.prototype.createGroup = function(groupName, description) {
|
SqlTiddlerDatabase.prototype.createGroup = function(groupName, description) {
|
||||||
const result = this.engine.runStatement(`
|
const result = this.engine.runStatement(`
|
||||||
|
@ -27,6 +27,20 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
|
|||||||
</$set>
|
</$set>
|
||||||
</$tiddler>
|
</$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">
|
<ul class="mws-vertical-list">
|
||||||
<$list filter="[<recipe-list>jsonindexes[]] :sort[<currentTiddler>jsonget[recipe_name]]" variable="recipe-index">
|
<$list filter="[<recipe-list>jsonindexes[]] :sort[<currentTiddler>jsonget[recipe_name]]" variable="recipe-index">
|
||||||
<li>
|
<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 .mws-admin-dropdown-content {display: block;}
|
||||||
|
|
||||||
.mws-admin-dropdown:hover {background-color: #2980B9;}
|
.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>
|
</style>
|
||||||
|
@ -18,34 +18,36 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
|||||||
</$tiddler>
|
</$tiddler>
|
||||||
|
|
||||||
<div class="users-container">
|
<div class="users-container">
|
||||||
<div class="users-list">
|
<$list filter="[<user-list>jsonindexes[]count[]!match[0]]">
|
||||||
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
<div class="users-list">
|
||||||
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
||||||
<$set name="user-id" value={{{ [<currentUser>jsonget[user_id]] }}}>
|
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
||||||
<a href={{{ [[/admin/users/]addsuffix<user-id>] }}} class="user-item">
|
<$set name="user-id" value={{{ [<currentUser>jsonget[user_id]] }}}>
|
||||||
<div class="user-info">
|
<a href={{{ [[/admin/users/]addsuffix<user-id>] }}} class="user-item">
|
||||||
<span class="user-name">
|
<div class="user-info">
|
||||||
<$text text={{{ [<currentUser>jsonget[username]] }}}/>
|
<span class="user-name">
|
||||||
</span>
|
<$text text={{{ [<currentUser>jsonget[username]] }}}/>
|
||||||
<span class="user-email">
|
</span>
|
||||||
<$text text={{{ [<currentUser>jsonget[email]] }}}/>
|
<span class="user-email">
|
||||||
</span>
|
<$text text={{{ [<currentUser>jsonget[email]] }}}/>
|
||||||
</div>
|
</span>
|
||||||
<div class="user-details">
|
</div>
|
||||||
<span class="user-created">
|
<div class="user-details">
|
||||||
Created: <$text text={{{ [<currentUser>jsonget[created_at]] }}}/>
|
<span class="user-created">
|
||||||
</span>
|
Created: <$text text={{{ [<currentUser>jsonget[created_at]] }}}/>
|
||||||
<span class="user-last-login">
|
</span>
|
||||||
Last Login: <$text text={{{ [<currentUser>jsonget[last_login]] }}}/>
|
<span class="user-last-login">
|
||||||
</span>
|
Last Login: <$text text={{{ [<currentUser>jsonget[last_login]] }}}/>
|
||||||
</div>
|
</span>
|
||||||
</a>
|
</div>
|
||||||
</$set>
|
</a>
|
||||||
</$let>
|
</$set>
|
||||||
</$list>
|
</$let>
|
||||||
</div>
|
</$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">
|
<div class="add-user-card">
|
||||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-form" mode="inline"/>
|
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-form" mode="inline"/>
|
||||||
</div>
|
</div>
|
||||||
@ -73,6 +75,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
.user-item {
|
.user-item {
|
||||||
display: block;
|
display: block;
|
||||||
@ -121,4 +124,9 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
|||||||
.tc-btn-big-green:hover {
|
.tc-btn-big-green:hover {
|
||||||
background-color: #45a049;
|
background-color: #45a049;
|
||||||
}
|
}
|
||||||
|
.no-users-message {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -28,6 +28,17 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
|||||||
</div>
|
</div>
|
||||||
<% endif %>
|
<% endif %>
|
||||||
<button type="submit" class="update-profile-btn">Update Profile</button>
|
<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>
|
</form>
|
||||||
</$set>
|
</$set>
|
||||||
<% if [<user-is-admin>match[yes]] && [<is-current-user-profile>match[no]] %>
|
<% 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.');">
|
<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]] }}}>
|
<input type="hidden" name="userId" value={{{ [<user>jsonget[user_id]] }}}>
|
||||||
<button type="submit" class="delete-account-btn">Delete User Account</button>
|
<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>
|
</form>
|
||||||
<% endif %>
|
<% endif %>
|
||||||
<% if [<is-current-user-profile>match[yes]] %>
|
<% 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 />
|
<input type="password" id="confirm-password" name="confirmPassword" required />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="update-password-btn">Change Password</button>
|
<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>
|
</form>
|
||||||
<% endif %>
|
<% endif %>
|
||||||
</div>
|
</div>
|
||||||
@ -58,6 +84,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
|||||||
<style>
|
<style>
|
||||||
.user-profile-management {
|
.user-profile-management {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-management h2 {
|
.user-profile-management h2 {
|
||||||
@ -133,4 +160,14 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account
|
|||||||
.delete-account-btn:hover {
|
.delete-account-btn:hover {
|
||||||
background: #c0392b;
|
background: #c0392b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tc-error-message {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tc-success-message {
|
||||||
|
color: green;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -14,7 +14,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
<h1 class="user-profile-name"><$text text={{{ [<user>jsonget[username]] }}}/></h1>
|
<h1 class="user-profile-name"><$text text={{{ [<user>jsonget[username]] }}}/></h1>
|
||||||
<p class="user-profile-email"><$text text={{{ [<user>jsonget[email]] }}}/></p>
|
<p class="user-profile-email"><$text text={{{ [<user>jsonget[email]] }}}/></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="user-profile-details">
|
<div class="user-profile-details">
|
||||||
<div class="user-profile-item">
|
<div class="user-profile-item">
|
||||||
<span class="user-profile-label">User ID:</span>
|
<span class="user-profile-label">User ID:</span>
|
||||||
@ -39,7 +39,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% if [<user-is-admin>match[yes]] %>
|
<% if [<user-is-admin>match[yes]] %>
|
||||||
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account">
|
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user-account">
|
||||||
<$transclude/>
|
<$transclude/>
|
||||||
@ -70,7 +70,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-container {
|
.user-profile-container {
|
||||||
flex: 1;
|
flex: 4;
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
@ -12,7 +12,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/mws-header
|
|||||||
<a href="/admin/roles">Manage Roles</a>
|
<a href="/admin/roles">Manage Roles</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% elseif [<username>!match[Guest]] %>
|
<% elseif [<username>!match[Guest]]+[<first-guest-user>match[no]] %>
|
||||||
<a href={{{ [<user>jsonget[user_id]addprefix[/admin/users/]] }}}>
|
<a href={{{ [<user>jsonget[user_id]addprefix[/admin/users/]] }}}>
|
||||||
<button class="mws-profile-btn">Profile</button>
|
<button class="mws-profile-btn">Profile</button>
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user