mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-23 10:07:19 +00:00
Module-ize server routes, add static file support and other enhancements(#2679)
* Module-ize server routes and add static file support (#2510)
* Refactor server routes to modules
New module type: serverroute
Caveats: Loading order is not deterministic but this would only matter
if two route modules attempted to use the same path regexp (that would
be silly).
* Add static assets plugin
This plugin allows the node server to fetch static assets in the /assets
directory. I felt that this was a feature that goes above the core
functionality. That is why I added it as a plugin. with the modular
route extensions this was a breeze.
* Add serverroute description to ModuleTypes
* Coding standards tweaks
* Fix filename typo
* Move support for attachments from a plugin into the core
* Missing "else"
* Refactor server handling
* Introduce a new named parameter scheme for commands
* Move the SimpleServer class into it's own module
* Deprecate the --server command because of the unwieldy syntax
* Add a new --listen command using the new syntax
For example:
tiddlywiki mywiki --listen host:0.0.0.0 port:8090
* Add check for unknown parameters
* Add support for multiple basic authentication credentials in a CSV file
Beware: Passwords are stored in plain text. If that's a problem, use an authenticating proxy and the trusted header authentication approach.
* Refactor module locations
* Rename "serverroute" module type to "route"
* Remove support for verifying optional named command parameters
The idea was to be able to flag unknown parameter names, but requiring a command to pre-specify all the parameter names makes it harder for (say) the listen command to be extensible so that plugins can add new optional parameters that they handle. (This is particularly in the context of work in progress to encapsulate authenticators into their own modules).
* Refactor the two authenticators into separate modules and add support for authorization
* Correct mistaken path.join vs. path.resolve
See https://stackoverflow.com/a/39836259
* Docs for the named command parameters
I'd be grateful if anyone with sufficient Windows experience could confirm that the note about double quotes in "NamedCommandParameters" is correct.
* Be consistent about lower case parameter names
* Do the right thing when we have a username but no password
With a username parameter but no password parameter we'll attribute edits to that username, but not require authentication.
* Remove obsolete code
* Add support for requiring authentication without restricting the username
* Refactor authorization checks
* Return read_only status in /status response
* Fix two code typos
* Add basic support for detecting readonly status and avoiding write errors
We now have syncadaptors returning readonly status and avoid attempting to write to the server if it's going to fail
* Add readonly-styles
We hide editing-related buttons in read only mode
I've made this part of the tiddlyweb plugin but I think a case could be made for putting it into the core.
* Add custom request header as CSRF mitigation
By default we require the header X-Requested-With to be set to TiddlyWiki. Can be overriden by setting csrfdisable to "yes"
See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers
* Add support for HTTPS
* First pass at a route for serving rendered tiddlers
cc @Drakor
* Tweaks to the single tiddler static view
Adding a simple sidebar
* Switch to "dash" separated parameter names
* Typo
* Docs: Update ServerCommand and ListenCommand
* First pass at docs for the new web server stuff
Writing the docs is turning out to be quite an undertaking, much harder than writing the code!
* Get rid of extraneous paragraphs in static renderings
* Rejig anonymous user handling
Now we can support wikis that are read-only for anonymous access, but allow a user to login for read/write access.
* More docs
Slowly getting there...
* Static tiddler rendering: Fix HTML content in page title
* Docs updates
* Fix server command parameter names
Missed off 30ce7ea
* Docs: Missing quotes
* Avoid inadvertent dependency on Node.js > v9.6.0
The listenOptions parameter of the plain HTTP version of CreateServer was only introduced in v9.6.0
cc @Drakor @pmario
* Typo
This commit is contained in:
parent
9735e13dea
commit
c05c0d3df6
@ -2,6 +2,7 @@ title: $:/language/Docs/ModuleTypes/
|
||||
|
||||
allfilteroperator: A sub-operator for the ''all'' filter operator.
|
||||
animation: Animations that may be used with the RevealWidget.
|
||||
authenticator: Defines how requests are authenticated by the built-in HTTP server.
|
||||
bitmapeditoroperation: A bitmap editor toolbar operation.
|
||||
command: Commands that can be executed under Node.js.
|
||||
config: Data to be inserted into `$tw.config`.
|
||||
@ -12,6 +13,7 @@ isfilteroperator: Operands for the ''is'' filter operator.
|
||||
library: Generic module type for general purpose JavaScript modules.
|
||||
macro: JavaScript macro definitions.
|
||||
parser: Parsers for different content types.
|
||||
route: Defines how individual URL patterns are handled by the built-in HTTP server.
|
||||
saver: Savers handle different methods for saving files from the browser.
|
||||
startup: Startup functions.
|
||||
storyview: Story views customise the animation and behaviour of list widgets.
|
||||
|
30
core/language/en-GB/Help/listen.tid
Normal file
30
core/language/en-GB/Help/listen.tid
Normal file
@ -0,0 +1,30 @@
|
||||
title: $:/language/Help/listen
|
||||
description: Provides an HTTP server interface to TiddlyWiki
|
||||
|
||||
Serves a wiki over HTTP.
|
||||
|
||||
The listen command uses NamedCommandParameters:
|
||||
|
||||
```
|
||||
--listen [<name>=<value>]...
|
||||
```
|
||||
|
||||
All parameters are optional with safe defaults, and can be specified in any order. The recognised parameters are:
|
||||
|
||||
* ''host'' - optional hostname to serve from (defaults to "127.0.0.1" aka "localhost")
|
||||
* ''path-prefix'' - optional prefix for paths
|
||||
* ''port'' - port number on which to listen; non-numeric values are interpreted as a system environment variable from which the port number is extracted (defaults to "8080")
|
||||
* ''credentials'' - pathname of credentials CSV file (relative to wiki folder)
|
||||
* ''anon-username'' - the username for signing edits for anonymous users
|
||||
* ''username'' - optional username for basic authentication
|
||||
* ''password'' - optional password for basic authentication
|
||||
* ''authenticated-user-header'' - optional name of header to be used for trusted authentication
|
||||
* ''readers'' - comma separated list of principals allowed to write to this wiki
|
||||
* ''writers'' - comma separated list of principals allowed to read from this wiki
|
||||
* ''csrf-disable'' - set to "yes" to disable CSRF checks (defaults to "no")
|
||||
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
||||
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
||||
* ''root-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
||||
* ''tls-cert'' - pathname of TLS certificate file (relative to wiki folder)
|
||||
* ''tls-key'' - pathname of TLS key file (relative to wiki folder)
|
||||
* ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none")
|
@ -1,27 +1,25 @@
|
||||
title: $:/language/Help/server
|
||||
description: Provides an HTTP server interface to TiddlyWiki
|
||||
description: Provides an HTTP server interface to TiddlyWiki (deprecated in favour of the new listen command)
|
||||
|
||||
The server built in to TiddlyWiki5 is very simple. Although compatible with TiddlyWeb it doesn't support many of the features needed for robust Internet-facing usage.
|
||||
|
||||
At the root, it serves a rendering of a specified tiddler. Away from the root, it serves individual tiddlers encoded in JSON, and supports the basic HTTP operations for `GET`, `PUT` and `DELETE`.
|
||||
Legacy command to serve a wiki over HTTP.
|
||||
|
||||
```
|
||||
--server <port> <roottiddler> <rendertype> <servetype> <username> <password> <host> <pathprefix>
|
||||
--server <port> <root-tiddler> <root-render-type> <root-serve-type> <username> <password> <host> <path-prefix> <debug-level>
|
||||
```
|
||||
|
||||
The parameters are:
|
||||
|
||||
* ''port'' - port number on which to listen; non-numeric values are interpreted as a system environment variable from which the port number is extracted (defaults to "8080")
|
||||
* ''roottiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
||||
* ''rendertype'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
||||
* ''servetype'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
||||
* ''root-tiddler'' - the tiddler to serve at the root (defaults to "$:/core/save/all")
|
||||
* ''root-render-type'' - the content type to which the root tiddler should be rendered (defaults to "text/plain")
|
||||
* ''rooot-serve-type'' - the content type with which the root tiddler should be served (defaults to "text/html")
|
||||
* ''username'' - the default username for signing edits
|
||||
* ''password'' - optional password for basic authentication
|
||||
* ''host'' - optional hostname to serve from (defaults to "127.0.0.1" aka "localhost")
|
||||
* ''pathprefix'' - optional prefix for paths
|
||||
* ''debuglevel'' - optional debug level; set to "debug" to view request details (defaults to "none")
|
||||
* ''path-prefix'' - optional prefix for paths
|
||||
* ''debug-level'' - optional debug level; set to "debug" to view request details (defaults to "none")
|
||||
|
||||
If the password parameter is specified then the browser will prompt the user for the username and password. Note that the password is transmitted in plain text so this implementation isn't suitable for general use.
|
||||
If the password parameter is specified then the browser will prompt the user for the username and password. Note that the password is transmitted in plain text so this implementation should only be used on a trusted network or over HTTPS.
|
||||
|
||||
For example:
|
||||
|
||||
@ -37,7 +35,6 @@ The username and password can be specified as empty strings if you need to set t
|
||||
|
||||
To run multiple TiddlyWiki servers at the same time you'll need to put each one on a different port. It can be useful to use an environment variable to pass the port number to the Node.js process. This example references an environment variable called "MY_PORT_NUMBER":
|
||||
|
||||
|
||||
```
|
||||
--server MY_PORT_NUMBER $:/core/save/all text/plain text/html MyUserName passw0rd
|
||||
```
|
||||
|
@ -94,6 +94,13 @@ Commander.prototype.executeNextCommand = function() {
|
||||
if(this.verbose) {
|
||||
this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n");
|
||||
}
|
||||
// Parse named parameters if required
|
||||
if(command.info.namedParameterMode) {
|
||||
params = this.extractNamedParameters(params,command.info.mandatoryParameters);
|
||||
if(typeof params === "string") {
|
||||
return this.callback(params);
|
||||
}
|
||||
}
|
||||
if(command.info.synchronous) {
|
||||
// Synchronous command
|
||||
c = new command.Command(params,this);
|
||||
@ -122,6 +129,35 @@ Commander.prototype.executeNextCommand = function() {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Given an array of parameter strings `params` in name:value format, and an array of mandatory parameter names in `mandatoryParameters`, returns a hashmap of values or a string if error
|
||||
*/
|
||||
Commander.prototype.extractNamedParameters = function(params,mandatoryParameters) {
|
||||
mandatoryParameters = mandatoryParameters || [];
|
||||
var errors = [],
|
||||
paramsByName = Object.create(null);
|
||||
// Extract the parameters
|
||||
$tw.utils.each(params,function(param) {
|
||||
var index = param.indexOf("=");
|
||||
if(index < 1) {
|
||||
errors.push("malformed named parameter: '" + param + "'");
|
||||
}
|
||||
paramsByName[param.slice(0,index)] = $tw.utils.trim(param.slice(index+1));
|
||||
});
|
||||
// Check the mandatory parameters are present
|
||||
$tw.utils.each(mandatoryParameters,function(mandatoryParameter) {
|
||||
if(!$tw.utils.hop(paramsByName,mandatoryParameter)) {
|
||||
errors.push("missing mandatory parameter: '" + mandatoryParameter + "'");
|
||||
}
|
||||
});
|
||||
// Return any errors
|
||||
if(errors.length > 0) {
|
||||
return errors.join(" and\n");
|
||||
} else {
|
||||
return paramsByName;
|
||||
}
|
||||
};
|
||||
|
||||
Commander.initCommands = function(moduleType) {
|
||||
moduleType = moduleType || "command";
|
||||
$tw.commands = {};
|
||||
|
48
core/modules/commands/listen.js
Normal file
48
core/modules/commands/listen.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*\
|
||||
title: $:/core/modules/commands/listen.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Listen for HTTP requests and serve tiddlers
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
var Server = require("$:/core/modules/server/server.js").Server;
|
||||
|
||||
exports.info = {
|
||||
name: "listen",
|
||||
synchronous: true,
|
||||
namedParameterMode: true,
|
||||
mandatoryParameters: [],
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
var self = this;
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
var self = this;
|
||||
if(!$tw.boot.wikiTiddlersPath) {
|
||||
$tw.utils.warning("Warning: Wiki folder '" + $tw.boot.wikiPath + "' does not exist or is missing a tiddlywiki.info file");
|
||||
}
|
||||
// Set up server
|
||||
this.server = new Server({
|
||||
wiki: this.commander.wiki,
|
||||
variables: self.params
|
||||
});
|
||||
var nodeServer = this.server.listen();
|
||||
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer);
|
||||
return null;
|
||||
};
|
||||
|
||||
exports.Command = Command;
|
||||
|
||||
})();
|
@ -3,7 +3,7 @@ title: $:/core/modules/commands/server.js
|
||||
type: application/javascript
|
||||
module-type: command
|
||||
|
||||
Serve tiddlers over http
|
||||
Deprecated legacy command for serving tiddlers
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
@ -12,311 +12,41 @@ Serve tiddlers over http
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
if($tw.node) {
|
||||
var util = require("util"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
path = require("path"),
|
||||
http = require("http");
|
||||
}
|
||||
var Server = require("$:/core/modules/server/server.js").Server;
|
||||
|
||||
exports.info = {
|
||||
name: "server",
|
||||
synchronous: true
|
||||
};
|
||||
|
||||
/*
|
||||
A simple HTTP server with regexp-based routes
|
||||
*/
|
||||
function SimpleServer(options) {
|
||||
this.routes = options.routes || [];
|
||||
this.wiki = options.wiki;
|
||||
this.variables = options.variables || {};
|
||||
}
|
||||
|
||||
SimpleServer.prototype.set = function(obj) {
|
||||
var self = this;
|
||||
$tw.utils.each(obj,function(value,name) {
|
||||
self.variables[name] = value;
|
||||
});
|
||||
};
|
||||
|
||||
SimpleServer.prototype.get = function(name) {
|
||||
return this.variables[name];
|
||||
};
|
||||
|
||||
SimpleServer.prototype.addRoute = function(route) {
|
||||
this.routes.push(route);
|
||||
};
|
||||
|
||||
SimpleServer.prototype.findMatchingRoute = function(request,state) {
|
||||
var pathprefix = this.get("pathprefix") || "";
|
||||
for(var t=0; t<this.routes.length; t++) {
|
||||
var potentialRoute = this.routes[t],
|
||||
pathRegExp = potentialRoute.path,
|
||||
pathname = state.urlInfo.pathname,
|
||||
match;
|
||||
if(pathprefix) {
|
||||
if(pathname.substr(0,pathprefix.length) === pathprefix) {
|
||||
pathname = pathname.substr(pathprefix.length) || "/";
|
||||
match = potentialRoute.path.exec(pathname);
|
||||
} else {
|
||||
match = false;
|
||||
}
|
||||
} else {
|
||||
match = potentialRoute.path.exec(pathname);
|
||||
}
|
||||
if(match && request.method === potentialRoute.method) {
|
||||
state.params = [];
|
||||
for(var p=1; p<match.length; p++) {
|
||||
state.params.push(match[p]);
|
||||
}
|
||||
return potentialRoute;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
SimpleServer.prototype.checkCredentials = function(request,incomingUsername,incomingPassword) {
|
||||
var header = request.headers.authorization || "",
|
||||
token = header.split(/\s+/).pop() || "",
|
||||
auth = $tw.utils.base64Decode(token),
|
||||
parts = auth.split(/:/),
|
||||
username = parts[0],
|
||||
password = parts[1];
|
||||
if(incomingUsername === username && incomingPassword === password) {
|
||||
return "ALLOWED";
|
||||
} else {
|
||||
return "DENIED";
|
||||
}
|
||||
};
|
||||
|
||||
SimpleServer.prototype.requestHandler = function(request,response) {
|
||||
// Compose the state object
|
||||
var self = this;
|
||||
var state = {};
|
||||
state.wiki = self.wiki;
|
||||
state.server = self;
|
||||
state.urlInfo = url.parse(request.url);
|
||||
// Optionally output debug info
|
||||
if(self.get("debugLevel") !== "none") {
|
||||
console.log("Request path:",JSON.stringify(state.urlInfo));
|
||||
console.log("Request headers:",JSON.stringify(request.headers));
|
||||
}
|
||||
// Find the route that matches this path
|
||||
var route = self.findMatchingRoute(request,state);
|
||||
// Check for the username and password if we've got one
|
||||
var username = self.get("username"),
|
||||
password = self.get("password");
|
||||
if(username && password) {
|
||||
// Check they match
|
||||
if(self.checkCredentials(request,username,password) !== "ALLOWED") {
|
||||
var servername = state.wiki.getTiddlerText("$:/SiteTitle") || "TiddlyWiki5";
|
||||
response.writeHead(401,"Authentication required",{
|
||||
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + servername + '"'
|
||||
});
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Return a 404 if we didn't find a route
|
||||
if(!route) {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Set the encoding for the incoming request
|
||||
// TODO: Presumably this would need tweaking if we supported PUTting binary tiddlers
|
||||
request.setEncoding("utf8");
|
||||
// Dispatch the appropriate method
|
||||
switch(request.method) {
|
||||
case "GET": // Intentional fall-through
|
||||
case "DELETE":
|
||||
route.handler(request,response,state);
|
||||
break;
|
||||
case "PUT":
|
||||
var data = "";
|
||||
request.on("data",function(chunk) {
|
||||
data += chunk.toString();
|
||||
});
|
||||
request.on("end",function() {
|
||||
state.data = data;
|
||||
route.handler(request,response,state);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
SimpleServer.prototype.listen = function(port,host) {
|
||||
return http.createServer(this.requestHandler.bind(this)).listen(port,host);
|
||||
};
|
||||
|
||||
var Command = function(params,commander,callback) {
|
||||
var self = this;
|
||||
this.params = params;
|
||||
this.commander = commander;
|
||||
this.callback = callback;
|
||||
// Set up server
|
||||
this.server = new SimpleServer({
|
||||
wiki: this.commander.wiki
|
||||
});
|
||||
// Add route handlers
|
||||
this.server.addRoute({
|
||||
method: "PUT",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
fields = JSON.parse(state.data);
|
||||
// Pull up any subfields in the `fields` object
|
||||
if(fields.fields) {
|
||||
$tw.utils.each(fields.fields,function(field,name) {
|
||||
fields[name] = field;
|
||||
});
|
||||
delete fields.fields;
|
||||
}
|
||||
// Remove any revision field
|
||||
if(fields.revision) {
|
||||
delete fields.revision;
|
||||
}
|
||||
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
|
||||
var changeCount = state.wiki.getChangeCount(title).toString();
|
||||
response.writeHead(204, "OK",{
|
||||
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "DELETE",
|
||||
path: /^\/bags\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]);
|
||||
state.wiki.deleteTiddler(title);
|
||||
response.writeHead(204, "OK", {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
|
||||
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/status$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var text = JSON.stringify({
|
||||
username: state.server.get("username"),
|
||||
space: {
|
||||
recipe: "default"
|
||||
},
|
||||
tiddlywiki_version: $tw.version
|
||||
});
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/favicon.ico$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "image/x-icon"});
|
||||
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
|
||||
response.end(buffer,"base64");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers.json$/,
|
||||
handler: function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
|
||||
var tiddlerFields = {};
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
if(name !== "text") {
|
||||
tiddlerFields[name] = tiddler.getFieldString(name);
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
}
|
||||
});
|
||||
this.server.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
|
||||
handler: function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title),
|
||||
tiddlerFields = {},
|
||||
knownFields = [
|
||||
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
|
||||
];
|
||||
if(tiddler) {
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
var value = tiddler.getFieldString(name);
|
||||
if(knownFields.indexOf(name) !== -1) {
|
||||
tiddlerFields[name] = value;
|
||||
} else {
|
||||
tiddlerFields.fields = tiddlerFields.fields || {};
|
||||
tiddlerFields.fields[name] = value;
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify(tiddlerFields),"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Command.prototype.execute = function() {
|
||||
if(!$tw.boot.wikiTiddlersPath) {
|
||||
$tw.utils.warning("Warning: Wiki folder '" + $tw.boot.wikiPath + "' does not exist or is missing a tiddlywiki.info file");
|
||||
}
|
||||
var port = this.params[0] || "8080",
|
||||
rootTiddler = this.params[1] || "$:/core/save/all",
|
||||
renderType = this.params[2] || "text/plain",
|
||||
serveType = this.params[3] || "text/html",
|
||||
username = this.params[4],
|
||||
password = this.params[5],
|
||||
host = this.params[6] || "127.0.0.1",
|
||||
pathprefix = this.params[7],
|
||||
debugLevel = this.params[8] || "none";
|
||||
if(parseInt(port,10).toString() !== port) {
|
||||
port = process.env[port] || 8080;
|
||||
}
|
||||
this.server.set({
|
||||
rootTiddler: rootTiddler,
|
||||
renderType: renderType,
|
||||
serveType: serveType,
|
||||
username: username,
|
||||
password: password,
|
||||
pathprefix: pathprefix,
|
||||
debugLevel: debugLevel
|
||||
// Set up server
|
||||
this.server = new Server({
|
||||
wiki: this.commander.wiki,
|
||||
variables: {
|
||||
port: this.params[0],
|
||||
host: this.params[6],
|
||||
"root-tiddler": this.params[1],
|
||||
"root-render-type": this.params[2],
|
||||
"root-serve-type": this.params[3],
|
||||
username: this.params[4],
|
||||
password: this.params[5],
|
||||
"path-prefix": this.params[7],
|
||||
"debug-level": this.params[8]
|
||||
}
|
||||
});
|
||||
var nodeServer = this.server.listen(port,host);
|
||||
$tw.utils.log("Serving on " + host + ":" + port,"brown/orange");
|
||||
$tw.utils.log("(press ctrl-C to exit)","red");
|
||||
// Warn if required plugins are missing
|
||||
if(!$tw.wiki.getTiddler("$:/plugins/tiddlywiki/tiddlyweb") || !$tw.wiki.getTiddler("$:/plugins/tiddlywiki/filesystem")) {
|
||||
$tw.utils.warning("Warning: Plugins required for client-server operation (\"tiddlywiki/filesystem\" and \"tiddlywiki/tiddlyweb\") are missing from tiddlywiki.info file");
|
||||
}
|
||||
$tw.hooks.invokeHook('th-server-command-post-start', this.server, nodeServer);
|
||||
var nodeServer = this.server.listen();
|
||||
$tw.hooks.invokeHook("th-server-command-post-start",this.server,nodeServer);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
94
core/modules/server/authenticators/basic.js
Normal file
94
core/modules/server/authenticators/basic.js
Normal file
@ -0,0 +1,94 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/authenticators/basic.js
|
||||
type: application/javascript
|
||||
module-type: authenticator
|
||||
|
||||
Authenticator for WWW basic authentication
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
if($tw.node) {
|
||||
var util = require("util"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
path = require("path");
|
||||
}
|
||||
|
||||
function BasicAuthenticator(server) {
|
||||
this.server = server;
|
||||
this.credentialsData = [];
|
||||
}
|
||||
|
||||
/*
|
||||
Returns true if the authenticator is active, false if it is inactive, or a string if there is an error
|
||||
*/
|
||||
BasicAuthenticator.prototype.init = function() {
|
||||
// Read the credentials data
|
||||
this.credentialsFilepath = this.server.get("credentials");
|
||||
if(this.credentialsFilepath) {
|
||||
var resolveCredentialsFilepath = path.resolve($tw.boot.wikiPath,this.credentialsFilepath);
|
||||
if(fs.existsSync(resolveCredentialsFilepath) && !fs.statSync(resolveCredentialsFilepath).isDirectory()) {
|
||||
var credentialsText = fs.readFileSync(resolveCredentialsFilepath,"utf8"),
|
||||
credentialsData = $tw.utils.parseCsvStringWithHeader(credentialsText);
|
||||
if(typeof credentialsData === "string") {
|
||||
return "Error: " + credentialsData + " reading credentials from '" + resolveCredentialsFilepath + "'";
|
||||
} else {
|
||||
this.credentialsData = credentialsData;
|
||||
}
|
||||
} else {
|
||||
return "Error: Unable to load user credentials from '" + credentialsFilepath + "'";
|
||||
}
|
||||
}
|
||||
// Add the hardcoded username and password if specified
|
||||
if(this.server.get("username") && this.server.get("password")) {
|
||||
this.credentialsData = this.credentialsData || [];
|
||||
this.credentialsData.push({
|
||||
username: this.server.get("username"),
|
||||
password: this.server.get("password")
|
||||
});
|
||||
}
|
||||
return this.credentialsData.length > 0;
|
||||
};
|
||||
|
||||
/*
|
||||
Returns true if the request is authenticated and assigns the "authenticatedUsername" state variable.
|
||||
Returns false if the request couldn't be authenticated having sent an appropriate response to the browser
|
||||
*/
|
||||
BasicAuthenticator.prototype.authenticateRequest = function(request,response,state) {
|
||||
// Extract the incoming username and password from the request
|
||||
var header = request.headers.authorization || "";
|
||||
if(!header && state.allowAnon) {
|
||||
// If there's no header and anonymous access is allowed then we don't set authenticatedUsername
|
||||
return true;
|
||||
}
|
||||
var token = header.split(/\s+/).pop() || "",
|
||||
auth = $tw.utils.base64Decode(token),
|
||||
parts = auth.split(/:/),
|
||||
incomingUsername = parts[0],
|
||||
incomingPassword = parts[1];
|
||||
// Check that at least one of the credentials matches
|
||||
var matchingCredentials = this.credentialsData.find(function(credential) {
|
||||
return credential.username === incomingUsername && credential.password === incomingPassword;
|
||||
});
|
||||
if(matchingCredentials) {
|
||||
// If so, add the authenticated username to the request state
|
||||
state.authenticatedUsername = incomingUsername;
|
||||
return true;
|
||||
} else {
|
||||
// If not, return an authentication challenge
|
||||
response.writeHead(401,"Authentication required",{
|
||||
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + state.server.servername + '"'
|
||||
});
|
||||
response.end();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
exports.AuthenticatorClass = BasicAuthenticator;
|
||||
|
||||
})();
|
47
core/modules/server/authenticators/header.js
Normal file
47
core/modules/server/authenticators/header.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/authenticators/header.js
|
||||
type: application/javascript
|
||||
module-type: authenticator
|
||||
|
||||
Authenticator for trusted header authentication
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
function HeaderAuthenticator(server) {
|
||||
this.server = server;
|
||||
this.header = server.get("authenticated-user-header");
|
||||
}
|
||||
|
||||
/*
|
||||
Returns true if the authenticator is active, false if it is inactive, or a string if there is an error
|
||||
*/
|
||||
HeaderAuthenticator.prototype.init = function() {
|
||||
return !!this.header;
|
||||
};
|
||||
|
||||
/*
|
||||
Returns true if the request is authenticated and assigns the "authenticatedUsername" state variable.
|
||||
Returns false if the request couldn't be authenticated having sent an appropriate response to the browser
|
||||
*/
|
||||
HeaderAuthenticator.prototype.authenticateRequest = function(request,response,state) {
|
||||
// Otherwise, authenticate as the username in the specified header
|
||||
var username = request.headers[this.header];
|
||||
if(!username && !state.allowAnon) {
|
||||
response.writeHead(401,"Authorization header required to login to '" + state.server.servername + "'");
|
||||
response.end();
|
||||
return false;
|
||||
} else {
|
||||
// authenticatedUsername will be undefined for anonymous users
|
||||
state.authenticatedUsername = username;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
exports.AuthenticatorClass = HeaderAuthenticator;
|
||||
|
||||
})();
|
28
core/modules/server/routes/delete-tiddler.js
Normal file
28
core/modules/server/routes/delete-tiddler.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/delete-tiddler.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
DELETE /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "DELETE";
|
||||
|
||||
exports.path = /^\/bags\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]);
|
||||
state.wiki.deleteTiddler(title);
|
||||
response.writeHead(204, "OK", {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
25
core/modules/server/routes/get-favicon.js
Normal file
25
core/modules/server/routes/get-favicon.js
Normal file
@ -0,0 +1,25 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-favicon.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /favicon.ico
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/favicon.ico$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "image/x-icon"});
|
||||
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
|
||||
response.end(buffer,"base64");
|
||||
};
|
||||
|
||||
}());
|
50
core/modules/server/routes/get-file.js
Normal file
50
core/modules/server/routes/get-file.js
Normal file
@ -0,0 +1,50 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-file.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /files/:filepath
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/files\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var path = require("path"),
|
||||
fs = require("fs"),
|
||||
util = require("util");
|
||||
var filename = path.resolve($tw.boot.wikiPath,"files",decodeURIComponent(state.params[0])),
|
||||
extension = path.extname(filename);
|
||||
fs.readFile(filename,function(err,content) {
|
||||
var status,content,type = "text/plain";
|
||||
if(err) {
|
||||
if(err.code === "ENOENT") {
|
||||
status = 404;
|
||||
content = "File '" + filename + "' not found";
|
||||
} else if(err.code === "EACCES") {
|
||||
status = 403;
|
||||
content = "You do not have permission to access the file '" + filename + "'";
|
||||
} else {
|
||||
status = 500;
|
||||
content = err.toString();
|
||||
}
|
||||
} else {
|
||||
status = 200;
|
||||
content = content;
|
||||
type = $tw.config.fileExtensionInfo[extension] || "application/octet-stream";
|
||||
}
|
||||
response.writeHead(status,{
|
||||
"Content-Type": type
|
||||
});
|
||||
response.end(content);
|
||||
});
|
||||
};
|
||||
|
||||
}());
|
25
core/modules/server/routes/get-index.js
Normal file
25
core/modules/server/routes/get-index.js
Normal file
@ -0,0 +1,25 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-index.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": state.server.get("root-serve-type")});
|
||||
var text = state.wiki.renderTiddler(state.server.get("root-render-type"),state.server.get("root-tiddler"));
|
||||
response.end(text,"utf8");
|
||||
};
|
||||
|
||||
}());
|
35
core/modules/server/routes/get-login-basic.js
Normal file
35
core/modules/server/routes/get-login-basic.js
Normal file
@ -0,0 +1,35 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-login-basic.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /login-basic -- force a Basic Authentication challenge
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/login-basic$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
if(!state.authenticatedUsername) {
|
||||
// Challenge if there's no username
|
||||
response.writeHead(401,{
|
||||
"WWW-Authenticate": 'Basic realm="Please provide your username and password to login to ' + state.server.servername + '"'
|
||||
});
|
||||
response.end();
|
||||
} else {
|
||||
// Redirect to the root wiki if login worked
|
||||
response.writeHead(302,{
|
||||
Location: "/"
|
||||
});
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
33
core/modules/server/routes/get-status.js
Normal file
33
core/modules/server/routes/get-status.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-status.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /status
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/status$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var text = JSON.stringify({
|
||||
username: state.authenticatedUsername || state.server.get("anon-username") || "",
|
||||
anonymous: !state.authenticatedUsername,
|
||||
read_only: !state.server.isAuthorized("writers",state.authenticatedUsername),
|
||||
space: {
|
||||
recipe: "default"
|
||||
},
|
||||
tiddlywiki_version: $tw.version
|
||||
});
|
||||
response.end(text,"utf8");
|
||||
};
|
||||
|
||||
}());
|
42
core/modules/server/routes/get-tiddler-html.js
Normal file
42
core/modules/server/routes/get-tiddler-html.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-tiddler-html.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/([^\/]+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title);
|
||||
if(tiddler) {
|
||||
var renderType,template;
|
||||
// Render ordinary tiddlers as HTML, and system tiddlers in plain text
|
||||
if(state.wiki.isSystemTiddler(title)) {
|
||||
renderType = state.server.get("system-tiddler-render-type");
|
||||
template = state.server.get("system-tiddler-template");
|
||||
} else {
|
||||
renderType = state.server.get("tiddler-render-type");
|
||||
template = state.server.get("tiddler-template");
|
||||
}
|
||||
var text = state.wiki.renderTiddler(renderType,template,{parseAsInline: true, variables: {currentTiddler: title}});
|
||||
// Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS
|
||||
response.writeHead(200);
|
||||
response.end(text,"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
46
core/modules/server/routes/get-tiddler.js
Normal file
46
core/modules/server/routes/get-tiddler.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-tiddler.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
tiddler = state.wiki.getTiddler(title),
|
||||
tiddlerFields = {},
|
||||
knownFields = [
|
||||
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
|
||||
];
|
||||
if(tiddler) {
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
var value = tiddler.getFieldString(name);
|
||||
if(knownFields.indexOf(name) !== -1) {
|
||||
tiddlerFields[name] = value;
|
||||
} else {
|
||||
tiddlerFields.fields = tiddlerFields.fields || {};
|
||||
tiddlerFields.fields[name] = value;
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
response.end(JSON.stringify(tiddlerFields),"utf8");
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
37
core/modules/server/routes/get-tiddlers-json.js
Normal file
37
core/modules/server/routes/get-tiddlers-json.js
Normal file
@ -0,0 +1,37 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/get-tiddlers-json.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
GET /recipes/default/tiddlers/tiddlers.json
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/recipes\/default\/tiddlers.json$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
response.writeHead(200, {"Content-Type": "application/json"});
|
||||
var tiddlers = [];
|
||||
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
|
||||
var tiddlerFields = {};
|
||||
$tw.utils.each(tiddler.fields,function(field,name) {
|
||||
if(name !== "text") {
|
||||
tiddlerFields[name] = tiddler.getFieldString(name);
|
||||
}
|
||||
});
|
||||
tiddlerFields.revision = state.wiki.getChangeCount(title);
|
||||
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
|
||||
tiddlers.push(tiddlerFields);
|
||||
});
|
||||
var text = JSON.stringify(tiddlers);
|
||||
response.end(text,"utf8");
|
||||
};
|
||||
|
||||
}());
|
42
core/modules/server/routes/put-tiddler.js
Normal file
42
core/modules/server/routes/put-tiddler.js
Normal file
@ -0,0 +1,42 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/routes/put-tiddler.js
|
||||
type: application/javascript
|
||||
module-type: route
|
||||
|
||||
PUT /recipes/default/tiddlers/:title
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "PUT";
|
||||
|
||||
exports.path = /^\/recipes\/default\/tiddlers\/(.+)$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var title = decodeURIComponent(state.params[0]),
|
||||
fields = JSON.parse(state.data);
|
||||
// Pull up any subfields in the `fields` object
|
||||
if(fields.fields) {
|
||||
$tw.utils.each(fields.fields,function(field,name) {
|
||||
fields[name] = field;
|
||||
});
|
||||
delete fields.fields;
|
||||
}
|
||||
// Remove any revision field
|
||||
if(fields.revision) {
|
||||
delete fields.revision;
|
||||
}
|
||||
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
|
||||
var changeCount = state.wiki.getChangeCount(title).toString();
|
||||
response.writeHead(204, "OK",{
|
||||
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
252
core/modules/server/server.js
Normal file
252
core/modules/server/server.js
Normal file
@ -0,0 +1,252 @@
|
||||
/*\
|
||||
title: $:/core/modules/server/server.js
|
||||
type: application/javascript
|
||||
module-type: library
|
||||
|
||||
Serve tiddlers over http
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
if($tw.node) {
|
||||
var util = require("util"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
path = require("path");
|
||||
}
|
||||
|
||||
/*
|
||||
A simple HTTP server with regexp-based routes
|
||||
options: variables - optional hashmap of variables to set (a misnomer - they are really constant parameters)
|
||||
routes - optional array of routes to use
|
||||
wiki - reference to wiki object
|
||||
*/
|
||||
function Server(options) {
|
||||
var self = this;
|
||||
this.routes = options.routes || [];
|
||||
this.authenticators = options.authenticators || [];
|
||||
this.wiki = options.wiki;
|
||||
this.servername = this.wiki.getTiddlerText("$:/SiteTitle") || "TiddlyWiki5";
|
||||
// Initialise the variables
|
||||
this.variables = $tw.utils.extend({},this.defaultVariables);
|
||||
if(options.variables) {
|
||||
for(var variable in options.variables) {
|
||||
if(options.variables[variable]) {
|
||||
this.variables[variable] = options.variables[variable];
|
||||
}
|
||||
}
|
||||
}
|
||||
$tw.utils.extend({},this.defaultVariables,options.variables);
|
||||
// Initialise CSRF
|
||||
this.csrfDisable = this.get("csrf-disable") === "yes";
|
||||
// Initialise authorization
|
||||
var authorizedUserName = (this.get("username") && this.get("password")) ? this.get("username") : "(anon)";
|
||||
this.authorizationPrincipals = {
|
||||
readers: (this.get("readers") || authorizedUserName).split(",").map($tw.utils.trim),
|
||||
writers: (this.get("writers") || authorizedUserName).split(",").map($tw.utils.trim)
|
||||
}
|
||||
// Load and initialise authenticators
|
||||
$tw.modules.forEachModuleOfType("authenticator", function(title,authenticatorDefinition) {
|
||||
// console.log("Loading server route " + title);
|
||||
self.addAuthenticator(authenticatorDefinition.AuthenticatorClass);
|
||||
});
|
||||
// Load route handlers
|
||||
$tw.modules.forEachModuleOfType("route", function(title,routeDefinition) {
|
||||
// console.log("Loading server route " + title);
|
||||
self.addRoute(routeDefinition);
|
||||
});
|
||||
// Initialise the http vs https
|
||||
this.listenOptions = null;
|
||||
this.protocol = "http";
|
||||
var tlsKeyFilepath = this.get("tls-key"),
|
||||
tlsCertFilepath = this.get("tls-cert");
|
||||
if(tlsCertFilepath && tlsKeyFilepath) {
|
||||
this.listenOptions = {
|
||||
key: fs.readFileSync(path.resolve($tw.boot.wikiPath,tlsKeyFilepath),"utf8"),
|
||||
cert: fs.readFileSync(path.resolve($tw.boot.wikiPath,tlsCertFilepath),"utf8")
|
||||
};
|
||||
this.protocol = "https";
|
||||
}
|
||||
this.transport = require(this.protocol);
|
||||
}
|
||||
|
||||
Server.prototype.defaultVariables = {
|
||||
port: "8080",
|
||||
host: "127.0.0.1",
|
||||
"root-tiddler": "$:/core/save/all",
|
||||
"root-render-type": "text/plain",
|
||||
"root-serve-type": "text/html",
|
||||
"tiddler-render-type": "text/html",
|
||||
"tiddler-template": "$:/core/templates/server/static.tiddler.html",
|
||||
"system-tiddler-render-type": "text/plain",
|
||||
"system-tiddler-template": "$:/core/templates/wikified-tiddler",
|
||||
"debug-level": "none"
|
||||
};
|
||||
|
||||
Server.prototype.get = function(name) {
|
||||
return this.variables[name];
|
||||
};
|
||||
|
||||
Server.prototype.addRoute = function(route) {
|
||||
this.routes.push(route);
|
||||
};
|
||||
|
||||
Server.prototype.addAuthenticator = function(AuthenticatorClass) {
|
||||
// Instantiate and initialise the authenticator
|
||||
var authenticator = new AuthenticatorClass(this),
|
||||
result = authenticator.init();
|
||||
if(typeof result === "string") {
|
||||
$tw.utils.error("Error: " + result);
|
||||
} else if(result) {
|
||||
// Only use the authenticator if it initialised successfully
|
||||
this.authenticators.push(authenticator);
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype.findMatchingRoute = function(request,state) {
|
||||
var pathprefix = this.get("path-prefix") || "";
|
||||
for(var t=0; t<this.routes.length; t++) {
|
||||
var potentialRoute = this.routes[t],
|
||||
pathRegExp = potentialRoute.path,
|
||||
pathname = state.urlInfo.pathname,
|
||||
match;
|
||||
if(pathprefix) {
|
||||
if(pathname.substr(0,pathprefix.length) === pathprefix) {
|
||||
pathname = pathname.substr(pathprefix.length) || "/";
|
||||
match = potentialRoute.path.exec(pathname);
|
||||
} else {
|
||||
match = false;
|
||||
}
|
||||
} else {
|
||||
match = potentialRoute.path.exec(pathname);
|
||||
}
|
||||
if(match && request.method === potentialRoute.method) {
|
||||
state.params = [];
|
||||
for(var p=1; p<match.length; p++) {
|
||||
state.params.push(match[p]);
|
||||
}
|
||||
return potentialRoute;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
Server.prototype.methodMappings = {
|
||||
"GET": "readers",
|
||||
"OPTIONS": "readers",
|
||||
"HEAD": "readers",
|
||||
"PUT": "writers",
|
||||
"POST": "writers",
|
||||
"DELETE": "writers"
|
||||
};
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
Server.prototype.isAuthorized = function(authorizationType,username) {
|
||||
var principals = this.authorizationPrincipals[authorizationType] || [];
|
||||
return principals.indexOf("(anon)") !== -1 || (username && (principals.indexOf("(authenticated)") !== -1 || principals.indexOf(username) !== -1));
|
||||
}
|
||||
|
||||
Server.prototype.requestHandler = function(request,response) {
|
||||
// Compose the state object
|
||||
var self = this;
|
||||
var state = {};
|
||||
state.wiki = self.wiki;
|
||||
state.server = self;
|
||||
state.urlInfo = url.parse(request.url);
|
||||
// Get the principals authorized to access this resource
|
||||
var authorizationType = this.methodMappings[request.method] || "readers";
|
||||
// Check for the CSRF header if this is a write
|
||||
if(!this.csrfDisable && authorizationType === "writers" && request.headers["x-requested-with"] !== "TiddlyWiki") {
|
||||
response.writeHead(403,"'X-Requested-With' header required to login to '" + this.servername + "'");
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Check whether anonymous access is granted
|
||||
state.allowAnon = this.isAuthorized(authorizationType,null);
|
||||
// Authenticate with the first active authenticator
|
||||
if(this.authenticators.length > 0) {
|
||||
if(!this.authenticators[0].authenticateRequest(request,response,state)) {
|
||||
// Bail if we failed (the authenticator will have sent the response)
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Authorize with the authenticated username
|
||||
if(!this.isAuthorized(authorizationType,state.authenticatedUsername)) {
|
||||
response.writeHead(401,"'" + state.authenticatedUsername + "' is not authorized to access '" + this.servername + "'");
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Find the route that matches this path
|
||||
var route = self.findMatchingRoute(request,state);
|
||||
// Optionally output debug info
|
||||
if(self.get("debug-level") !== "none") {
|
||||
console.log("Request path:",JSON.stringify(state.urlInfo));
|
||||
console.log("Request headers:",JSON.stringify(request.headers));
|
||||
console.log("authenticatedUsername:",state.authenticatedUsername);
|
||||
}
|
||||
// Return a 404 if we didn't find a route
|
||||
if(!route) {
|
||||
response.writeHead(404);
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
// Set the encoding for the incoming request
|
||||
// TODO: Presumably this would need tweaking if we supported PUTting binary tiddlers
|
||||
request.setEncoding("utf8");
|
||||
// Dispatch the appropriate method
|
||||
switch(request.method) {
|
||||
case "GET": // Intentional fall-through
|
||||
case "DELETE":
|
||||
route.handler(request,response,state);
|
||||
break;
|
||||
case "PUT":
|
||||
var data = "";
|
||||
request.on("data",function(chunk) {
|
||||
data += chunk.toString();
|
||||
});
|
||||
request.on("end",function() {
|
||||
state.data = data;
|
||||
route.handler(request,response,state);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Listen for requests
|
||||
port: optional port number (falls back to value of "port" variable)
|
||||
host: optional host address (falls back to value of "hist" variable)
|
||||
*/
|
||||
Server.prototype.listen = function(port,host) {
|
||||
// Handle defaults for port and host
|
||||
port = port || this.get("port");
|
||||
host = host || this.get("host");
|
||||
// Check for the port being a string and look it up as an environment variable
|
||||
if(parseInt(port,10).toString() !== port) {
|
||||
port = process.env[port] || 8080;
|
||||
}
|
||||
$tw.utils.log("Serving on " + this.protocol + "://" + host + ":" + port,"brown/orange");
|
||||
$tw.utils.log("(press ctrl-C to exit)","red");
|
||||
// Warn if required plugins are missing
|
||||
if(!$tw.wiki.getTiddler("$:/plugins/tiddlywiki/tiddlyweb") || !$tw.wiki.getTiddler("$:/plugins/tiddlywiki/filesystem")) {
|
||||
$tw.utils.warning("Warning: Plugins required for client-server operation (\"tiddlywiki/filesystem\" and \"tiddlywiki/tiddlyweb\") are missing from tiddlywiki.info file");
|
||||
}
|
||||
// Listen
|
||||
var server;
|
||||
if(this.listenOptions) {
|
||||
server = this.transport.createServer(this.listenOptions,this.requestHandler.bind(this));
|
||||
} else {
|
||||
server = this.transport.createServer(this.requestHandler.bind(this));
|
||||
}
|
||||
return server.listen(port,host);
|
||||
};
|
||||
|
||||
exports.Server = Server;
|
||||
|
||||
})();
|
@ -16,6 +16,8 @@ The syncer tracks changes to the store. If a syncadaptor is used then individual
|
||||
Defaults
|
||||
*/
|
||||
Syncer.prototype.titleIsLoggedIn = "$:/status/IsLoggedIn";
|
||||
Syncer.prototype.titleIsAnonymous = "$:/status/IsAnonymous";
|
||||
Syncer.prototype.titleIsReadOnly = "$:/status/IsReadOnly";
|
||||
Syncer.prototype.titleUserName = "$:/status/UserName";
|
||||
Syncer.prototype.titleSyncFilter = "$:/config/SyncFilter";
|
||||
Syncer.prototype.titleSavedNotification = "$:/language/Notifications/Save/Done";
|
||||
@ -169,12 +171,14 @@ Syncer.prototype.getStatus = function(callback) {
|
||||
// Mark us as not logged in
|
||||
this.wiki.addTiddler({title: this.titleIsLoggedIn,text: "no"});
|
||||
// Get login status
|
||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username) {
|
||||
this.syncadaptor.getStatus(function(err,isLoggedIn,username,isReadOnly,isAnonymous) {
|
||||
if(err) {
|
||||
self.logger.alert(err);
|
||||
return;
|
||||
}
|
||||
// Set the various status tiddlers
|
||||
self.wiki.addTiddler({title: self.titleIsReadOnly,text: isReadOnly ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsAnonymous,text: isAnonymous ? "yes" : "no"});
|
||||
self.wiki.addTiddler({title: self.titleIsLoggedIn,text: isLoggedIn ? "yes" : "no"});
|
||||
if(isLoggedIn) {
|
||||
self.wiki.addTiddler({title: self.titleUserName,text: username || ""});
|
||||
|
46
core/modules/utils/csv.js
Normal file
46
core/modules/utils/csv.js
Normal file
@ -0,0 +1,46 @@
|
||||
/*\
|
||||
title: $:/core/modules/utils/csv.js
|
||||
type: application/javascript
|
||||
module-type: utils
|
||||
|
||||
A barebones CSV parser
|
||||
|
||||
\*/
|
||||
(function(){
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Parse a CSV string with a header row and return an array of hashmaps.
|
||||
*/
|
||||
exports.parseCsvStringWithHeader = function(text,options) {
|
||||
options = options || {};
|
||||
var separator = options.separator || ",",
|
||||
rows = text.split(/\r?\n/mg).map(function(row) {
|
||||
return $tw.utils.trim(row);
|
||||
}).filter(function(row) {
|
||||
return row !== "";
|
||||
});
|
||||
if(rows.length < 1) {
|
||||
return "Missing header row";
|
||||
}
|
||||
var headings = rows[0].split(separator),
|
||||
results = [];
|
||||
for(var row=1; row<rows.length; row++) {
|
||||
var columns = rows[row].split(separator),
|
||||
columnResult = Object.create(null);
|
||||
if(columns.length !== headings.length) {
|
||||
return "Malformed CSV row '" + rows[row] + "'";
|
||||
}
|
||||
for(var column=0; column<columns.length; column++) {
|
||||
var columnName = headings[column];
|
||||
columnResult[columnName] = $tw.utils.trim(columns[column] || "");
|
||||
}
|
||||
results.push(columnResult);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
})();
|
@ -60,6 +60,9 @@ exports.httpRequest = function(options) {
|
||||
if(data && !$tw.utils.hop(headers,"Content-type")) {
|
||||
request.setRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8");
|
||||
}
|
||||
if(!$tw.utils.hop(headers,"X-Requested-With")) {
|
||||
request.setRequestHeader("X-Requested-With","TiddlyWiki");
|
||||
}
|
||||
try {
|
||||
request.send(data);
|
||||
} catch(e) {
|
||||
|
30
core/templates/server/static.sidebar.wikitext.tid
Normal file
30
core/templates/server/static.sidebar.wikitext.tid
Normal file
@ -0,0 +1,30 @@
|
||||
title: $:/core/templates/server/static.sidebar.wikitext
|
||||
|
||||
\whitespace trim
|
||||
<div class="tc-sidebar-scrollable" style="overflow: auto;">
|
||||
<div class="tc-sidebar-header">
|
||||
<h1 class="tc-site-title">
|
||||
<$transclude tiddler="$:/SiteTitle"/>
|
||||
</h1>
|
||||
<div class="tc-site-subtitle">
|
||||
<$transclude tiddler="$:/SiteSubtitle"/>
|
||||
</div>
|
||||
<h2>
|
||||
</h2>
|
||||
<div class="tc-sidebar-lists">
|
||||
<$list filter={{$:/DefaultTiddlers}}>
|
||||
<div class="tc-menu-list-subitem">
|
||||
<$link><$text text=<<currentTiddler>>/></$link>
|
||||
</div>
|
||||
</$list>
|
||||
</div>
|
||||
<!-- Currently disabled the recent list as it is unweildy when the responsive narrow view kicks in
|
||||
<h2>
|
||||
{{$:/language/SideBar/Recent/Caption}}
|
||||
</h2>
|
||||
<div class="tc-sidebar-lists">
|
||||
<$macrocall $name="timeline" format={{$:/language/RecentChanges/DateFormat}}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
29
core/templates/server/static.tiddler.html.tid
Normal file
29
core/templates/server/static.tiddler.html.tid
Normal file
@ -0,0 +1,29 @@
|
||||
title: $:/core/templates/server/static.tiddler.html
|
||||
|
||||
\whitespace trim
|
||||
\define tv-wikilink-template() $uri_encoded$
|
||||
<$importvariables filter="[[$:/core/ui/PageMacros]] [all[shadows+tiddlers]tag[$:/tags/Macro]!has[draft.of]]">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||
<meta name="generator" content="TiddlyWiki" />
|
||||
<meta name="tiddlywiki-version" content={{$:/core/templates/version}} />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="mobile-web-app-capable" content="yes"/>
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<link id="faviconLink" rel="shortcut icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="%24%3A%2Fcore%2Ftemplates%2Fstatic.template.css">
|
||||
<title><$view field="caption" format="plainwikified"><$view field="title"/></$view>: <$view tiddler="$:/core/wiki/title" format="plainwikified"/></title>
|
||||
</head>
|
||||
<body class="tc-body">
|
||||
<$transclude tiddler="$:/core/templates/server/static.sidebar.wikitext" mode="inline"/>
|
||||
<section class="tc-story-river">
|
||||
<div class="tc-tiddler-frame">
|
||||
<$transclude tiddler="$:/core/templates/server/static.tiddler.wikitext" mode="inline"/>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
</$importvariables>
|
23
core/templates/server/static.tiddler.wikitext.tid
Normal file
23
core/templates/server/static.tiddler.wikitext.tid
Normal file
@ -0,0 +1,23 @@
|
||||
title: $:/core/templates/server/static.tiddler.wikitext
|
||||
|
||||
\whitespace trim
|
||||
<div class="tc-tiddler-title">
|
||||
<div class="tc-titlebar">
|
||||
<h2><$text text=<<currentTiddler>>/></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tc-subtitle">
|
||||
<$link to={{!!modifier}}>
|
||||
<$view field="modifier"/>
|
||||
</$link> <$view field="modified" format="date" template={{$:/language/Tiddler/DateFormat}}/>
|
||||
</div>
|
||||
<div class="tc-tags-wrapper">
|
||||
<$list filter="[all[current]tags[]sort[title]]">
|
||||
<a href={{{ [<currentTiddler>encodeuricomponent[]] }}}>
|
||||
<$macrocall $name="tag-pill" tag=<<currentTiddler>>/>
|
||||
</a>
|
||||
</$list>
|
||||
</div>
|
||||
<div class="tc-tiddler-body">
|
||||
<$transclude mode="block"/>
|
||||
</div>
|
@ -1,8 +1,9 @@
|
||||
created: 20150117174359000
|
||||
modified: 20150117205257000
|
||||
title: Commands
|
||||
modified: 20180626122309578
|
||||
tags: Concepts Reference
|
||||
title: Commands
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
A <<.def command>> is one of the following words, written with a `--` prefix and used as a command-line option to [[TiddlyWiki on Node.js]], indicating which action is desired.
|
||||
A <<.def command>> is one of the following words, written with a `--` prefix and used as a command-line option under Node.js, indicating which action is desired. See [[Using TiddlyWiki on Node.js]] for details of how to use them.
|
||||
|
||||
<<list-links "[tag[Commands]]">>
|
||||
|
10
editions/tw5.com/tiddlers/commands/ListenCommand.tid
Normal file
10
editions/tw5.com/tiddlers/commands/ListenCommand.tid
Normal file
@ -0,0 +1,10 @@
|
||||
caption: listen
|
||||
created: 20180626135301279
|
||||
modified: 20180701171046122
|
||||
tags: Commands
|
||||
title: ListenCommand
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
<<.from-version "5.1.18">> See WebServer for details of TiddlyWiki's web server functionality.
|
||||
|
||||
{{$:/language/Help/listen}}
|
@ -0,0 +1,22 @@
|
||||
created: 20180626122427188
|
||||
modified: 20180626134639673
|
||||
tags: [[Using TiddlyWiki on Node.js]]
|
||||
title: NamedCommandParameters
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
<<.from-version "5.1.18">> Most TiddlyWiki [[Commands]] use a position-based system for their parameters where each parameter must be listed in the precise order defined by the command. Some of the more complex commands offer an alternative scheme of named command parameters. For example, here we provide two parameters named "port" and "host":
|
||||
|
||||
```
|
||||
--listen port=8090 host=0.0.0.0
|
||||
```
|
||||
|
||||
Note that the order of the parameters does not matter.
|
||||
|
||||
Using special characters within a parameter requires quoting. Unix, Linux and the Mac use single quotes, and Windows uses double quotes:
|
||||
|
||||
```
|
||||
--listen port=8090 username=joe 'password=s3cret(!'
|
||||
--listen port=8090 username=joe "password=s3cret(!"
|
||||
```
|
||||
|
||||
Note that the quotes are applied to the entire name=value pair, not just to the value part.
|
@ -1,8 +1,12 @@
|
||||
caption: server
|
||||
created: 20131219163923630
|
||||
modified: 20131229130513478
|
||||
modified: 20180626150505679
|
||||
tags: Commands
|
||||
title: ServerCommand
|
||||
type: text/vnd.tiddlywiki
|
||||
caption: server
|
||||
|
||||
''Note that the `--server` command is now deprecated in favour of the new ListenCommand''.
|
||||
|
||||
See WebServer for details of TiddlyWiki's web server functionality.
|
||||
|
||||
{{$:/language/Help/server}}
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20141126153016142
|
||||
modified: 20160622141441383
|
||||
modified: 20180701185730340
|
||||
tags: Community
|
||||
title: Translate TiddlyWiki into your language
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -11,6 +11,6 @@ There is a special edition of TiddlyWiki that simplifies creating and maintainin
|
||||
|
||||
Note that no knowledge of Node.js or GitHub is required.
|
||||
|
||||
You can translate ~TiddlyWiki on Node.js, type `tiddlywiki editions/translators --server` and visit http://127.0.0.1:8080/ in your browser.
|
||||
You can translate ~TiddlyWiki on Node.js, type `tiddlywiki editions/translators --listen` and visit http://127.0.0.1:8080/ in your browser.
|
||||
|
||||
See https://tiddlywiki.com/dev for technical details of creating and maintaining translations.
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20140206214608586
|
||||
modified: 20151105122712982
|
||||
modified: 20180701185417525
|
||||
tags: Features
|
||||
title: LazyLoading
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -19,17 +19,13 @@ To start TiddlyWiki with lazy loading for image tiddlers use this command:
|
||||
|
||||
|
||||
```
|
||||
tiddlywiki --server 8080 $:/core/save/lazy-images
|
||||
tiddlywiki --listen root-tiddler=$:/core/save/lazy-images
|
||||
```
|
||||
|
||||
To apply lazy loading to all non-system tiddlers use this command:
|
||||
|
||||
|
||||
```
|
||||
tiddlywiki --server 8080 $:/core/save/lazy-all
|
||||
tiddlywiki --listen root-tiddler=$:/core/save/lazy-all
|
||||
```
|
||||
|
||||
! Lazy loading under TiddlyWeb
|
||||
|
||||
With the current configuration, lazy loading is enabled by default.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20150926162849519
|
||||
modified: 20161215160531991
|
||||
modified: 20180701185329863
|
||||
tags: [[Installing TiddlyWiki on Node.js]]
|
||||
title: Installing TiddlyWiki Prerelease on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -7,7 +7,7 @@ type: text/vnd.tiddlywiki
|
||||
# Clone a local copy of the TiddlyWiki5 GitHub repository from https://github.com/Jermolene/TiddlyWiki5
|
||||
# Open a command line terminal and change the current working directory to the root of the TiddlyWiki5 repo
|
||||
# Type `npm link` (Windows) or `sudo npm link` (Mac/Linux) to tell [[npm]] to use this copy of the repo as the globally installed one
|
||||
# Inside the root, you can launch ~TiddlyWiki like this: <br/>``tiddlywiki editions/tw5.com-server --server 8080 $:/core/save/all text/plain text/html``
|
||||
# Inside the root, you can launch ~TiddlyWiki like this: <br/>``tiddlywiki editions/tw5.com-server --listen``
|
||||
|
||||
After this procedure you can work with TiddlyWiki5 via [[npm]] as though it had been installed in the usual way with `npm install -g tiddlywiki`.
|
||||
|
||||
|
@ -3,7 +3,7 @@ created: 20131219100608529
|
||||
delivery: DIY
|
||||
description: Flexible hosting on your own machine or in the cloud
|
||||
method: sync
|
||||
modified: 20171115174105090
|
||||
modified: 20180701185303780
|
||||
tags: Saving [[TiddlyWiki on Node.js]] Windows Mac Linux
|
||||
title: Installing TiddlyWiki on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -20,7 +20,7 @@ type: text/vnd.tiddlywiki
|
||||
# In response, you should see TiddlyWiki report its current version (eg "<<version>>"; you may also see other debugging information reported)
|
||||
# Try it out:
|
||||
## `tiddlywiki mynewwiki --init server` to create a folder for a new wiki that includes server-related components
|
||||
## `tiddlywiki mynewwiki --server` to start TiddlyWiki
|
||||
## `tiddlywiki mynewwiki --listen` to start TiddlyWiki
|
||||
## Visit http://127.0.0.1:8080/ in your browser
|
||||
## Try editing and creating tiddlers
|
||||
# Optionally, make an offline copy:
|
||||
|
@ -1,10 +1,10 @@
|
||||
created: 20131219100520659
|
||||
modified: 20140920135025757
|
||||
modified: 20180626122347768
|
||||
tags: [[TiddlyWiki on Node.js]]
|
||||
title: Using TiddlyWiki on Node.js
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
TiddlyWiki5 can be used on the command line to perform an extensive set of operations based on TiddlyWikiFolders, TiddlerFiles and TiddlyWikiFiles.
|
||||
TiddlyWiki5 includes a set of [[Commands]] for use on the command line to perform an extensive set of operations based on TiddlyWikiFolders, TiddlerFiles and TiddlyWikiFiles.
|
||||
|
||||
For example, the following command loads the tiddlers from a TiddlyWiki HTML file and then saves one of them in static HTML:
|
||||
|
||||
@ -22,6 +22,10 @@ The commands and their individual arguments follow, each command being identifie
|
||||
tiddlywiki [<wikipath>] [--<command> [<arg>[,<arg>]]]
|
||||
```
|
||||
|
||||
The available commands are:
|
||||
<<.from-version "5.1.18">> Commands such as the ListenCommand that support large numbers of parameters can use NamedCommandParameters to make things less unwieldy. For example:
|
||||
|
||||
<<list-links "[tag[Commands]]">>
|
||||
```
|
||||
tiddlywiki wikipath --listen username=jeremy port=8090
|
||||
```
|
||||
|
||||
See [[Commands]] for a full listing of the available commands.
|
||||
|
@ -15,7 +15,7 @@
|
||||
stdoutLogFile=".\node.log"
|
||||
startupTimeLimit="20"
|
||||
processPath="C:\Program Files\nodejs\node.exe"
|
||||
arguments=".\node_modules\tiddlywiki\tiddlywiki.js ./wiki --server PORT $:/core/save/all text/plain text/html "" "" 127.0.0.1 /MyApp">
|
||||
arguments=".\node_modules\tiddlywiki\tiddlywiki.js ./wiki --listen port=PORT path-prefix=/MyApp">
|
||||
<environmentVariables>
|
||||
<environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
|
||||
<environmentVariable name="NODE_ENV" value="Production" />
|
||||
|
@ -1,5 +1,5 @@
|
||||
created: 20180328145259455
|
||||
modified: 20180328151038658
|
||||
modified: 20180701185215523
|
||||
tags:
|
||||
title: Example web.config for IIS
|
||||
type: text/plain
|
||||
|
@ -3,7 +3,7 @@ created: 20180328120356008
|
||||
delivery: DIY
|
||||
description: Windows' built-in web server
|
||||
method: sync
|
||||
modified: 20180328151441294
|
||||
modified: 20180701185718671
|
||||
tags: Saving [[TiddlyWiki on Node.js]] Windows
|
||||
title: Installing TiddlyWiki on Microsoft Internet Information Server
|
||||
type: text/vnd.tiddlywiki
|
||||
@ -61,8 +61,8 @@ Test the app by visiting http://localhost/MyApp/ in a browser.
|
||||
|
||||
! Notes
|
||||
|
||||
* If you require authentication, specify a username and password in the `--server` command in `web.config`. For example:
|
||||
** `arguments=".\node_modules\tiddlywiki\tiddlywiki.js ./wiki-server --server PORT $:/core/save/all text/plain text/html "username" "password" 127.0.0.1 /MyApp">`
|
||||
** Take note of the need to HTML encode the double quotes around the username and password into `"`
|
||||
* If you require authentication, specify a username and password in the `--listen` command in `web.config`. For example:
|
||||
** `arguments=".\node_modules\tiddlywiki\tiddlywiki.js ./wiki-server --listen username=joe "password=bloggs" port=PORT path-prefix=/MyApp">`
|
||||
** Take note of the need to use double quotes around non-alphanumeric passwords, and to HTML encode them into `"`
|
||||
* If you change the settings in the `web.config` file, or modify the app code, then you'll need to restart the server using the IIS manager application
|
||||
|
||||
|
19
editions/tw5.com/tiddlers/webserver/Using HTTPS.tid
Normal file
19
editions/tw5.com/tiddlers/webserver/Using HTTPS.tid
Normal file
@ -0,0 +1,19 @@
|
||||
created: 20180702160923664
|
||||
modified: 20180703100549667
|
||||
tags: [[WebServer Guides]]
|
||||
title: Using HTTPS
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
By default, TiddlyWiki's WebServer serves resources over the insecure HTTP protocol. The risk is minimal if it is only being used within a private, trusted network but in many situations it is desirable to use a secure HTTPS connection.
|
||||
|
||||
HTTPS requires the server to be configured with a certificate via a "cert" file and a "key" file, configured via the [[tls-cert|WebServer Parameter: tls-cert]] and [[tls-key|WebServer Parameter: tls-key]] parameters
|
||||
|
||||
Certificates can be obtained from a certification authority such as https://letsencrypt.org/, or you can create a self-signed certificate for internal testing.
|
||||
|
||||
To create the required certificate files with the popular [[openssl|https://www.openssl.org/]] utility:
|
||||
|
||||
```
|
||||
openssl req -newkey rsa:2048 -new -nodes -keyout mywikifolder/key.pem -out mywikifolder/csr.pem
|
||||
openssl x509 -req -days 365 -in mywikifolder/csr.pem -signkey mywikifolder/key.pem -out mywikifolder/server.crt
|
||||
tiddlywiki mywikifolder --listen username=joe password=bloggs tlskey=key.pem tlscert=server.crt
|
||||
```
|
@ -0,0 +1,16 @@
|
||||
created: 20180703095630828
|
||||
modified: 20180703100445719
|
||||
tags: [[WebServer Guides]]
|
||||
title: Using the integrated static file server
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Any files in the subfolder `files` of the wiki folder will be available via the route `\files\<uri-encoded-filename>`. For example: http://127.0.0.1:8080/files/Motovun%20Jack.jpg
|
||||
|
||||
This can be useful for publishing large files that you don't want to incorporate into the main wiki (PDFs, videos, large images, etc.).
|
||||
|
||||
Static files can be referenced directly:
|
||||
|
||||
* `[ext[./files/a-big-document.pdf]]` - to make a link to a PDF
|
||||
* `[img[./files/a-big-image.png]]` - to embed an image
|
||||
|
||||
Alternatively, the ''_canonical_uri'' field can be used to reference the files as [[external tiddlers|ExternalImages]].
|
@ -0,0 +1,7 @@
|
||||
created: 20180703095435813
|
||||
modified: 20180703100525994
|
||||
tags: [[WebServer Guides]]
|
||||
title: Using the read-only single tiddler view
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
As well as serving the full interactive wiki at the path `/` (e.g. http://127.0.0.1:8080/), TiddlyWiki also serves an experimental single tiddler per page, read-only view of the wiki at the path `/<url-encoded-tiddler-title>` (e.g. http://127.0.0.1:8080/HelloThere). It uses a simplified page layout, and implements links between tiddlers, but there are no other interactive features.
|
@ -0,0 +1,8 @@
|
||||
created: 20180701175139654
|
||||
modified: 20180701202339421
|
||||
tags: [[WebServer Authentication]]
|
||||
title: WebServer Anonymous Access
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Anonymous access is only permitted if the special ''(anon)'' token is present in the [[readers|WebServer Parameter: readers]] (for reading) and optionally [[writers|WebServer Parameter: writers]] (for writing) authorization parameters.
|
||||
|
@ -0,0 +1,11 @@
|
||||
created: 20180630193939007
|
||||
modified: 20180701184519624
|
||||
tags: WebServer
|
||||
title: WebServer Authentication
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
''Authentication'' is the process of identifying the current user. TiddlyWiki supports three types of authentication:
|
||||
|
||||
* [[Anonymous Access|WebServer Anonymous Access]] allows any user to access resources without requiring authentication. Optionally, a username can still be specified for signing edits
|
||||
* [[Basic Authentication|WebServer Basic Authentication]] requires the user to enter a username and password combination which TiddlyWiki validates against an internal database of credentials
|
||||
* [[Header Authentication|WebServer Header Authentication]] requires an external proxy to place the username of the current user in a trusted header of the request. It is often used as the basis of "single sign-on" features
|
@ -0,0 +1,30 @@
|
||||
created: 20180630194006239
|
||||
modified: 20180701174944267
|
||||
tags: WebServer
|
||||
title: WebServer Authorization
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
''Authorization'' is the process of determining which resources may be accessed by a particular user. It occurs after [[authentication|WebServer Authentication]] has determined the identity of the user. TiddlyWiki's WebServer implements a simple authorization scheme which permits independent control of who has read and write access to a wiki.
|
||||
|
||||
The WebServer parameters [[readers|WebServer Parameter: readers]] and [[writers|WebServer Parameter: writers]] each contain a comma separated list of //principals// (which is to say, either usernames or certain special tokens) which should have read or write access respectively.
|
||||
|
||||
The available special tokens are:
|
||||
|
||||
* ''(anon)'' - indicates all anonymous users
|
||||
* ''(authenticated)'' - indicates all authenticated users
|
||||
|
||||
!! Examples
|
||||
|
||||
These example use the [[credentials|WebServer Parameter: credentials]] parameter to specify the location of a file containing usernames and passwords.
|
||||
|
||||
In the first example, read access is permitted for the users "joe" and "mary", with write access restricted to "mary":
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen credentials=myusers.csv readers=joe,mary writers=mary
|
||||
```
|
||||
|
||||
In the following example, read access is granted to all authenticated users, but only "mary" is granted write access:
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen credentials=myusers.csv "readers=(authenticated)" writers=mary
|
||||
```
|
@ -0,0 +1,11 @@
|
||||
created: 20180701175133376
|
||||
modified: 20180702132942777
|
||||
tags: [[WebServer Authentication]]
|
||||
title: WebServer Basic Authentication
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
[[Basic authentication|https://en.wikipedia.org/wiki/Basic_access_authentication]] is a standard [[mechanism|WebServer Authentication]] for servers to instruct browsers to prompt the user for credentials. It is recommended to use it in association with HTTPS due to the way that it passes unencrypted passwords over the network.
|
||||
|
||||
Basic authentication is activated if credentials are specified via the [[username|WebServer Parameter: username]]/[[password|WebServer Parameter: password]] or [[credentials|WebServer Parameter: credentials]] parameters.
|
||||
|
||||
If [[WebServer Authorization]] is configured to allow access by both anonymous and authenticated users then by default users will not be prompted for credentials, and will be given anonymous access. To force a password prompt visit the route `/login-basic` (for example, http://127.0.0.1:8080/login-basic).
|
9
editions/tw5.com/tiddlers/webserver/WebServer Guides.tid
Normal file
9
editions/tw5.com/tiddlers/webserver/WebServer Guides.tid
Normal file
@ -0,0 +1,9 @@
|
||||
created: 20180703094704164
|
||||
modified: 20180703094729900
|
||||
tags: WebServer
|
||||
title: WebServer Guides
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Further information on usage of the integrated [[WebServer]]:
|
||||
|
||||
<<list-links filter:"[tag[WebServer Guides]]">>
|
@ -0,0 +1,9 @@
|
||||
created: 20180701175127987
|
||||
modified: 20180702140238032
|
||||
tags: [[WebServer Authentication]]
|
||||
title: WebServer Header Authentication
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
Header authentication is a web integration technique enabling external entities to securely pass details of the authenticated user to an application. It is commonly used for "single sign on" in corporate environments.
|
||||
|
||||
Header authentication is activated if is configured via the [[authenticated-user-header|WebServer Parameter: authenticated-user-header]]
|
@ -0,0 +1,10 @@
|
||||
caption: anon-username
|
||||
created: 20180702124636124
|
||||
modified: 20180702124836655
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: anon-username
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''anon-username'' provides an optional username for signing edits from anonymous users.
|
||||
|
||||
Without this parameter, anonymous users will be given a blank username.
|
@ -0,0 +1,8 @@
|
||||
caption: authenticated-user-header
|
||||
created: 20180630180213047
|
||||
modified: 20180702140416583
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: authenticated-user-header
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''authenticated-user-header'' activates [[header authentication|WebServer Header Authentication]] by specifying the name of the HTTP header that will be used to pass the username to TiddlyWiki.
|
@ -0,0 +1,25 @@
|
||||
caption: credentials
|
||||
created: 20180630180156036
|
||||
modified: 20180702154456353
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: credentials
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''credentials'' contains the pathname of a [[CSV file|https://en.wikipedia.org/wiki/Comma-separated_values]] containing a list of username/password combinations. Using the ''credentials'' parameter activates [[WebServer Basic Authentication]].
|
||||
|
||||
The CSV file must contain a header row and columns labelled ''username'' and ''password''. For example:
|
||||
|
||||
```
|
||||
username,password
|
||||
jane,do3
|
||||
andy,sm1th
|
||||
roger,m00re
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
* The optional [[username|WebServer Parameter: username]]/[[password|WebServer Parameter: password]] parameters may be used to provide an additional single set of credentials
|
||||
* The pathname is taken relative to the wiki folder
|
||||
* Passwords cannot contain the comma character `,`
|
||||
* The header row must be present
|
||||
|
@ -0,0 +1,10 @@
|
||||
caption: csrf-disable
|
||||
created: 20180630180340448
|
||||
modified: 20180702142051779
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: csrf-disable
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''csrf-disable'' causes the usual [[cross-site request forgery|https://en.wikipedia.org/wiki/Cross-site_request_forgery]] checks to be disabled. This might be necessary in unusual or experimental configurations.
|
||||
|
||||
The only currently implemented check is the use of a [[custom header|https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Protecting_REST_Services:_Use_of_Custom_Request_Headers]] called `x-requested-with` that must contain the string `TiddlyWiki` in order for write requests to succeed.
|
@ -0,0 +1,11 @@
|
||||
caption: debug-level
|
||||
created: 20180630180535728
|
||||
modified: 20180702142154308
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: debug-level
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''debug-level'' determines the level of debugging information printed to the console:
|
||||
|
||||
* ''full'' - maximum logging
|
||||
* ''none'' - no logging
|
@ -0,0 +1,12 @@
|
||||
caption: host
|
||||
created: 20180630180123321
|
||||
modified: 20180702153356797
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: host
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''host'' is the IP address on which the server listens. The most common settings are:
|
||||
|
||||
* ''127.0.0.1'' (default) - only listens for connections from browsers on the same computer
|
||||
* ''0.0.0.0'' - listens for connections on all network interfaces, and thus from any browser on a reachable network
|
||||
* ''n.n.n.n'' - listens for connections on the network interface with the specified IP address
|
@ -0,0 +1,8 @@
|
||||
caption: password
|
||||
created: 20180630170932602
|
||||
modified: 20180630171122879
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: password
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''password'', is used with its companion [[password|WebServer Parameter: username]] as a shortcut for setting user credentials for [[WebServer Basic Authentication]].
|
@ -0,0 +1,14 @@
|
||||
caption: path-prefix
|
||||
created: 20180630180514893
|
||||
modified: 20180702154716090
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: path-prefix
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''path-prefix'' can be used to set an optional prefix for all paths served.
|
||||
|
||||
This example causes the server to serve from http://127.0.0.1/MyApp instead of the default http://127.0.0.1/MyApp.
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen "path-prefix=/MyApp"
|
||||
```
|
@ -0,0 +1,25 @@
|
||||
caption: port
|
||||
created: 20180630180552254
|
||||
modified: 20180702155017130
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: port
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''port'' specifies the TCP port on which the server will listen for connections. The default value is `8080`.
|
||||
|
||||
The ''port'' parameter accepts two types of value:
|
||||
|
||||
* Numerical values are interpreted as a decimal port number
|
||||
* Non-numeric values are interpreted as an environment variable from which the port should be read
|
||||
|
||||
This example configures the server to listen on port 8090:
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen port:8090
|
||||
```
|
||||
|
||||
This example configures the server to listen on the port specified in the environment variable `THE_PORT`:
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen port:THE_PORT
|
||||
```
|
@ -0,0 +1,8 @@
|
||||
caption: readers
|
||||
created: 20180630180405978
|
||||
modified: 20180702155139006
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: readers
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''readers'' is used to specify the security principals with read access to the wiki. See [[WebServer Authorization]] for more details.
|
@ -0,0 +1,12 @@
|
||||
caption: root-render-type
|
||||
created: 20180630180301861
|
||||
modified: 20180702160458499
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: root-render-type
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''root-render-type'' determines the way that the [[root wiki tiddler|WebServer Parameter: root-tiddler]] is rendered:
|
||||
|
||||
* `text/plain` (default) -- the plain text content of the output is rendered (i.e. HTML elements are ignored)
|
||||
* `text/html` -- the full HTML content of the output is rendered (i.e. including HTML elements)
|
||||
|
@ -0,0 +1,8 @@
|
||||
caption: root-serve-type
|
||||
created: 20180630180233285
|
||||
modified: 20180702160557700
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: root-serve-type
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''root-serve-type'' determines the content type with which the root wiki tiddler is rendered. The default is `text/html`.
|
@ -0,0 +1,8 @@
|
||||
caption: root-tiddler
|
||||
created: 20180630180320376
|
||||
modified: 20180702160659310
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: root-tiddler
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''root-tiddler'' determines the title of the tiddler that is rendered as the root wiki. The default setting is `$:/core/save/all`.
|
@ -0,0 +1,10 @@
|
||||
caption: tls-cert
|
||||
created: 20180630180449581
|
||||
modified: 20180703100612649
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: tls-cert
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The optional [[web server configuration parameter|WebServer Parameters]] ''tls-cert'' contains the pathname to the certificate file required when running the web server under HTTPS. The pathname is taken relative to the wiki folder.
|
||||
|
||||
See [[Using HTTPS]] for details.
|
@ -0,0 +1,10 @@
|
||||
caption: tls-key
|
||||
created: 20180630180435405
|
||||
modified: 20180703100619863
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: tls-key
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The optional [[web server configuration parameter|WebServer Parameters]] ''tls-key'' contains the pathname to the key file required when running the web server under HTTPS. The pathname is taken relative to the wiki folder.
|
||||
|
||||
See [[Using HTTPS]] for details.
|
@ -0,0 +1,26 @@
|
||||
caption: username
|
||||
created: 20180628130114210
|
||||
modified: 20180702124624290
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: username
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''username'', in conjunction with its companion [[password|WebServer Parameter: password]]:
|
||||
|
||||
* Enables [[Basic Authentication|WebServer Basic Authentication]] with the specified username/password combination being added to any credentials specified with the [[credentials|WebServer Parameter: credentials]] parameter
|
||||
* The specified username is used as a default value for the [[readers|WebServer Parameter: readers]] and [[writers|WebServer Parameter: writers]] authorization parameters if they not specified
|
||||
|
||||
!! Examples
|
||||
|
||||
Serve anonymous users, setting the username to "joe":
|
||||
|
||||
```
|
||||
tiddlywik mywikifolder --listen anon-username=joe
|
||||
```
|
||||
|
||||
Restrict access to the user "joe" with a password of "secret":
|
||||
|
||||
```
|
||||
tiddlywik mywikifolder --listen username=joe password=secret
|
||||
```
|
||||
|
@ -0,0 +1,8 @@
|
||||
caption: writers
|
||||
created: 20180630180359439
|
||||
modified: 20180702155133411
|
||||
tags: [[WebServer Parameters]]
|
||||
title: WebServer Parameter: writers
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The [[web server configuration parameter|WebServer Parameters]] ''writers'' is used to specify the security principals with write access to the wiki. See [[WebServer Authorization]] for more details.
|
@ -0,0 +1,9 @@
|
||||
created: 20180628125910136
|
||||
modified: 20180630170919859
|
||||
tags: WebServer
|
||||
title: WebServer Parameters
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
The following configuration parameters are supported by the integrated [[WebServer]]:
|
||||
|
||||
<<list-links filter:"[tag[WebServer Parameters]]">>
|
@ -0,0 +1,6 @@
|
||||
created: 20180630194032981
|
||||
modified: 20180630194042074
|
||||
tags: WebServer
|
||||
title: WebServer Routing
|
||||
type: text/vnd.tiddlywiki
|
||||
|
64
editions/tw5.com/tiddlers/webserver/WebServer.tid
Normal file
64
editions/tw5.com/tiddlers/webserver/WebServer.tid
Normal file
@ -0,0 +1,64 @@
|
||||
created: 20180626150526207
|
||||
modified: 20180703095555387
|
||||
tags: ListenCommand ServerCommand Features
|
||||
title: WebServer
|
||||
type: text/vnd.tiddlywiki
|
||||
|
||||
When [[running under Node.js|TiddlyWiki on Node.js]], TiddlyWiki includes a simple HTTP/HTTPS web server that allows you to use it from any browser running on the same machine or over a network.
|
||||
|
||||
<<.tip """The web server includes a very simple mechanism allowing multiple users to log in with different credentials. The implementation is designed to be simple and easy to use, and would not generally be considered robust enough for use on the open internet. It is intended for use by individuals or small groups on a trusted network. It is recommended to use an external proxy before exposing it on the Internet.""">>
|
||||
|
||||
! How It Works
|
||||
|
||||
The web server listens for requests coming over the network, and performs the following actions in turn:
|
||||
|
||||
* [[Authentication|WebServer Authentication]] is the process of identifying the current user. TiddlyWiki supports three types of authentication: [[Anonymous|WebServer Anonymous Access]], [[Basic|WebServer Basic Authentication]] and [[Header|WebServer Header Authentication]]
|
||||
* [[Authorization|WebServer Authorization]] is the process of determining which resources may be accessed by a particular user. TiddlyWiki implements a simple scheme whereby read and write access to the wiki can be independently controlled.
|
||||
* [[Routing|WebServer Routing]] is the process of acting on the request, and returning any required data.
|
||||
|
||||
! Usage
|
||||
|
||||
!! Anonymous Access
|
||||
|
||||
The web server is started with the ListenCommand (which supersedes the older ServerCommand). All
|
||||
the NamedCommandParameters are optional, so the simplest form is:
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen
|
||||
```
|
||||
|
||||
Visit http://127.0.0.1:8080/ to access the wiki. Access is anonymous, so anyone can read or write to the wiki.
|
||||
|
||||
!! Authenticated Access
|
||||
|
||||
Adding [[username|WebServer Parameter: username]] and [[password|WebServer Parameter: password]] parameters enforces basic authentication for both reading and writing:
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen username=test password=tset
|
||||
```
|
||||
|
||||
Visiting the wiki will prompt for a username and password, and access is denied if they do not match the provided credentials.
|
||||
|
||||
!! Anonymous Read, Authenticated Write
|
||||
|
||||
This example adds the [[authorization|WebServer Authorization]] parameters [[readers|WebServer Parameter: readers]] and [[writers|WebServer Parameter: writers]] to grant read access to anonymous users, but require authentication as "joe" in order to gain write access.
|
||||
|
||||
> Note that anonymous users can trigger a username/password prompt by visiting the route `\login-basic` (eg http://127.0.0.1:8080/login-basic).
|
||||
|
||||
```
|
||||
tiddlywiki mywikifolder --listen "readers=(anon)" writers=joe username=joe password=bloggs
|
||||
```
|
||||
|
||||
Note the double quotes that are required for parameters containing special characters.
|
||||
|
||||
! Arguments
|
||||
|
||||
The full list of available optional parameters is:
|
||||
|
||||
<<list-links filter:"[tag[WebServer Parameters]]">>
|
||||
|
||||
! Guides
|
||||
|
||||
Further information on usage of the integrated [[WebServer]]:
|
||||
|
||||
<<list-links filter:"[tag[WebServer Guides]]">>
|
27
plugins/tiddlywiki/tiddlyweb/readonly-styles.tid
Normal file
27
plugins/tiddlywiki/tiddlyweb/readonly-styles.tid
Normal file
@ -0,0 +1,27 @@
|
||||
title: $:/plugins/tiddlywiki/tiddlyweb/readonly
|
||||
tags: [[$:/tags/Stylesheet]]
|
||||
|
||||
\define button-selector(title)
|
||||
button.$title$, .tc-drop-down button.$title$, div.$title$
|
||||
\end
|
||||
|
||||
\define hide-edit-controls()
|
||||
<$reveal state="$:/status/IsReadOnly" type="match" text="yes" default="yes">
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fclone>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fdelete>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fedit>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-here>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-journal-here>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fimport>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fmanager>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-image>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-journal>>`,`
|
||||
<<button-selector tc-btn-\%24\%3A\%2Fcore\%2Fui\%2FButtons\%2Fnew-tiddler>> `{
|
||||
display: none;
|
||||
}`
|
||||
</$reveal>
|
||||
\end
|
||||
|
||||
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
|
||||
|
||||
<<hide-edit-controls>>
|
@ -21,6 +21,8 @@ function TiddlyWebAdaptor(options) {
|
||||
this.recipe = undefined;
|
||||
this.hasStatus = false;
|
||||
this.logger = new $tw.utils.Logger("TiddlyWebAdaptor");
|
||||
this.isLoggedIn = false;
|
||||
this.isReadOnly = false;
|
||||
}
|
||||
|
||||
TiddlyWebAdaptor.prototype.name = "tiddlyweb";
|
||||
@ -63,8 +65,7 @@ TiddlyWebAdaptor.prototype.getStatus = function(callback) {
|
||||
return callback(err);
|
||||
}
|
||||
// Decode the status JSON
|
||||
var json = null,
|
||||
isLoggedIn = false;
|
||||
var json = null;
|
||||
try {
|
||||
json = JSON.parse(data);
|
||||
} catch (e) {
|
||||
@ -76,11 +77,13 @@ TiddlyWebAdaptor.prototype.getStatus = function(callback) {
|
||||
self.recipe = json.space.recipe;
|
||||
}
|
||||
// Check if we're logged in
|
||||
isLoggedIn = json.username !== "GUEST";
|
||||
self.isLoggedIn = json.username !== "GUEST";
|
||||
self.isReadOnly = !!json["read_only"];
|
||||
self.isAnonymous = !!json.anonymous;
|
||||
}
|
||||
// Invoke the callback if present
|
||||
if(callback) {
|
||||
callback(null,isLoggedIn,json.username);
|
||||
callback(null,self.isLoggedIn,json.username,self.isReadOnly,self.isAnonymous);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -165,6 +168,9 @@ Save a tiddler and invoke the callback with (err,adaptorInfo,revision)
|
||||
*/
|
||||
TiddlyWebAdaptor.prototype.saveTiddler = function(tiddler,callback) {
|
||||
var self = this;
|
||||
if(this.isReadOnly) {
|
||||
return callback(null);
|
||||
}
|
||||
$tw.utils.httpRequest({
|
||||
url: this.host + "recipes/" + encodeURIComponent(this.recipe) + "/tiddlers/" + encodeURIComponent(tiddler.fields.title),
|
||||
type: "PUT",
|
||||
@ -209,9 +215,12 @@ options include:
|
||||
tiddlerInfo: the syncer's tiddlerInfo for this tiddler
|
||||
*/
|
||||
TiddlyWebAdaptor.prototype.deleteTiddler = function(title,callback,options) {
|
||||
var self = this,
|
||||
bag = options.tiddlerInfo.adaptorInfo.bag;
|
||||
var self = this;
|
||||
if(this.isReadOnly) {
|
||||
return callback(null);
|
||||
}
|
||||
// If we don't have a bag it means that the tiddler hasn't been seen by the server, so we don't need to delete it
|
||||
var bag = options.tiddlerInfo.adaptorInfo.bag;
|
||||
if(!bag) {
|
||||
return callback(null);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user