mirror of
https://github.com/slynn1324/tinypin.git
synced 2026-01-23 02:25:08 +00:00
added settings page and enabled disabling registration and admin user management
This commit is contained in:
parent
cc0a95f8f3
commit
deaa8ded34
14 changed files with 658 additions and 16 deletions
|
|
@ -52,11 +52,17 @@ module.exports = async (req, res, next) => {
|
|||
|
||||
// skip auth for pub resources
|
||||
// handle login and register paths
|
||||
if ( req.originalUrl == "/favicon.ico" ){
|
||||
res.sendFile(path.resolve("./client/pub/icons/favicon.ico"));
|
||||
return;
|
||||
}
|
||||
if ( req.originalUrl.startsWith("/pub/") ){
|
||||
next();
|
||||
return;
|
||||
} if ( req.method == "GET" && req.originalUrl == "/login" ){
|
||||
res.type("html").sendFile(path.resolve('./templates/login.html'));
|
||||
console.log("login");
|
||||
// res.type("html").sendFile(path.resolve('./templates/login.html'));
|
||||
res.render("login", { registerEnabled: dao.getProperty("registerEnabled") });
|
||||
return;
|
||||
} else if ( req.method == "POST" && req.originalUrl == "/login" ){
|
||||
let username = req.body.username;
|
||||
|
|
@ -86,15 +92,32 @@ module.exports = async (req, res, next) => {
|
|||
res.redirect("./");
|
||||
return;
|
||||
} else if ( req.method == "GET" && req.originalUrl == "/register" ){
|
||||
res.type("html").sendFile(path.resolve('./templates/register.html'));
|
||||
|
||||
let registerEnabled = dao.getProperty("registerEnabled");
|
||||
if ( registerEnabled != "y" ){
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
res.render("register", {});
|
||||
return;
|
||||
} else if ( req.method == "POST" && req.originalUrl == "/register" ){
|
||||
|
||||
let registerEnabled = dao.getProperty("registerEnabled");
|
||||
if ( registerEnabled != "y" ){
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
// if it's the first user, make them an admin - otherwise don't.
|
||||
let userCount = dao.getUserCount();
|
||||
let admin = userCount == 0 ? 1 : 0;
|
||||
|
||||
let username = req.body.username;
|
||||
let salt = tokenUtils.createSalt();
|
||||
let key = await tokenUtils.deriveKey(salt, req.body.password);
|
||||
|
||||
let result = dao.createUser(username, key, salt);
|
||||
let result = dao.createUser(username, admin, key, salt);
|
||||
|
||||
if ( result && result.changes == 1 ){
|
||||
sendAuthCookie(res, {
|
||||
|
|
@ -115,6 +138,10 @@ module.exports = async (req, res, next) => {
|
|||
// if we made it this far, we're eady to check for the cookie
|
||||
let s = req.cookies.s;
|
||||
|
||||
// TODO: should probably check if the user's access has been revoked,
|
||||
// but we currently don't allow deleting users anyway. A key rotation would
|
||||
// be the other solution, but that would log out all users and require new tokens
|
||||
// to be created.
|
||||
if ( s ){
|
||||
try {
|
||||
s = tokenUtils.decrypt(s);
|
||||
|
|
|
|||
101
server/dao.js
101
server/dao.js
|
|
@ -6,7 +6,7 @@ const conf = require('./conf.js');
|
|||
let db = null;
|
||||
|
||||
function listBoards(userId){
|
||||
let boards = db.prepare("SELECT * FROM boards").all();
|
||||
let boards = db.prepare("SELECT * FROM boards where userId = @userId").all({userId:userId});
|
||||
|
||||
// get title pins
|
||||
for( let i = 0; i < boards.length; ++i ){
|
||||
|
|
@ -21,6 +21,10 @@ function listBoards(userId){
|
|||
return boards;
|
||||
}
|
||||
|
||||
function countBoardsByUser(userId){
|
||||
return db.prepare("SELECT count(id) as count FROM boards WHERE userId = @userId").get({userId: userId});
|
||||
}
|
||||
|
||||
function getBoard(userId, boardId){
|
||||
let board = db.prepare("SELECT * FROM boards WHERE userId = @userId and id = @boardId").get({userId:userId, boardId:boardId});
|
||||
|
||||
|
|
@ -58,6 +62,10 @@ function listPins(userId, boardId){
|
|||
return db.prepare("SELECT * FROM pins WHERE userId = @userId and boardId = @boardId").all({userId:userId, boardId:boardId});
|
||||
}
|
||||
|
||||
function countPinsByUser(userId){
|
||||
return db.prepare("SELECT count(id) as count FROM pins WHERE userId = @userId").get({userId:userId});
|
||||
}
|
||||
|
||||
function getPin(userId, pinId){
|
||||
return db.prepare('SELECT * FROM pins WHERE userId = @userId and id = @pinId').get({userId: userId, pinId:pinId});
|
||||
}
|
||||
|
|
@ -130,7 +138,24 @@ function deletePin(userId, pinId){
|
|||
|
||||
function getProperty(key){
|
||||
// this will throw if the property does not exist
|
||||
return db.prepare("SELECT value FROM properties WHERE key = ?").get('cookieKey').value;
|
||||
let result = db.prepare("SELECT value FROM properties WHERE key = ?").get(key);
|
||||
if ( result ){
|
||||
return result.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setProperty(key, value){
|
||||
let result = db.prepare("UPDATE properties SET value = @value WHERE key = @key").run({key: key, value: value});
|
||||
return result.changes == 1;
|
||||
}
|
||||
|
||||
function getUser(userId){
|
||||
return db.prepare("SELECT id, username, admin, createDate FROM users where id = ?").get(userId);
|
||||
}
|
||||
|
||||
function listUsers(){
|
||||
return db.prepare("SELECT id, username, admin, createDate FROM users").all();
|
||||
}
|
||||
|
||||
function getSaltForUser(username){
|
||||
|
|
@ -138,11 +163,26 @@ function getSaltForUser(username){
|
|||
}
|
||||
|
||||
function getUserByNameAndKey(username, key){
|
||||
return db.prepare("SELECT * FROM users WHERE username = @username AND key = @key").get({username: username, key: key});
|
||||
return db.prepare("SELECT id, username, admin, createDate FROM users WHERE username = @username AND key = @key").get({username: username, key: key});
|
||||
}
|
||||
|
||||
function createUser(username, key, salt){
|
||||
return db.prepare("INSERT INTO users (username, key, salt, createDate) VALUES (@username, @key, @salt, @createDate)").run({username: username, key: key, salt: salt, createDate: new Date().toISOString()});
|
||||
function createUser(username, admin, key, salt){
|
||||
return db.prepare("INSERT INTO users (username, admin, key, salt, createDate) VALUES (@username, @admin, @key, @salt, @createDate)").run({username: username, admin: admin, key: key, salt: salt, createDate: new Date().toISOString()});
|
||||
}
|
||||
|
||||
function getUserCount(){
|
||||
return db.prepare("SELECT count(id) as count FROM users").get().count;
|
||||
}
|
||||
|
||||
function setUserAdmin(userId, admin){
|
||||
let result = db.prepare("UPDATE users SET admin = @admin WHERE id = @userId").run({userId:userId, admin:admin});
|
||||
return result.changes == 1;
|
||||
}
|
||||
|
||||
function deleteUser(userId){
|
||||
db.prepare("DELETE FROM pins WHERE userId = ?").run(userId);
|
||||
db.prepare("DELETE FROM boards WHERE userId = ?").run(userId);
|
||||
db.prepare("DELETE FROM users WHERE id = ?").run(userId);
|
||||
}
|
||||
|
||||
async function init(path){
|
||||
|
|
@ -310,6 +350,47 @@ async function init(path){
|
|||
schemaVersion = 3;
|
||||
}
|
||||
|
||||
if ( schemaVersion < 4 ){
|
||||
|
||||
console.log(" running migration v4");
|
||||
|
||||
if ( !isNewDb && !createdBackup ){
|
||||
let backupPath = DB_PATH + ".backup-" + new Date().toISOString();
|
||||
console.log(" backing up to: " + backupPath);
|
||||
db.prepare(`
|
||||
VACUUM INTO ?
|
||||
`).run(backupPath);
|
||||
createdBackup = true;
|
||||
}
|
||||
|
||||
db.transaction( () => {
|
||||
|
||||
db.prepare('ALTER TABLE users ADD COLUMN admin').run();
|
||||
db.prepare('ALTER TABLE users ADD COLUMN uuid').run(); // need a uuid column to track real uniqueness, because we didn't use AUTOINCREMENT.
|
||||
|
||||
db.prepare("UPDATE users SET admin = 1").run();
|
||||
|
||||
let users = db.prepare("SELECT id FROM users").all();
|
||||
|
||||
for ( let i = 0; i < users.length; ++i ){
|
||||
let uuid = crypto.randomBytes(16).toString("hex"); // not a real uuid, but serves the same purpose
|
||||
db.prepare("UPDATE users SET uuid = @uuid WHERE id = @id").run({id: users[i].id, uuid: uuid});
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO properties (key,value) VALUES (@key, @value)
|
||||
`).run({key: 'registerEnabled', value: "y"});
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )
|
||||
`).run({id:4, createDate: new Date().toISOString()});
|
||||
|
||||
schemaVersion = 4;
|
||||
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
console.log(`database ready - schema version v${schemaVersion}`);
|
||||
console.log('');
|
||||
}
|
||||
|
|
@ -318,18 +399,26 @@ async function init(path){
|
|||
module.exports = {
|
||||
init: init,
|
||||
listBoards: listBoards,
|
||||
countBoardsByUser, countBoardsByUser,
|
||||
getBoard: getBoard,
|
||||
findBoardByUserAndName: findBoardByUserAndName,
|
||||
createBoard: createBoard,
|
||||
updateBoard: updateBoard,
|
||||
deleteBoard: deleteBoard,
|
||||
listPins: listPins,
|
||||
countPinsByUser: countPinsByUser,
|
||||
getPin: getPin,
|
||||
createPin: createPin,
|
||||
updatePin: updatePin,
|
||||
deletePin: deletePin,
|
||||
getProperty: getProperty,
|
||||
setProperty: setProperty,
|
||||
getUser: getUser,
|
||||
listUsers: listUsers,
|
||||
getSaltForUser: getSaltForUser,
|
||||
getUserCount: getUserCount,
|
||||
getUserByNameAndKey: getUserByNameAndKey,
|
||||
createUser: createUser
|
||||
createUser: createUser,
|
||||
deleteUser: deleteUser,
|
||||
setUserAdmin: setUserAdmin
|
||||
};
|
||||
126
server/server.js
126
server/server.js
|
|
@ -7,6 +7,8 @@ const tokenUtil = require('./token-utils.js');
|
|||
const dao = require("./dao.js");
|
||||
const conf = require("./conf.js");
|
||||
const imageUtils = require('./image-utils.js');
|
||||
var eta = require("eta");
|
||||
const tokenUtils = require('./token-utils.js');
|
||||
|
||||
module.exports = async () => {
|
||||
|
||||
|
|
@ -77,6 +79,9 @@ module.exports = async () => {
|
|||
|
||||
// express config
|
||||
const app = express();
|
||||
app.engine("eta", eta.renderFile);
|
||||
app.set("view engine", "eta");
|
||||
app.set("views", "./templates")
|
||||
const expressWs = require('express-ws')(app);
|
||||
|
||||
app.use(bodyParser.raw({type: 'image/jpeg', limit: '25mb'})); // accept image/jpeg files only
|
||||
|
|
@ -131,7 +136,12 @@ module.exports = async () => {
|
|||
const SERVER_ERROR = {status: "error", error: "server error"};
|
||||
|
||||
app.get("/api/whoami", (req, res) => {
|
||||
res.send({name: req.user.name, version: VERSION, id: req.user.id});
|
||||
let user = dao.getUser(req.user.id);
|
||||
if ( !user ){
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
res.send({name: user.username, id: user.id, admin: user.admin, version: VERSION});
|
||||
});
|
||||
|
||||
// list boards
|
||||
|
|
@ -333,6 +343,9 @@ module.exports = async () => {
|
|||
app.post("/up", async (req, res) => {
|
||||
|
||||
try {
|
||||
|
||||
require("fs").writeFileSync("up.jpg", req.body);
|
||||
|
||||
// try to parse the image first... if this blows up we'll stop early
|
||||
let image = await imageUtils.processImage(req.body);
|
||||
|
||||
|
|
@ -358,6 +371,117 @@ module.exports = async () => {
|
|||
}
|
||||
});
|
||||
|
||||
app.get("/api/apikey", (req,res) => {
|
||||
let s = req.cookies['s'];
|
||||
console.log("s=" + s);
|
||||
res.send({apiKey: s});
|
||||
});
|
||||
|
||||
app.get("/settings", (req, res) => {
|
||||
|
||||
let user = dao.getUser(req.user.id);
|
||||
if ( user.admin != "y" ){
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
let registerEnabled = dao.getProperty("registerEnabled");
|
||||
|
||||
let users = dao.listUsers();
|
||||
for ( let i = 0; i < users.length; ++i ){
|
||||
users[i].boardCount = dao.countBoardsByUser(users[i].id).count;
|
||||
users[i].pinCount = dao.countPinsByUser(users[i].id).count;
|
||||
}
|
||||
|
||||
res.render("settings", {
|
||||
registerEnabled: registerEnabled,
|
||||
users: users,
|
||||
userId: req.user.id
|
||||
});
|
||||
});
|
||||
|
||||
app.post("/settings", async (req, res) => {
|
||||
|
||||
let user = dao.getUser(req.user.id);
|
||||
if ( user.admin != "y" ){
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( req.body.action == "updateUsers" ){
|
||||
|
||||
let users = dao.listUsers();
|
||||
|
||||
for ( let i = 0; i < users.length; ++i ){
|
||||
if ( users[i].id != req.user.id ){ // can't update yourself
|
||||
let adminSetting = req.body['admin-' + users[i].id];
|
||||
|
||||
if ( adminSetting != users[i].admin ){
|
||||
dao.setUserAdmin(users[i].id, adminSetting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect("./settings#users-updated");
|
||||
return;
|
||||
|
||||
} else if ( req.body.action == "updateSettings" ){
|
||||
let registerEnabled = 'y';
|
||||
if ( req.body.registerEnabled == "n" ){
|
||||
registerEnabled = 'n';
|
||||
}
|
||||
dao.setProperty('registerEnabled', registerEnabled);
|
||||
|
||||
res.redirect("./settings#settings-updated");
|
||||
return;
|
||||
} else if ( req.body.action == "createUser" ){
|
||||
|
||||
let username = req.body.username;
|
||||
let password = req.body.password;
|
||||
let repeatPassword = req.body.repeatPassword;
|
||||
|
||||
console.log(`username: ${username} password: ${password} rp: ${repeatPassword}`);
|
||||
|
||||
if ( password != repeatPassword ){
|
||||
res.redirect("./settings#password-match")
|
||||
return;
|
||||
}
|
||||
|
||||
let salt = tokenUtils.createSalt();
|
||||
let key = await tokenUtils.deriveKey(salt, password);
|
||||
|
||||
try{
|
||||
dao.createUser(username, 'n', key, salt);
|
||||
} catch (err){
|
||||
console.log("error creating user " + username, err);
|
||||
res.redirect("./settings#create-user-error");
|
||||
return;
|
||||
}
|
||||
|
||||
res.redirect("./settings#created-user");
|
||||
return;
|
||||
|
||||
} else if ( req.body.action == "deleteUser" ){
|
||||
|
||||
let uid = req.body.uid;
|
||||
|
||||
try {
|
||||
dao.deleteUser(uid);
|
||||
require("fs").rmdirSync(conf.getImagePath() + "/" + uid , { recursive: true });
|
||||
} catch (err){
|
||||
console.log("error deleting user " + uid, err);
|
||||
res.redirect("./settings#delete-user-error");
|
||||
return;
|
||||
}
|
||||
|
||||
res.redirect("./settings#deleted-user");
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
res.redirect("./settings");
|
||||
|
||||
});
|
||||
|
||||
|
||||
// start listening
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue