mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2025-04-08 19:56:39 +00:00
refactor auth routes & added user management page
This commit is contained in:
parent
4e753603e8
commit
0932f2ce79
@ -4,8 +4,7 @@
|
||||
"tiddlywiki/tiddlyweb",
|
||||
"tiddlywiki/filesystem",
|
||||
"tiddlywiki/multiwikiclient",
|
||||
"tiddlywiki/multiwikiserver",
|
||||
"tiddlywiki/authentication"
|
||||
"tiddlywiki/multiwikiserver"
|
||||
],
|
||||
"themes": [
|
||||
"tiddlywiki/vanilla",
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"title": "$:/plugins/tiddlywiki/authentication",
|
||||
"description": "Authentication plugin for TiddlyWiki",
|
||||
"author": "Anon",
|
||||
"version": "0.1.0",
|
||||
"core-version": ">=5.0.0",
|
||||
"plugin-type": "plugin",
|
||||
"list": ["login"]
|
||||
}
|
19
plugins/tiddlywiki/multiwikiserver/auth/form/login.tid
Normal file
19
plugins/tiddlywiki/multiwikiserver/auth/form/login.tid
Normal file
@ -0,0 +1,19 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login
|
||||
tags: $:/tags/ServerRoute
|
||||
route-method: GET
|
||||
route-path: /login
|
||||
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/styles"/>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/head"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/header" mode="block"/>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form" mode="block"/>
|
||||
<$transclude tiddler="$:/plugins/tiddlywiki/multiwikiserver/auth/form/login/error-message" mode="block"/>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,7 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/error-message
|
||||
|
||||
<$list filter="[[$:/temp/mws/login/error]!is[missing]]" variable="errorTiddler">
|
||||
<div class="tc-error-message">
|
||||
{{$:/temp/mws/login/error}}
|
||||
</div>
|
||||
</$list>
|
10
plugins/tiddlywiki/multiwikiserver/auth/form/login/form.tid
Normal file
10
plugins/tiddlywiki/multiwikiserver/auth/form/login/form.tid
Normal file
@ -0,0 +1,10 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/form
|
||||
|
||||
|
||||
<$macrocall $name="loginForm"/>
|
||||
<form class="login-form" method="POST" action="/login">
|
||||
<input type="hidden" name="returnUrl" value=<<returnUrl>>/>
|
||||
<input type="text" name="username" placeholder="Username"/>
|
||||
<input type="password" name="password" placeholder="Password"/>
|
||||
<input type="submit" value="Log In"/>
|
||||
</form>
|
@ -0,0 +1,3 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/head
|
||||
|
||||
<title>TiddlyWiki Login</title>
|
@ -0,0 +1,3 @@
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/header
|
||||
|
||||
<h1>TiddlyWiki Login</h1>
|
@ -1,19 +1,5 @@
|
||||
title: $:/plugins/tiddlywiki/authentication/login
|
||||
tags: $:/tags/ServerRoute
|
||||
route-method: GET
|
||||
route-path: /login
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/auth/form/login/styles
|
||||
|
||||
\define loginForm()
|
||||
<form class="login-form" method="POST" action="/login">
|
||||
<input type="hidden" name="returnUrl" value=<<returnUrl>>/>
|
||||
<input type="text" name="username" placeholder="Username"/>
|
||||
<input type="password" name="password" placeholder="Password"/>
|
||||
<input type="submit" value="Log In"/>
|
||||
</form>
|
||||
\end
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
@ -59,19 +45,4 @@ body {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h1>TiddlyWiki Login</h1>
|
||||
<$set name="returnUrl" value={{{ [{$:/temp/mws/login/returnUrl}!is[blank]else{$:/info/url/query}split[returnUrl=]last[]else[/]] }}}>
|
||||
<<loginForm>>
|
||||
</$set>
|
||||
<$list filter="[[$:/temp/mws/login/error]!is[missing]]" variable="errorTiddler">
|
||||
<div class="tc-error-message">
|
||||
{{$:/temp/mws/login/error}}
|
||||
</div>
|
||||
</$list>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</style>
|
@ -77,8 +77,6 @@ function Server(options) {
|
||||
$tw.modules.forEachModuleOfType("mws-route", function(title,routeDefinition) {
|
||||
self.addRoute(routeDefinition);
|
||||
});
|
||||
// Load tiddler-based routes
|
||||
self.loadAuthRoutes();
|
||||
// Initialise the http vs https
|
||||
this.listenOptions = null;
|
||||
this.protocol = "http";
|
||||
@ -304,88 +302,6 @@ Server.prototype.addRoute = function(route) {
|
||||
this.routes.push(route);
|
||||
};
|
||||
|
||||
Server.prototype.loadAuthRoutes = function () {
|
||||
var self = this;
|
||||
// add the login page route
|
||||
self.addRoute({
|
||||
method: "GET",
|
||||
path: /^\/login$/,
|
||||
handler: function (request, response, state) {
|
||||
// Check if the user already has a valid session
|
||||
const authenticatedUser = self.authenticateUser(request, response);
|
||||
if (authenticatedUser) {
|
||||
// User is already logged in, redirect to home page
|
||||
response.writeHead(302, { 'Location': '/' });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
var loginTiddler = self.wiki.getTiddler("$:/plugins/tiddlywiki/authentication/login");
|
||||
if (loginTiddler) {
|
||||
var text = self.wiki.renderTiddler("text/html", loginTiddler.fields.title);
|
||||
response.writeHead(200, { "Content-Type": "text/html" });
|
||||
response.end(text);
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end("Login page not found");
|
||||
}
|
||||
}
|
||||
});
|
||||
// add the login submission handler route
|
||||
self.addRoute({
|
||||
method: "POST",
|
||||
path: /^\/login$/,
|
||||
csrfDisable: true,
|
||||
handler: function(request, response, state) {
|
||||
self.handleLogin(request, response, state);
|
||||
}.bind(self)
|
||||
});
|
||||
self.addRoute({
|
||||
method: "POST",
|
||||
path: /^\/logout$/,
|
||||
csrfDisable: true,
|
||||
handler: function(request, response, state) {
|
||||
self.handleLogout(request, response, state);
|
||||
}.bind(self)
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype.handleLogout = function (request, response, state) {
|
||||
var self = this;
|
||||
if (state.authenticatedUser) {
|
||||
self.sqlTiddlerDatabase.deleteSession(state.authenticatedUser.sessionId);
|
||||
}
|
||||
response.setHeader('Set-Cookie', 'session=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT');
|
||||
response.writeHead(302, { 'Location': '/login' });
|
||||
response.end();
|
||||
}
|
||||
|
||||
Server.prototype.handleLogin = function (request, response, state) {
|
||||
var self = this;
|
||||
const querystring = require('querystring');
|
||||
const formData = querystring.parse(state.data);
|
||||
const { username, password } = formData;
|
||||
const user = self.sqlTiddlerDatabase.getUserByUsername(username);
|
||||
const isPasswordValid = self.verifyPassword(password, user?.password)
|
||||
|
||||
if (user && isPasswordValid) {
|
||||
const sessionId = self.createSession(user.user_id);
|
||||
const {returnUrl} = this.parseCookieString(request.headers.cookie)
|
||||
response.setHeader('Set-Cookie', `session=${sessionId}; HttpOnly; Path=/`);
|
||||
response.writeHead(302, {
|
||||
'Location': returnUrl || '/'
|
||||
});
|
||||
} else {
|
||||
this.wiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/login/error",
|
||||
text: "Invalid username or password"
|
||||
}));
|
||||
response.writeHead(302, {
|
||||
'Location': '/login'
|
||||
});
|
||||
}
|
||||
response.end();
|
||||
};
|
||||
|
||||
Server.prototype.verifyPassword = function(inputPassword, storedHash) {
|
||||
const hashedInput = this.hashPassword(inputPassword);
|
||||
return hashedInput === storedHash;
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/get-login.js
|
||||
type: application/javascript
|
||||
module-type: mws-route
|
||||
|
||||
GET /login
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/login$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
// Check if the user already has a valid session
|
||||
var authenticatedUser = state.server.authenticateUser(request, response);
|
||||
if(authenticatedUser) {
|
||||
// User is already logged in, redirect to home page
|
||||
response.writeHead(302, { "Location": "/" });
|
||||
response.end();
|
||||
return;
|
||||
}
|
||||
var loginTiddler = $tw.mws.store.adminWiki.getTiddler("$:/plugins/tiddlywiki/multiwikiserver/auth/form/login");
|
||||
if(loginTiddler) {
|
||||
var text = $tw.mws.store.adminWiki.renderTiddler("text/html", loginTiddler.fields.title);
|
||||
response.writeHead(200, { "Content-Type": "text/html" });
|
||||
response.end(text);
|
||||
} else {
|
||||
response.writeHead(404);
|
||||
response.end("Login page not found");
|
||||
}
|
||||
};
|
||||
|
||||
}());
|
@ -0,0 +1,54 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/get-users.js
|
||||
type: application/javascript
|
||||
module-type: mws-route
|
||||
|
||||
GET /admin/users
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "GET";
|
||||
|
||||
exports.path = /^\/admin\/users$/;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var userList = state.server.sqlTiddlerDatabase.listUsers();
|
||||
|
||||
// Ensure userList is an array
|
||||
if (!Array.isArray(userList)) {
|
||||
userList = [];
|
||||
console.error("userList is not an array");
|
||||
}
|
||||
|
||||
// Convert dates to strings and ensure all necessary fields are present
|
||||
userList = userList.map(user => ({
|
||||
user_id: user.user_id || '',
|
||||
username: user.username || '',
|
||||
email: user.email || '',
|
||||
created_at: user.created_at ? new Date(user.created_at).toISOString() : '',
|
||||
last_login: user.last_login ? new Date(user.last_login).toISOString() : ''
|
||||
}));
|
||||
|
||||
console.log("Processed userList =>", userList);
|
||||
|
||||
response.writeHead(200, "OK", {
|
||||
"Content-Type": "text/html"
|
||||
});
|
||||
|
||||
// Render the html
|
||||
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain","$:/plugins/tiddlywiki/multiwikiserver/templates/page",{
|
||||
variables: {
|
||||
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-users",
|
||||
"user-list": JSON.stringify(userList),
|
||||
}
|
||||
});
|
||||
response.write(html);
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -0,0 +1,53 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/post-login.js
|
||||
type: application/javascript
|
||||
module-type: mws-route
|
||||
|
||||
POST /login
|
||||
|
||||
Parameters:
|
||||
|
||||
username
|
||||
password
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "POST";
|
||||
|
||||
exports.path = /^\/login$/;
|
||||
|
||||
exports.bodyFormat = "www-form-urlencoded";
|
||||
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
var username = state.data.username;
|
||||
var password = state.data.password;
|
||||
var user = state.server.sqlTiddlerDatabase.getUserByUsername(username);
|
||||
var isPasswordValid = state.server.verifyPassword(password, user ? user.password : null)
|
||||
|
||||
if(user && isPasswordValid) {
|
||||
var sessionId = state.server.createSession(user.user_id);
|
||||
var returnUrl = state.server.parseCookieString(request.headers.cookie).returnUrl
|
||||
response.setHeader('Set-Cookie', `session=${sessionId}; HttpOnly; Path=/`);
|
||||
response.writeHead(302, {
|
||||
'Location': returnUrl || '/'
|
||||
});
|
||||
} else {
|
||||
$tw.mws.store.adminWiki.addTiddler(new $tw.Tiddler({
|
||||
title: "$:/temp/mws/login/error",
|
||||
text: "Invalid username or password"
|
||||
}));
|
||||
response.writeHead(302, {
|
||||
'Location': '/login'
|
||||
});
|
||||
}
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
@ -0,0 +1,30 @@
|
||||
/*\
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/post-logout.js
|
||||
type: application/javascript
|
||||
module-type: mws-route
|
||||
|
||||
POST /logout
|
||||
|
||||
\*/
|
||||
(function() {
|
||||
|
||||
/*jslint node: true, browser: true */
|
||||
/*global $tw: false */
|
||||
"use strict";
|
||||
|
||||
exports.method = "POST";
|
||||
|
||||
exports.path = /^\/logout$/;
|
||||
|
||||
exports.csrfDisable = true;
|
||||
|
||||
exports.handler = function(request,response,state) {
|
||||
if(state.authenticatedUser) {
|
||||
state.server.sqlTiddlerDatabase.deleteSession(state.authenticatedUser.sessionId);
|
||||
}
|
||||
response.setHeader("Set-Cookie", "session=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT");
|
||||
response.writeHead(302, { "Location": "/login" });
|
||||
response.end();
|
||||
};
|
||||
|
||||
}());
|
89
plugins/tiddlywiki/multiwikiserver/templates/get-users.tid
Normal file
89
plugins/tiddlywiki/multiwikiserver/templates/get-users.tid
Normal file
@ -0,0 +1,89 @@
|
||||
<!--
|
||||
title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
|
||||
-->
|
||||
|
||||
\define lingo-base() $:/language/ControlPanel/Tools/
|
||||
|
||||
<h1>User Management</h1>
|
||||
|
||||
<!-- Display raw user-list for debugging -->
|
||||
<h2>Debug Info</h2>
|
||||
<p><strong>Raw user list JSON:</strong> <$text text=<<user-list>>/></p>
|
||||
|
||||
<!-- Attempt to parse the user-list JSON -->
|
||||
<$set name="userList" value=<<user-list>>>
|
||||
<!-- Display a message if the userList is empty -->
|
||||
<$list filter="[<userList>!is[blank]]" emptyMessage="The user list is empty or not provided.">
|
||||
<p><strong>User list found:</strong> <$text text=<<userList>>/></p>
|
||||
|
||||
<!-- Attempt to parse the user list -->
|
||||
<$set name="parsedUserList" value={{{ [<userList>jsonparse[]] }}}/>
|
||||
|
||||
<!-- Check if parsedUserList has any entries -->
|
||||
<$list filter="[<parsedUserList>count[]compare:number:gt[0]]" emptyMessage="No users found or failed to parse user data">
|
||||
<!-- Display parsed user list for debugging -->
|
||||
<p><strong>Parsed User List (as JSON):</strong> <$text text={{{ [<parsedUserList>jsonstringify[]] }}}/></p>
|
||||
|
||||
<!-- Display total user count -->
|
||||
<p><strong>Total users:</strong> <$text text={{{ [<parsedUserList>count[]] }}}/></p>
|
||||
|
||||
<!-- Render the user table if parsing was successful -->
|
||||
<table class="tc-view-field-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Created At</th>
|
||||
<th>Last Login</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<$list filter="[<userList>jsonindexes[]]" variable="userIndex">
|
||||
<$let currentUser={{{ [<userList>jsonget<userIndex>] }}}>
|
||||
<tr>
|
||||
<td><$text text={{{ [<currentUser>jsonget[username]] }}}/></td>
|
||||
<td><$text text={{{ [<currentUser>jsonget[email]] }}}/></td>
|
||||
<td><$text text={{{ [<currentUser>jsonget[created_at]] }}}/></td>
|
||||
<td><$text text={{{ [<currentUser>jsonget[last_login]] }}}/></td>
|
||||
<td>
|
||||
<$button message="tm-server-command" param="edit-user" user_id={{{ [<currentUser>jsonget[user_id]] }}} class="tc-btn-invisible">
|
||||
{{$:/core/images/edit-button}} Edit
|
||||
</$button>
|
||||
<$button message="tm-server-command" param="delete-user" user_id={{{ [<currentUser>jsonget[user_id]] }}} class="tc-btn-invisible">
|
||||
{{$:/core/images/delete-button}} Delete
|
||||
</$button>
|
||||
</td>
|
||||
</tr>
|
||||
</$let>
|
||||
</$list>
|
||||
</tbody>
|
||||
</table>
|
||||
</$list>
|
||||
</$list>
|
||||
</$set>
|
||||
|
||||
<$button message="tm-modal" param="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-modal" class="tc-btn-big-green">
|
||||
Add New User
|
||||
</$button>
|
||||
|
||||
<style>
|
||||
.tc-view-field-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.tc-view-field-table th, .tc-view-field-table td {
|
||||
border: 1px solid <<colour table-border>>;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.tc-view-field-table th {
|
||||
background-color: <<colour table-header-background>>;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tc-view-field-table tr:nth-child(even) {
|
||||
background-color: <<colour table-header-background>>;
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user