mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-07-04 02:52:52 +00:00
remove list users command & added support for database in server options
This commit is contained in:
parent
f02c8562f0
commit
81f73de87d
@ -336,7 +336,7 @@ Get the browser location.hash. We don't use location.hash because of the way tha
|
|||||||
*/
|
*/
|
||||||
$tw.utils.getLocationHash = function() {
|
$tw.utils.getLocationHash = function() {
|
||||||
var href = window.location.href;
|
var href = window.location.href;
|
||||||
var idx = href.indexOf("#");
|
var idx = href.indexOf('#');
|
||||||
if(idx === -1) {
|
if(idx === -1) {
|
||||||
return "#";
|
return "#";
|
||||||
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
|
} else if(href.substr(idx + 1,1) === "#" || href.substr(idx + 1,3) === "%23") {
|
||||||
@ -605,7 +605,7 @@ var globalCheck =[
|
|||||||
" delete Object.prototype.__temp__;",
|
" delete Object.prototype.__temp__;",
|
||||||
" }",
|
" }",
|
||||||
" delete Object.prototype.__temp__;",
|
" delete Object.prototype.__temp__;",
|
||||||
].join("\n");
|
].join('\n');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Run code globally with specified context variables in scope
|
Run code globally with specified context variables in scope
|
||||||
@ -1997,7 +1997,7 @@ $tw.loadTiddlersFromSpecification = function(filepath,excludeRegExp) {
|
|||||||
value = path.relative(rootPath, filename).split(path.sep).slice(0, -1);
|
value = path.relative(rootPath, filename).split(path.sep).slice(0, -1);
|
||||||
break;
|
break;
|
||||||
case "filepath":
|
case "filepath":
|
||||||
value = path.relative(rootPath, filename).split(path.sep).join("/");
|
value = path.relative(rootPath, filename).split(path.sep).join('/');
|
||||||
break;
|
break;
|
||||||
case "filename":
|
case "filename":
|
||||||
value = path.basename(filename);
|
value = path.basename(filename);
|
||||||
@ -2623,7 +2623,7 @@ $tw.boot.executeNextStartupTask = function(callback) {
|
|||||||
}
|
}
|
||||||
taskIndex++;
|
taskIndex++;
|
||||||
}
|
}
|
||||||
if(typeof callback === "function") {
|
if(typeof callback === 'function') {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -11,9 +11,6 @@
|
|||||||
"tiddlywiki/snowwhite"
|
"tiddlywiki/snowwhite"
|
||||||
],
|
],
|
||||||
"build": {
|
"build": {
|
||||||
"--mws-list-users": [
|
|
||||||
"--mws-list-users"
|
|
||||||
],
|
|
||||||
"mws-add-user": [
|
"mws-add-user": [
|
||||||
"--mws-add-permission", "READ", "Allows user to create tiddlers",
|
"--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-permission", "WRITE", "Gives the user the permission to edit and delete tiddlers",
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form
|
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form
|
||||||
|
|
||||||
<$macrocall $name="loginForm"/>
|
|
||||||
<form class="login-form" method="POST" action="/login">
|
<form class="login-form" method="POST" action="/login">
|
||||||
<input type="hidden" name="returnUrl" value=<<returnUrl>>/>
|
<input type="hidden" name="returnUrl" value=<<returnUrl>>/>
|
||||||
<input type="text" name="username" placeholder="Username"/>
|
<input type="text" name="username" placeholder="Username"/>
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
/*\
|
|
||||||
title: $:/plugins/tiddlywiki/multiwikiserver/commands/mws-list-users.js
|
|
||||||
type: application/javascript
|
|
||||||
module-type: command
|
|
||||||
|
|
||||||
Command to list users
|
|
||||||
|
|
||||||
\*/
|
|
||||||
(function(){
|
|
||||||
|
|
||||||
/*jslint node: true, browser: true */
|
|
||||||
/*global $tw: false */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
exports.info = {
|
|
||||||
name: "mws-list-users",
|
|
||||||
synchronous: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var Command = function(params,commander,callback) {
|
|
||||||
this.params = params;
|
|
||||||
this.commander = commander;
|
|
||||||
this.callback = callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
Command.prototype.execute = function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if(!$tw.mws || !$tw.mws.store || !$tw.mws.store.sqlTiddlerDatabase) {
|
|
||||||
return "Error: MultiWikiServer or SQL database not initialized.";
|
|
||||||
}
|
|
||||||
|
|
||||||
var users = $tw.mws.store.sqlTiddlerDatabase.listUsers().map(function(user){
|
|
||||||
return ({
|
|
||||||
username: user.username,
|
|
||||||
email: user.email,
|
|
||||||
created_at: user.created_at,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
console.log("Users:", users);
|
|
||||||
self.callback(null, "Users retrieved successfully:\n" + JSON.stringify(users, null, 2));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Command = Command;
|
|
||||||
|
|
||||||
})();
|
|
@ -50,11 +50,18 @@ TestRunner.prototype.runTests = function(callback) {
|
|||||||
const self = this;
|
const self = this;
|
||||||
let currentTestSpec = 0;
|
let currentTestSpec = 0;
|
||||||
let hasFailed = false;
|
let hasFailed = false;
|
||||||
|
let sessionId;
|
||||||
function runNextTest() {
|
function runNextTest() {
|
||||||
if(currentTestSpec < testSpecs.length) {
|
if(currentTestSpec < testSpecs.length) {
|
||||||
const testSpec = testSpecs[currentTestSpec];
|
const testSpec = testSpecs[currentTestSpec];
|
||||||
|
if(!!sessionId) {
|
||||||
|
testSpec.headers['Cookie'] = `session=${sessionId}; HttpOnly; Path=/`;
|
||||||
|
}
|
||||||
currentTestSpec += 1;
|
currentTestSpec += 1;
|
||||||
self.runTest(testSpec,function(err) {
|
self.runTest(testSpec,function(err, data) {
|
||||||
|
if(data?.sessionId) {
|
||||||
|
sessionId = data?.sessionId;
|
||||||
|
}
|
||||||
if(err) {
|
if(err) {
|
||||||
hasFailed = true;
|
hasFailed = true;
|
||||||
console.log(`Failed "${testSpec.description}" with "${err}"`)
|
console.log(`Failed "${testSpec.description}" with "${err}"`)
|
||||||
@ -96,7 +103,7 @@ TestRunner.prototype.runTest = function(testSpec,callback) {
|
|||||||
response.on("end", () => {
|
response.on("end", () => {
|
||||||
const jsonData = $tw.utils.parseJSONSafe(buffer,function() {return undefined;});
|
const jsonData = $tw.utils.parseJSONSafe(buffer,function() {return undefined;});
|
||||||
const testResult = testSpec.expectedResult(jsonData,buffer,response.headers);
|
const testResult = testSpec.expectedResult(jsonData,buffer,response.headers);
|
||||||
callback(testResult ? null : "Test failed");
|
callback(testResult ? null : "Test failed", jsonData);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
request.on("error", (e) => {
|
request.on("error", (e) => {
|
||||||
@ -112,6 +119,20 @@ 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",
|
||||||
|
@ -19,7 +19,8 @@ if($tw.node) {
|
|||||||
path = require("path"),
|
path = require("path"),
|
||||||
querystring = require("querystring"),
|
querystring = require("querystring"),
|
||||||
crypto = require("crypto"),
|
crypto = require("crypto"),
|
||||||
zlib = require("zlib");
|
zlib = require("zlib"),
|
||||||
|
aclMiddleware = require('$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js').middleware;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -34,7 +35,7 @@ function Server(options) {
|
|||||||
this.authenticators = options.authenticators || [];
|
this.authenticators = options.authenticators || [];
|
||||||
this.wiki = options.wiki;
|
this.wiki = options.wiki;
|
||||||
this.boot = options.boot || $tw.boot;
|
this.boot = options.boot || $tw.boot;
|
||||||
this.sqlTiddlerDatabase = $tw.mws.store.sqlTiddlerDatabase;
|
this.sqlTiddlerDatabase = options.sqlTiddlerDatabase || $tw.mws.store.sqlTiddlerDatabase;
|
||||||
// Initialise the variables
|
// Initialise the variables
|
||||||
this.variables = $tw.utils.extend({},this.defaultVariables);
|
this.variables = $tw.utils.extend({},this.defaultVariables);
|
||||||
if(options.variables) {
|
if(options.variables) {
|
||||||
@ -158,9 +159,10 @@ function sendResponse(request,response,statusCode,headers,data,encoding) {
|
|||||||
data = zlib.gzipSync(data);
|
data = zlib.gzipSync(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!response.headersSent) {
|
||||||
response.writeHead(statusCode,headers);
|
response.writeHead(statusCode,headers);
|
||||||
response.end(data,encoding);
|
response.end(data,encoding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirect(request,response,statusCode,location) {
|
function redirect(request,response,statusCode,location) {
|
||||||
@ -351,6 +353,13 @@ Server.prototype.methodMappings = {
|
|||||||
"DELETE": "writers"
|
"DELETE": "writers"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Server.prototype.methodACLPermMappings = {
|
||||||
|
"GET": "READ",
|
||||||
|
"PUT": "WRITE",
|
||||||
|
"POST": "WRITE",
|
||||||
|
"DELETE": "WRITE"
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Check whether a given user is authorized for the specified authorizationType ("readers" or "writers"). Pass null or undefined as the username to check for anonymous access
|
Check whether a given user is authorized for the specified authorizationType ("readers" or "writers"). Pass null or undefined as the username to check for anonymous access
|
||||||
*/
|
*/
|
||||||
@ -411,8 +420,7 @@ Server.prototype.redirectToLogin = function(response, returnUrl) {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`Invalid return URL detected: ${returnUrl}. Redirecting to home page.`);
|
console.log(`Invalid return URL detected: ${returnUrl}. Redirecting to home page.`);
|
||||||
}
|
}
|
||||||
response.setHeader('Set-Cookie', `returnUrl=${encodeURIComponent(sanitizedReturnUrl)}; HttpOnly; Path=/`);
|
response.setHeader('Set-Cookie', `returnUrl=${encodeURIComponent(sanitizedReturnUrl)}; HttpOnly; Secure; SameSite=Strict; Path=/`); const loginUrl = '/login';
|
||||||
const loginUrl = '/login';
|
|
||||||
response.writeHead(302, {
|
response.writeHead(302, {
|
||||||
'Location': loginUrl
|
'Location': loginUrl
|
||||||
});
|
});
|
||||||
@ -469,6 +477,12 @@ Server.prototype.requestHandler = function(request,response,options) {
|
|||||||
// Find the route that matches this path
|
// Find the route that matches this path
|
||||||
var route = self.findMatchingRoute(request,state);
|
var route = self.findMatchingRoute(request,state);
|
||||||
|
|
||||||
|
// If the route is configured to use ACL middleware, check that the user has permission
|
||||||
|
if(route?.useACL) {
|
||||||
|
const permissionName = this.methodACLPermMappings[route.method];
|
||||||
|
aclMiddleware(request,response,state,route.entityName,permissionName)
|
||||||
|
}
|
||||||
|
|
||||||
// Optionally output debug info
|
// Optionally output debug info
|
||||||
if(self.get("debug-level") !== "none") {
|
if(self.get("debug-level") !== "none") {
|
||||||
console.log("Request path:",JSON.stringify(state.urlInfo));
|
console.log("Request path:",JSON.stringify(state.urlInfo));
|
||||||
|
@ -13,12 +13,14 @@ GET /bags/:bag_name
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require('$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js').middleware;
|
|
||||||
|
|
||||||
exports.method = "GET";
|
exports.method = "GET";
|
||||||
|
|
||||||
exports.path = /^\/bags\/([^\/]+)(\/?)$/;
|
exports.path = /^\/bags\/([^\/]+)(\/?)$/;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "bag"
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
exports.handler = function (request, response, state) {
|
||||||
// Redirect if there is no trailing slash. We do this so that the relative URL specified in the upload form works correctly
|
// Redirect if there is no trailing slash. We do this so that the relative URL specified in the upload form works correctly
|
||||||
if (state.params[1] !== "/") {
|
if (state.params[1] !== "/") {
|
||||||
@ -33,7 +35,6 @@ exports.handler = function (request, response, state) {
|
|||||||
if (request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) {
|
if (request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) {
|
||||||
state.sendResponse(200, { "Content-Type": "application/json" }, JSON.stringify(bagTiddlers), "utf8");
|
state.sendResponse(200, { "Content-Type": "application/json" }, JSON.stringify(bagTiddlers), "utf8");
|
||||||
} else {
|
} else {
|
||||||
aclMiddleware(request, response, state, 'bag', 'READ');
|
|
||||||
if (!response.headersSent) {
|
if (!response.headersSent) {
|
||||||
// This is not a JSON API request, we should return the raw tiddler content
|
// This is not a JSON API request, we should return the raw tiddler content
|
||||||
response.writeHead(200, "OK", {
|
response.writeHead(200, "OK", {
|
||||||
|
@ -16,14 +16,15 @@ fallback=<url> // Optional redirect if the tiddler is not found
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "GET";
|
exports.method = "GET";
|
||||||
|
|
||||||
exports.path = /^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/;
|
exports.path = /^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "recipe"
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
aclMiddleware(request, response, state, "recipe", "READ");
|
|
||||||
// Get the parameters
|
// Get the parameters
|
||||||
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||||
title = $tw.utils.decodeURIComponentSafe(state.params[1]),
|
title = $tw.utils.decodeURIComponentSafe(state.params[1]),
|
||||||
|
@ -12,8 +12,6 @@ POST /bags/:bag_name/tiddlers/
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "POST";
|
exports.method = "POST";
|
||||||
|
|
||||||
exports.path = /^\/bags\/([^\/]+)\/tiddlers\/$/;
|
exports.path = /^\/bags\/([^\/]+)\/tiddlers\/$/;
|
||||||
@ -22,8 +20,11 @@ exports.bodyFormat = "stream";
|
|||||||
|
|
||||||
exports.csrfDisable = true;
|
exports.csrfDisable = true;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "bag"
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
aclMiddleware(request, response, state, "bag", "WRITE");
|
|
||||||
const path = require("path"),
|
const path = require("path"),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
processIncomingStream = require("$:/plugins/tiddlywiki/multiwikiserver/routes/helpers/multipart-forms.js").processIncomingStream;
|
processIncomingStream = require("$:/plugins/tiddlywiki/multiwikiserver/routes/helpers/multipart-forms.js").processIncomingStream;
|
||||||
|
@ -17,8 +17,6 @@ description
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "POST";
|
exports.method = "POST";
|
||||||
|
|
||||||
exports.path = /^\/bags$/;
|
exports.path = /^\/bags$/;
|
||||||
@ -27,8 +25,11 @@ exports.bodyFormat = "www-form-urlencoded";
|
|||||||
|
|
||||||
exports.csrfDisable = true;
|
exports.csrfDisable = true;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "bag"
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
aclMiddleware(request, response, state, "bag", "WRITE");
|
|
||||||
if(state.data.bag_name) {
|
if(state.data.bag_name) {
|
||||||
const result = $tw.mws.store.createBag(state.data.bag_name,state.data.description);
|
const result = $tw.mws.store.createBag(state.data.bag_name,state.data.description);
|
||||||
if(!result) {
|
if(!result) {
|
||||||
|
@ -37,17 +37,29 @@ exports.handler = function(request,response,state) {
|
|||||||
var sessionId = auth.createSession(user.user_id);
|
var sessionId = auth.createSession(user.user_id);
|
||||||
var returnUrl = state.server.parseCookieString(request.headers.cookie).returnUrl
|
var returnUrl = state.server.parseCookieString(request.headers.cookie).returnUrl
|
||||||
response.setHeader('Set-Cookie', `session=${sessionId}; HttpOnly; Path=/`);
|
response.setHeader('Set-Cookie', `session=${sessionId}; HttpOnly; Path=/`);
|
||||||
response.writeHead(302, {
|
if(request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) {
|
||||||
'Location': returnUrl || '/'
|
state.sendResponse(200,{"Content-Type": "application/json"},JSON.stringify({
|
||||||
});
|
"sessionId": sessionId
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
response.writeHead(302, {
|
||||||
|
'Location': returnUrl || '/'
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||||
title: "$:/temp/mws/login/error",
|
title: "$:/temp/mws/login/error",
|
||||||
text: "Invalid username or password"
|
text: "Invalid username or password"
|
||||||
}));
|
}));
|
||||||
response.writeHead(302, {
|
if(request.headers.accept && request.headers.accept.indexOf("application/json") !== -1) {
|
||||||
'Location': '/login'
|
state.sendResponse(200,{"Content-Type": "application/json"},JSON.stringify({
|
||||||
});
|
"message": "Invalid username or password"
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
response.writeHead(302, {
|
||||||
|
'Location': '/login'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
response.end();
|
response.end();
|
||||||
};
|
};
|
||||||
|
@ -18,8 +18,6 @@ bag_names: space separated list of bags
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "POST";
|
exports.method = "POST";
|
||||||
|
|
||||||
exports.path = /^\/recipes$/;
|
exports.path = /^\/recipes$/;
|
||||||
@ -28,8 +26,11 @@ exports.bodyFormat = "www-form-urlencoded";
|
|||||||
|
|
||||||
exports.csrfDisable = true;
|
exports.csrfDisable = true;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "recipe"
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
aclMiddleware(request, response, state, "recipe", "WRITE");
|
|
||||||
if(state.data.recipe_name && state.data.bag_names) {
|
if(state.data.recipe_name && state.data.bag_names) {
|
||||||
const result = $tw.mws.store.createRecipe(state.data.recipe_name,$tw.utils.parseStringArray(state.data.bag_names),state.data.description);
|
const result = $tw.mws.store.createRecipe(state.data.recipe_name,$tw.utils.parseStringArray(state.data.bag_names),state.data.description);
|
||||||
if(!result) {
|
if(!result) {
|
||||||
|
@ -12,14 +12,15 @@ PUT /bags/:bag_name
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "PUT";
|
exports.method = "PUT";
|
||||||
|
|
||||||
exports.path = /^\/bags\/(.+)$/;
|
exports.path = /^\/bags\/(.+)$/;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "bag"
|
||||||
|
|
||||||
exports.handler = function(request,response,state) {
|
exports.handler = function(request,response,state) {
|
||||||
aclMiddleware(request, response, state, "bag", "WRITE");
|
|
||||||
// Get the parameters
|
// Get the parameters
|
||||||
var bag_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
var bag_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||||
data = $tw.utils.parseJSONSafe(state.data);
|
data = $tw.utils.parseJSONSafe(state.data);
|
||||||
|
@ -12,14 +12,15 @@ PUT /recipes/:recipe_name/tiddlers/:title
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "PUT";
|
exports.method = "PUT";
|
||||||
|
|
||||||
exports.path = /^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/;
|
exports.path = /^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "recipe"
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
exports.handler = function (request, response, state) {
|
||||||
aclMiddleware(request, response, state, "recipe", "WRITE");
|
|
||||||
// Get the parameters
|
// Get the parameters
|
||||||
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||||
title = $tw.utils.decodeURIComponentSafe(state.params[1]),
|
title = $tw.utils.decodeURIComponentSafe(state.params[1]),
|
||||||
|
@ -12,14 +12,15 @@ PUT /recipes/:recipe_name
|
|||||||
/*global $tw: false */
|
/*global $tw: false */
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var aclMiddleware = require("$:/plugins/tiddlywiki/multiwikiserver/modules/routes/helpers/acl-middleware.js").middleware;
|
|
||||||
|
|
||||||
exports.method = "PUT";
|
exports.method = "PUT";
|
||||||
|
|
||||||
exports.path = /^\/recipes\/(.+)$/;
|
exports.path = /^\/recipes\/(.+)$/;
|
||||||
|
|
||||||
|
exports.useACL = true;
|
||||||
|
|
||||||
|
exports.entityName = "recipe"
|
||||||
|
|
||||||
exports.handler = function (request, response, state) {
|
exports.handler = function (request, response, state) {
|
||||||
aclMiddleware(request, response, state, "recipe", "WRITE");
|
|
||||||
// Get the parameters
|
// Get the parameters
|
||||||
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
var recipe_name = $tw.utils.decodeURIComponentSafe(state.params[0]),
|
||||||
data = $tw.utils.parseJSONSafe(state.data);
|
data = $tw.utils.parseJSONSafe(state.data);
|
||||||
|
@ -215,14 +215,12 @@ SqlTiddlerDatabase.prototype.createBag = function(bag_name,description,accesscon
|
|||||||
$description: description
|
$description: description
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const admin = this.getRoleByName("ADMIN");
|
||||||
// update the permissions on ACL records
|
|
||||||
const admin = this.getRoleByName('ADMIN');
|
|
||||||
if(admin) {
|
if(admin) {
|
||||||
const readPermission = this.getPermissionByName('READ');
|
const readPermission = this.getPermissionByName("READ");
|
||||||
const writePermission = this.getPermissionByName('WRITE');
|
const writePermission = this.getPermissionByName("WRITE");
|
||||||
this.createACL(bag_name, 'bag', admin.role_id, readPermission.permission_id);
|
this.createACL(bag_name, "bag", admin.role_id, readPermission.permission_id);
|
||||||
this.createACL(bag_name, 'bag', admin.role_id, writePermission.permission_id);
|
this.createACL(bag_name, "bag", admin.role_id, writePermission.permission_id);
|
||||||
}
|
}
|
||||||
return updateBags.lastInsertRowid;
|
return updateBags.lastInsertRowid;
|
||||||
};
|
};
|
||||||
@ -290,12 +288,12 @@ SqlTiddlerDatabase.prototype.createRecipe = function(recipe_name,bag_names,descr
|
|||||||
|
|
||||||
|
|
||||||
// update the permissions on ACL records
|
// update the permissions on ACL records
|
||||||
const admin = this.getRoleByName('ADMIN');
|
const admin = this.getRoleByName("ADMIN");
|
||||||
if(admin) {
|
if(admin) {
|
||||||
const readPermission = this.getPermissionByName('READ');
|
const readPermission = this.getPermissionByName("READ");
|
||||||
const writePermission = this.getPermissionByName('WRITE');
|
const writePermission = this.getPermissionByName("WRITE");
|
||||||
this.createACL(recipe_name, 'recipe', admin.role_id, readPermission.permission_id);
|
this.createACL(recipe_name, "recipe", admin.role_id, readPermission.permission_id);
|
||||||
this.createACL(recipe_name, 'recipe', admin.role_id, writePermission.permission_id);
|
this.createACL(recipe_name, "recipe", admin.role_id, writePermission.permission_id);
|
||||||
}
|
}
|
||||||
return updateRecipes.lastInsertRowid;
|
return updateRecipes.lastInsertRowid;
|
||||||
};
|
};
|
||||||
@ -495,34 +493,34 @@ SqlTiddlerDatabase.prototype.getRecipeTiddler = function(title,recipe_name) {
|
|||||||
Checks if a user has permission to access a recipe
|
Checks if a user has permission to access a recipe
|
||||||
*/
|
*/
|
||||||
SqlTiddlerDatabase.prototype.hasRecipePermission = function(userId, recipeName, permissionName) {
|
SqlTiddlerDatabase.prototype.hasRecipePermission = function(userId, recipeName, permissionName) {
|
||||||
return this.checkACLPermission(userId, 'recipe', recipeName, permissionName)
|
return this.checkACLPermission(userId, "recipe", recipeName, permissionName)
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Checks if a user has permission to access a bag
|
Checks if a user has permission to access a bag
|
||||||
*/
|
*/
|
||||||
SqlTiddlerDatabase.prototype.hasBagPermission = function(userId, bagName, permissionName) {
|
SqlTiddlerDatabase.prototype.hasBagPermission = function(userId, bagName, permissionName) {
|
||||||
return this.checkACLPermission(userId, 'bag', bagName, permissionName)
|
return this.checkACLPermission(userId, "bag", bagName, permissionName)
|
||||||
};
|
};
|
||||||
|
|
||||||
SqlTiddlerDatabase.prototype.checkACLPermission = function(userId, entityType, entityName, permissionName) {
|
SqlTiddlerDatabase.prototype.checkACLPermission = function(userId, entityType, entityName, permissionName) {
|
||||||
const entityTypeToTableMap = {
|
const entityTypeToTableMap = {
|
||||||
bag: {
|
bag: {
|
||||||
table: 'bags',
|
table: "bags",
|
||||||
column: 'bag_name'
|
column: "bag_name"
|
||||||
},
|
},
|
||||||
recipe: {
|
recipe: {
|
||||||
table: 'recipes',
|
table: "recipes",
|
||||||
column: 'recipe_name'
|
column: "recipe_name"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityInfo = entityTypeToTableMap[entityType];
|
const entityInfo = entityTypeToTableMap[entityType];
|
||||||
if (!entityInfo) {
|
if (!entityInfo) {
|
||||||
throw new Error('Invalid entity type: ' + entityType);
|
throw new Error("Invalid entity type: " + entityType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the entityName starts with "$:/", we'll assume its a system tiddler, then grant the user permission
|
// if the entityName starts with "$:/", we'll assume its a system bag/recipe, then grant the user permission
|
||||||
if(entityName.startsWith("$:/")){
|
if(entityName.startsWith("$:/")){
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -5,43 +5,45 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
<$transclude/>
|
<$transclude/>
|
||||||
</$set>
|
</$set>
|
||||||
</$tiddler>
|
</$tiddler>
|
||||||
|
<div class="main-wrapper">
|
||||||
<div class="user-profile-container">
|
<div class="user-profile-container">
|
||||||
<div class="user-profile-header">
|
<div class="user-profile-header">
|
||||||
<div class="user-profile-avatar">
|
<div class="user-profile-avatar">
|
||||||
<$text text={{{ [<user>jsonget[username]substr[0,1]uppercase[]] }}}/>
|
<$text text={{{ [<user>jsonget[username]substr[0,1]uppercase[]] }}}/>
|
||||||
</div>
|
</div>
|
||||||
<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 class="user-profile-details">
|
|
||||||
<div class="user-profile-item">
|
|
||||||
<span class="user-profile-label">User ID:</span>
|
|
||||||
<span class="user-profile-value"><$text text={{{ [<user>jsonget[user_id]] }}}/></span>
|
|
||||||
</div>
|
|
||||||
<div class="user-profile-item">
|
|
||||||
<span class="user-profile-label">Created At:</span>
|
|
||||||
<span class="user-profile-value"><$text text={{{ [<user>jsonget[created_at]split[T]first[]] }}}/></span>
|
|
||||||
</div>
|
|
||||||
<div class="user-profile-item">
|
|
||||||
<span class="user-profile-label">Last Login:</span>
|
|
||||||
<span class="user-profile-value"><$text text={{{ [<user>jsonget[last_login]split[T]first[]] }}}/></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="user-profile-roles">
|
<div class="user-profile-details">
|
||||||
<h2>User Roles</h2>
|
<div class="user-profile-item">
|
||||||
<ul>
|
<span class="user-profile-label">User ID:</span>
|
||||||
<$list filter="[<user-roles>jsonindexes[]]" variable="role-index">
|
<span class="user-profile-value"><$text text={{{ [<user>jsonget[user_id]] }}}/></span>
|
||||||
<li>
|
</div>
|
||||||
<$text text={{{ [<user-roles>jsonextract<role-index>jsonget[role_name]] }}}/>
|
<div class="user-profile-item">
|
||||||
</li>
|
<span class="user-profile-label">Created At:</span>
|
||||||
</$list>
|
<span class="user-profile-value"><$text text={{{ [<user>jsonget[created_at]split[T]first[]] }}}/></span>
|
||||||
</ul>
|
</div>
|
||||||
|
<div class="user-profile-item">
|
||||||
|
<span class="user-profile-label">Last Login:</span>
|
||||||
|
<span class="user-profile-value"><$text text={{{ [<user>jsonget[last_login]split[T]first[]] }}}/></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="user-profile-roles">
|
||||||
|
<h2>User Roles</h2>
|
||||||
|
<ul>
|
||||||
|
<$list filter="[<user-roles>jsonindexes[]]" variable="role-index">
|
||||||
|
<li>
|
||||||
|
<$text text={{{ [<user-roles>jsonextract<role-index>jsonget[role_name]] }}}/>
|
||||||
|
</li>
|
||||||
|
</$list>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<$reveal type="match" state="is-current-user-profile" text="yes">
|
|
||||||
|
<!-- <$reveal type="match" state="is-current-user-profile" text="yes"> -->
|
||||||
<div class="user-profile-management">
|
<div class="user-profile-management">
|
||||||
<h2>Manage Your Account</h2>
|
<h2>Manage Your Account</h2>
|
||||||
<form class="user-profile-form">
|
<form class="user-profile-form">
|
||||||
@ -53,28 +55,42 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
<label for="email">Email:</label>
|
<label for="email">Email:</label>
|
||||||
<input type="email" id="email" name="email" value={{{ [<user>jsonget[email]] }}} />
|
<input type="email" id="email" name="email" value={{{ [<user>jsonget[email]] }}} />
|
||||||
</div>
|
</div>
|
||||||
|
<button type="submit" class="update-profile-btn">Update Profile</button>
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
<h2>Danger Zone</h2>
|
||||||
|
<form class="user-profile-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="new-password">New Password:</label>
|
<label for="new-password">New Password:</label>
|
||||||
<input type="password" id="new-password" name="new-password" />
|
<input type="password" id="new-password" name="new-password" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="confirm-password">Confirm New Password:</label>
|
<label for="confirm-password">Confirm New Password:</label>
|
||||||
<input type="password" id="confirm-password" name="confirm-password" />
|
<input type="password" id="confirm-password" name="confirm-password" required />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="update-profile-btn">Update Profile</button>
|
<button type="submit" class="update-password-btn">Change Password</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</$reveal>
|
<!-- </$reveal> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.main-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 5px;
|
||||||
|
max-width: 80vw;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.user-profile-container {
|
.user-profile-container {
|
||||||
max-width: 600px;
|
flex: 1;
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
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);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-header {
|
.user-profile-header {
|
||||||
@ -155,9 +171,7 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-management {
|
.user-profile-management {
|
||||||
margin-top: 2rem;
|
padding: 20px;
|
||||||
padding: 2rem;
|
|
||||||
border-top: 1px solid #e0e0e0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-profile-management h2 {
|
.user-profile-management h2 {
|
||||||
@ -166,6 +180,10 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-profile-form {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.user-profile-form .form-group {
|
.user-profile-form .form-group {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
@ -184,7 +202,8 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-profile-btn {
|
.update-profile-btn,
|
||||||
|
.update-password-btn {
|
||||||
background: #3498db;
|
background: #3498db;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border: none;
|
border: none;
|
||||||
@ -194,7 +213,15 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-password-btn {
|
||||||
|
background: #00796b;
|
||||||
|
}
|
||||||
|
|
||||||
.update-profile-btn:hover {
|
.update-profile-btn:hover {
|
||||||
background: #2980b9;
|
background: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-password-btn:hover {
|
||||||
|
background: #00695c;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user