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:
snlhnk 2018-07-18 08:54:43 -07:00 committed by Jeremy Ruston
parent 9735e13dea
commit c05c0d3df6
68 changed files with 1555 additions and 336 deletions

View File

@ -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.

View 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")

View File

@ -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
```

View File

@ -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 = {};

View 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;
})();

View File

@ -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;
};

View 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;
})();

View 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;
})();

View 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();
};
}());

View 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");
};
}());

View 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);
});
};
}());

View 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");
};
}());

View 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();
}
};
}());

View 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");
};
}());

View 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();
}
};
}());

View 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();
}
};
}());

View 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");
};
}());

View 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();
};
}());

View 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;
})();

View File

@ -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
View 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;
}
})();

View File

@ -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) {

View 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>
-->

View 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>

View 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>

View File

@ -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]]">>

View 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}}

View File

@ -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.

View File

@ -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}}

View File

@ -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.

View File

@ -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.

View File

@ -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`.

View File

@ -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:

View File

@ -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.

View File

@ -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 &quot;&quot; &quot;&quot; 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" />

View File

@ -1,5 +1,5 @@
created: 20180328145259455
modified: 20180328151038658
modified: 20180701185215523
tags:
title: Example web.config for IIS
type: text/plain

View File

@ -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 &quot;username&quot; &quot;password&quot; 127.0.0.1 /MyApp">`
** Take note of the need to HTML encode the double quotes around the username and password into `&quot;`
* 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 &quot;password=bloggs&quot; port=PORT path-prefix=/MyApp">`
** Take note of the need to use double quotes around non-alphanumeric passwords, and to HTML encode them into `&quot;`
* 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

View 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
```

View File

@ -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]].

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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
```

View File

@ -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).

View 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]]">>

View File

@ -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]]

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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]].

View File

@ -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"
```

View File

@ -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
```

View File

@ -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.

View File

@ -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)

View File

@ -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`.

View File

@ -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`.

View File

@ -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.

View File

@ -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.

View File

@ -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
```

View File

@ -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.

View File

@ -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]]">>

View File

@ -0,0 +1,6 @@
created: 20180630194032981
modified: 20180630194042074
tags: WebServer
title: WebServer Routing
type: text/vnd.tiddlywiki

View 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]]">>

View 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>>

View File

@ -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);
}