mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-01-23 23:46:52 +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,41 +8,76 @@ 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({
|
||||||
|
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();
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent admin from deleting their own account
|
// Prevent admin from deleting their own account
|
||||||
if(state.authenticatedUser.user_id === userId) {
|
if(state.authenticatedUser.user_id === userId) {
|
||||||
response.writeHead(400, "Bad Request");
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("Cannot delete your own account");
|
title: "$:/temp/mws/delete-user/error",
|
||||||
|
text: "Cannot delete your own account"
|
||||||
|
}));
|
||||||
|
response.writeHead(302, { "Location": '/admin/users/'+userId });
|
||||||
|
response.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user exists
|
// Check if the user exists
|
||||||
var user = sqlTiddlerDatabase.getUser(userId);
|
var user = sqlTiddlerDatabase.getUser(userId);
|
||||||
if(!user) {
|
if(!user) {
|
||||||
response.writeHead(404, "Not Found");
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
response.end("User not found");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +88,6 @@ POST /delete-user-account
|
|||||||
// Redirect back to the users management page
|
// Redirect back to the users management page
|
||||||
response.writeHead(302, { "Location": "/admin/users" });
|
response.writeHead(302, { "Location": "/admin/users" });
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -7,15 +7,15 @@ 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];
|
||||||
@ -85,13 +85,13 @@ GET /admin/acl
|
|||||||
"recipe": JSON.stringify(recipe),
|
"recipe": JSON.stringify(recipe),
|
||||||
"recipe-acl-records": JSON.stringify(recipeAclRecords),
|
"recipe-acl-records": JSON.stringify(recipeAclRecords),
|
||||||
"bag-acl-records": JSON.stringify(bagAclRecords),
|
"bag-acl-records": JSON.stringify(bagAclRecords),
|
||||||
"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"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
response.write(html);
|
response.write(html);
|
||||||
response.end();
|
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();
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ exports.handler = function(request,response,state) {
|
|||||||
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;
|
||||||
@ -49,8 +49,9 @@ 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);
|
||||||
|
@ -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,18 +8,31 @@ 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.method = "GET";
|
||||||
|
|
||||||
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
|
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
|
||||||
var userData = state.server.sqlTiddlerDatabase.getUser(user_id);
|
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) {
|
if(!userData) {
|
||||||
response.writeHead(404, "Not Found", {"Content-Type": "text/html"});
|
response.writeHead(404, "Not Found", {"Content-Type": "text/html"});
|
||||||
var errorHtml = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/error", {
|
var errorHtml = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/error", {
|
||||||
@ -33,7 +46,7 @@ GET /admin/users/:user_id
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is trying to access their own profile or is an admin
|
// 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;
|
var hasPermission = ($tw.utils.parseInt(user_id) === state.authenticatedUser.user_id) || state.authenticatedUser.isAdmin;
|
||||||
if(!hasPermission) {
|
if(!hasPermission) {
|
||||||
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
response.writeHead(403, "Forbidden", { "Content-Type": "text/plain" });
|
||||||
response.end("Forbidden");
|
response.end("Forbidden");
|
||||||
@ -54,7 +67,12 @@ GET /admin/users/:user_id
|
|||||||
var allRoles = state.server.sqlTiddlerDatabase.listRoles();
|
var allRoles = state.server.sqlTiddlerDatabase.listRoles();
|
||||||
|
|
||||||
// sort allRoles by placing the user's role at the top of the list
|
// 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) });
|
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", {
|
response.writeHead(200, "OK", {
|
||||||
"Content-Type": "text/html"
|
"Content-Type": "text/html"
|
||||||
@ -69,12 +87,13 @@ GET /admin/users/:user_id
|
|||||||
"user-role": JSON.stringify(userRole),
|
"user-role": JSON.stringify(userRole),
|
||||||
"all-roles": JSON.stringify(allRoles),
|
"all-roles": JSON.stringify(allRoles),
|
||||||
"is-current-user-profile": state.authenticatedUser && state.authenticatedUser.user_id === $tw.utils.parseInt(user_id, 10) ? "yes" : "no",
|
"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",
|
"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",
|
||||||
|
"user-id": user_id,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
response.write(html);
|
response.write(html);
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -8,20 +8,19 @@ 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) {
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||||
var entity_type = state.data.entity_type;
|
var entity_type = state.data.entity_type;
|
||||||
var recipe_name = state.data.recipe_name;
|
var recipe_name = state.data.recipe_name;
|
||||||
@ -59,6 +58,6 @@ POST /admin/post-acl
|
|||||||
|
|
||||||
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
response.writeHead(302, { "Location": "/admin/acl/" + recipe_name + "/" + bag_name });
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -8,19 +8,19 @@ 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;
|
||||||
@ -31,6 +31,6 @@ POST /admin/post-role
|
|||||||
|
|
||||||
response.writeHead(302, { "Location": "/admin/roles" });
|
response.writeHead(302, { "Location": "/admin/roles" });
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
|
||||||
}());
|
}());
|
@ -8,26 +8,28 @@ 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) {
|
||||||
|
|
||||||
exports.handler = function(request, response, state) {
|
|
||||||
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
var sqlTiddlerDatabase = state.server.sqlTiddlerDatabase;
|
||||||
var username = state.data.username;
|
var username = state.data.username;
|
||||||
var email = state.data.email;
|
var email = state.data.email;
|
||||||
var password = state.data.password;
|
var password = state.data.password;
|
||||||
var confirmPassword = state.data.confirmPassword;
|
var confirmPassword = state.data.confirmPassword;
|
||||||
|
|
||||||
if(!state.authenticatedUser) {
|
if(!state.authenticatedUser && !state.firstGuestUser) {
|
||||||
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
response.writeHead(401, "Unauthorized", { "Content-Type": "text/plain" });
|
||||||
response.end("Unauthorized");
|
response.end("Unauthorized");
|
||||||
return;
|
return;
|
||||||
@ -53,11 +55,32 @@ POST /admin/post-user
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new user
|
var hasUsers = sqlTiddlerDatabase.listUsers().length > 0;
|
||||||
var userId = sqlTiddlerDatabase.createUser(username, email, password);
|
var hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
|
||||||
|
|
||||||
|
// 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.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +37,15 @@ exports.handler = function (request,response,state) {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,12 +57,18 @@ exports.handler = function (request,response,state) {
|
|||||||
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,6 +18,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
|||||||
</$tiddler>
|
</$tiddler>
|
||||||
|
|
||||||
<div class="users-container">
|
<div class="users-container">
|
||||||
|
<$list filter="[<user-list>jsonindexes[]count[]!match[0]]">
|
||||||
<div class="users-list">
|
<div class="users-list">
|
||||||
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
|
||||||
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
|
||||||
@ -44,8 +45,9 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
|||||||
</$let>
|
</$let>
|
||||||
</$list>
|
</$list>
|
||||||
</div>
|
</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>
|
@ -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