mirror of
https://github.com/slynn1324/tinypin.git
synced 2026-01-22 18:16:28 +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
|
|
@ -95,6 +95,7 @@ There is trivial security on the web pages to allow for multiple user support.
|
|||
- download icon > [Download by Yoyo from the Noun Project](https://thenounproject.com/term/download/2120379/)
|
||||
- share icon > [Share by Тимур Минвалеев from the Noun Project](https://thenounproject.com/term/share/1058858/)
|
||||
- done icon > [done by Viktor Ostrovsky from the Noun Project](https://thenounproject.com/term/done/587164/)
|
||||
- settings icon > [setting by LUTFI GANI AL ACHMAD from the Noun Project](https://thenounproject.com/term/settings/3291880/)
|
||||
|
||||
## server
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,51 @@ app.addSetter('aboutModal.open', (data) => {
|
|||
});
|
||||
|
||||
app.addSetter('aboutModal.close', (data) => {
|
||||
data.apiKey = null;
|
||||
data.showApiKey = false;
|
||||
data.aboutModal.active = false;
|
||||
});
|
||||
|
||||
app.addSetter('about.showApiKey', async (data, apiKey) => {
|
||||
data.apiKey = apiKey;
|
||||
data.showApiKey = true;
|
||||
});
|
||||
|
||||
async function showApiKey(){
|
||||
let result = await fetch("/api/apikey");
|
||||
let json = await result.json();
|
||||
let apiKey = json.apiKey;
|
||||
store.do('about.showApiKey', encodeURIComponent(apiKey));
|
||||
}
|
||||
|
||||
app.addComponent('aboutModal', (store) => { return new Reef("#aboutModal", {
|
||||
store: store,
|
||||
template: (data) => {
|
||||
|
||||
let apiKeyElement = "";
|
||||
if ( data.showApiKey ){
|
||||
apiKeyElement = /*html*/`
|
||||
<div>
|
||||
<h2><strong>api key for ${data.user.name}:</strong></h2>
|
||||
<input value="${data.apiKey}" style="width: 100%">
|
||||
<br /><br />
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<div class="modal ${data.aboutModal.active ? 'is-active' : ''}">
|
||||
<div class="modal-background" data-onclick="aboutModal.close"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box" style="font-family: monospace;">
|
||||
<h1><strong>tinypin</strong></h1>
|
||||
<div class="level mb-0">
|
||||
<div class="level-left">
|
||||
<h1><strong>tinypin</strong></h1>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<a data-onclick-x="showApiKey">show api key</a>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://www.github.com">github.com/slynn1324/tinypin</a>
|
||||
<br />
|
||||
|
|
@ -22,6 +55,9 @@ app.addComponent('aboutModal', (store) => { return new Reef("#aboutModal", {
|
|||
<br />
|
||||
|
||||
</div>
|
||||
|
||||
${apiKeyElement}
|
||||
|
||||
<div>
|
||||
<h2><strong>credits</strong></h2>
|
||||
client
|
||||
|
|
@ -57,6 +93,8 @@ app.addComponent('aboutModal', (store) => { return new Reef("#aboutModal", {
|
|||
share icon » <a href="https://thenounproject.com/term/share/1058858/">Share by Тимур Минвалеев from the Noun Project</a>
|
||||
<br />
|
||||
done icon » <a href="https://thenounproject.com/term/done/587164/">done by Viktor Ostrovsky from the Noun Project</a>
|
||||
<br />
|
||||
settings icon » <a href="https://thenounproject.com/term/settings/3291880/">setting by LUTFI GANI AL ACHMAD from the Noun Project</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
|||
|
|
@ -19,13 +19,14 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
|
|||
store: store,
|
||||
template: (data) => {
|
||||
|
||||
let boardName = "";
|
||||
|
||||
|
||||
let hiddenBoardImage = '';
|
||||
if ( data.board && data.board.hidden ){
|
||||
hiddenBoardImage = '<img alt="(hidden)" style="width: 16px; height: 16px; vertical-align: middle; margin-top: 2px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDI0IDI0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkFydGJvYXJkIDY8L3RpdGxlPjxwYXRoIGQ9Ik0xMiw2LjkyQTguNjUsOC42NSwwLDAsMSwxOS44NSwxMmE4LjYxLDguNjEsMCwwLDEtMTUuNywwQTguNjUsOC42NSwwLDAsMSwxMiw2LjkybTAtMkExMC42MiwxMC42MiwwLDAsMCwyLDEyYTEwLjYsMTAuNiwwLDAsMCwyMCwwQTEwLjYyLDEwLjYyLDAsMCwwLDEyLDQuOTJaIj48L3BhdGg+PHBhdGggZD0iTTE0LjIxLDExLjEyYTEuMzQsMS4zNCwwLDAsMS0xLjMzLTEuMzMsMS4zMSwxLjMxLDAsMCwxLC41Mi0xQTMuNDQsMy40NCwwLDAsMCwxMiw4LjQ2LDMuNTQsMy41NCwwLDEsMCwxNS41NCwxMmEzLjQ0LDMuNDQsMCwwLDAtLjI5LTEuNEExLjMxLDEuMzEsMCwwLDEsMTQuMjEsMTEuMTJaIj48L3BhdGg+PHBhdGggZD0iTTE5LDIwYTEsMSwwLDAsMS0uNzEtLjI5bC0xNC0xNEExLDEsMCwwLDEsNS43MSw0LjI5bDE0LDE0YTEsMSwwLDAsMSwwLDEuNDJBMSwxLDAsMCwxLDE5LDIwWiI+PC9wYXRoPjwvc3ZnPg==" />';
|
||||
}
|
||||
|
||||
let boardName = "";
|
||||
if ( data.board ){
|
||||
boardName = /*html*/`
|
||||
<span class="navbar-item">
|
||||
|
|
@ -64,9 +65,15 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
|
|||
<img style="24px; height:24px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDI0IDI0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkFydGJvYXJkIDY8L3RpdGxlPjxwYXRoIGQ9Ik0xMiw2LjkyQTguNjUsOC42NSwwLDAsMSwxOS44NSwxMmE4LjYxLDguNjEsMCwwLDEtMTUuNywwQTguNjUsOC42NSwwLDAsMSwxMiw2LjkybTAtMkExMC42MiwxMC42MiwwLDAsMCwyLDEyYTEwLjYsMTAuNiwwLDAsMCwyMCwwQTEwLjYyLDEwLjYyLDAsMCwwLDEyLDQuOTJaIj48L3BhdGg+PHBhdGggZD0iTTE0LjIxLDExLjEyYTEuMzQsMS4zNCwwLDAsMS0xLjMzLTEuMzMsMS4zMSwxLjMxLDAsMCwxLC41Mi0xQTMuNDQsMy40NCwwLDAsMCwxMiw4LjQ2LDMuNTQsMy41NCwwLDEsMCwxNS41NCwxMmEzLjQ0LDMuNDQsMCwwLDAtLjI5LTEuNEExLjMxLDEuMzEsMCwwLDEsMTQuMjEsMTEuMTJaIj48L3BhdGg+PHBhdGggZD0iTTE5LDIwYTEsMSwwLDAsMS0uNzEtLjI5bC0xNC0xNEExLDEsMCwwLDEsNS43MSw0LjI5bDE0LDE0YTEsMSwwLDAsMSwwLDEuNDJBMSwxLDAsMCwxLDE5LDIwWiI+PC9wYXRoPjwvc3ZnPg==" />
|
||||
</a>`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
let settingsItem = "";
|
||||
if (data.user.admin == "y"){
|
||||
settingsItem = `
|
||||
<a class="navbar-item has-text-right" href="./settings">
|
||||
<span>settings</span>
|
||||
<img style="20px; height:20px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDY0IDY0IiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPnNldHRpbmc8L3RpdGxlPjxwYXRoIGQ9Ik01OS42MSwyMS44M2wtNS04LjY2YTEsMSwwLDAsMC0xLjIzLS40M0w0Ni41MSwxNS41QTIyLjExLDIyLjExLDAsMCwwLDM5LDExLjE1TDM4LDMuODZBMSwxLDAsMCwwLDM3LDNIMjdhMSwxLDAsMCwwLTEsLjg2bC0xLDcuMjlhMjIuMTEsMjIuMTEsMCwwLDAtNy40OCw0LjM1bC02Ljg3LTIuNzZhMSwxLDAsMCwwLTEuMjMuNDNsLTUsOC42NmExLDEsMCwwLDAsLjI0LDEuMjlsNS44MSw0LjU2YTIxLjQzLDIxLjQzLDAsMCwwLDAsOC42NEw0LjYzLDQwLjg4YTEsMSwwLDAsMC0uMjQsMS4yOWw1LDguNjZhMSwxLDAsMCwwLDEuMjMuNDNsNi44Ny0yLjc2QTIyLjExLDIyLjExLDAsMCwwLDI1LDUyLjg1bDEsNy4yOUExLDEsMCwwLDAsMjcsNjFIMzdhMSwxLDAsMCwwLDEtLjg2bDEtNy4yOWEyMi4xMSwyMi4xMSwwLDAsMCw3LjQ4LTQuMzVsNi44NywyLjc2YTEsMSwwLDAsMCwxLjIzLS40M2w1LTguNjZhMSwxLDAsMCwwLS4yNC0xLjI5bC01LjgxLTQuNTZhMjEuNDMsMjEuNDMsMCwwLDAsMC04LjY0bDUuODEtNC41NkExLDEsMCwwLDAsNTkuNjEsMjEuODNabS03Ljc4LDQuNjZhMSwxLDAsMCwwLS4zNiwxLDE5LjM3LDE5LjM3LDAsMCwxLDAsOSwxLDEsMCwwLDAsLjM2LDFsNS42Miw0LjQxLTQuMTMsNy4xNi02LjY0LTIuNjdhMSwxLDAsMCwwLTEuMDYuMiwyMC4wNiwyMC4wNiwwLDAsMS03Ljc4LDQuNTIsMSwxLDAsMCwwLS43LjgxbC0xLDcuMDZIMjcuODdsLTEtNy4wNmExLDEsMCwwLDAtLjctLjgxLDIwLjA2LDIwLjA2LDAsMCwxLTcuNzgtNC41MiwxLDEsMCwwLDAtMS4wNi0uMmwtNi42NCwyLjY3TDYuNTUsNDEuOTJsNS42Mi00LjQxYTEsMSwwLDAsMCwuMzYtMSwxOS4zNywxOS4zNywwLDAsMSwwLTksMSwxLDAsMCwwLS4zNi0xTDYuNTUsMjIuMDhsNC4xMy03LjE2LDYuNjQsMi42N2ExLDEsMCwwLDAsMS4wNi0uMiwyMC4wNiwyMC4wNiwwLDAsMSw3Ljc4LTQuNTIsMSwxLDAsMCwwLC43LS44MWwxLTcuMDZoOC4yNmwxLDcuMDZhMSwxLDAsMCwwLC43LjgxLDIwLjA2LDIwLjA2LDAsMCwxLDcuNzgsNC41MiwxLDEsMCwwLDAsMS4wNi4ybDYuNjQtMi42Nyw0LjEzLDcuMTZaIj48L3BhdGg+PHBhdGggZD0iTTMyLDE3QTE1LDE1LDAsMSwwLDQ3LDMyLDE1LDE1LDAsMCwwLDMyLDE3Wm0wLDI4QTEzLDEzLDAsMSwxLDQ1LDMyLDEzLDEzLDAsMCwxLDMyLDQ1WiI+PC9wYXRoPjwvc3ZnPg==" />
|
||||
</a>`;
|
||||
}
|
||||
|
||||
return /*html*/`
|
||||
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
|
||||
|
|
@ -106,6 +113,8 @@ app.addComponent('navbar', (store) => { return new Reef("#navbar", {
|
|||
|
||||
${refreshItem}
|
||||
|
||||
${settingsItem}
|
||||
|
||||
<a class="navbar-item has-text-right" data-onclick="navbar.logout">
|
||||
<span>log out ${data.user ? data.user.name : ''}</span>
|
||||
<img alt="log out" width="32" height="32" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHg9IjBweCIgeT0iMHB4Ij48dGl0bGU+QXJ0Ym9hcmQgODQ8L3RpdGxlPjxnPjxwYXRoIGQ9Ik0yOCw4MkgzOFY3NEgyOGEyLDIsMCwwLDEtMi0yVjI4YTIsMiwwLDAsMSwyLTJIMzhWMThIMjhBMTAsMTAsMCwwLDAsMTgsMjhWNzJBMTAsMTAsMCwwLDAsMjgsODJaIj48L3BhdGg+PHBhdGggZD0iTTY2LDMyLjM0LDYwLjM0LDM4bDgsOEgzNHY4SDY4LjM0bC04LDhMNjYsNjcuNjYsODAuODMsNTIuODNhNCw0LDAsMCwwLDAtNS42NloiPjwvcGF0aD48L2c+PC9zdmc+" />
|
||||
|
|
|
|||
17
package-lock.json
generated
17
package-lock.json
generated
|
|
@ -11,6 +11,7 @@
|
|||
"better-sqlite3": "^7.1.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"eta": "^1.12.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
|
@ -579,6 +580,17 @@
|
|||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"node_modules/eta": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/eta/-/eta-1.12.1.tgz",
|
||||
"integrity": "sha512-H8npoci2J/7XiPnVcCVulBSPsTNGvGaINyMjQDU8AFqp9LGsEYS88g2CiU+d01Sg44WtX7o4nb8wUJ9vnI+tiA==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/eta-dev/eta?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
|
|
@ -2059,6 +2071,11 @@
|
|||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"eta": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/eta/-/eta-1.12.1.tgz",
|
||||
"integrity": "sha512-H8npoci2J/7XiPnVcCVulBSPsTNGvGaINyMjQDU8AFqp9LGsEYS88g2CiU+d01Sg44WtX7o4nb8wUJ9vnI+tiA=="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"better-sqlite3": "^7.1.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"eta": "^1.12.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -41,12 +41,14 @@
|
|||
<input class="input" name="password" id="password" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<button id="submitButton" class="button is-success" type="submit">login</button>
|
||||
|
||||
<% if ( it.registerEnabled == "y" ){ %>
|
||||
<a class="button" href="register">create account</a>
|
||||
<% } %>
|
||||
<span id="nope"></span>
|
||||
</footer>
|
||||
</form>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>tinypin</title>
|
||||
267
templates/settings.eta
Normal file
267
templates/settings.eta
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>tinypin > settings</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="pub/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="pub/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="pub/icons/favicon-16x16.png">
|
||||
<link rel="manifest" href="pub/icons/site.webmanifest">
|
||||
<link rel="mask-icon" href="pub/icons/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="shortcut icon" href="pub/icons/favicon.ico">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-config" content="pub/icons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="stylesheet" href="pub/bulma-custom.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<nav class="navbar is-light" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="./">
|
||||
<img alt="boards" style="width:24px; height: 24px;" src="data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2OCA0OCIgeD0iMHB4IiB5PSIwcHgiPjxwYXRoIGZpbGw9IiMwMDAwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTcyLDgwNiBMMTA3LDgwNiBDMTA4LjEwNDU2OSw4MDYgMTA5LDgwNi44OTU0MzEgMTA5LDgwOCBMMTA5LDgzMiBDMTA5LDgzMy4xMDQ1NjkgMTA4LjEwNDU2OSw4MzQgMTA3LDgzNCBMNzIsODM0IEM3MC44OTU0MzA1LDgzNCA3MCw4MzMuMTA0NTY5IDcwLDgzMiBMNzAsODA4IEM3MCw4MDYuODk1NDMxIDcwLjg5NTQzMDUsODA2IDcyLDgwNiBaIE0xMTIsODIyIEwxMTIsODIyIEwxMTIsODA4IEMxMTIsODA1LjIzODU3NiAxMDkuNzYxNDI0LDgwMyAxMDcsODAzIEw5Niw4MDMgTDk2LDc4OCBDOTYsNzg2Ljg5NTQzMSA5Ni44OTU0MzA1LDc4NiA5OCw3ODYgTDEyMiw3ODYgQzEyMy4xMDQ1NjksNzg2IDEyNCw3ODYuODk1NDMxIDEyNCw3ODggTDEyNCw4MjAgQzEyNCw4MjEuMTA0NTY5IDEyMy4xMDQ1NjksODIyIDEyMiw4MjIgTDExMiw4MjIgWiBNODQsODAzIEw3Miw4MDMgQzY5LjIzODU3NjMsODAzIDY3LDgwNS4yMzg1NzYgNjcsODA4IEw2Nyw4MTcgTDU4LDgxNyBDNTYuODk1NDMwNSw4MTcgNTYsODE2LjEwNDU2OSA1Niw4MTUgTDU2LDc5MSBDNTYsNzg5Ljg5NTQzMSA1Ni44OTU0MzA1LDc4OSA1OCw3ODkgTDgyLDc4OSBDODMuMTA0NTY5NSw3ODkgODQsNzg5Ljg5NTQzMSA4NCw3OTEgTDg0LDgwMyBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNTYgLTc4NikiPjwvcGF0aD48L3N2Zz4=" />
|
||||
</a>
|
||||
|
||||
<span class="navbar-item">
|
||||
<span>Settings</span>
|
||||
</span>
|
||||
|
||||
<a role="button" class="navbar-burger is-active" aria-label="menu" aria-expanded="false" href="./">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="section" id="settings-updated" style="display: none;">
|
||||
<div class="notification is-success">
|
||||
Settings updated successfully.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="users-updated" style="display: none;">
|
||||
<div class="notification is-success">
|
||||
Users updated successfully.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="create-user-error" style="display: none;">
|
||||
<div class="notification is-danger">
|
||||
Error creating user.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="created-user" style="display: none;">
|
||||
<div class="notification is-success">
|
||||
Created user.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="box">
|
||||
<h1 style="border-bottom: 1px solid #eee;"><strong>Settings</strong></h1>
|
||||
<br />
|
||||
<form method="POST" action="./settings">
|
||||
<input type="hidden" name="action" value="updatePreferences">
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label has-text-weight-normal">Registration: </label>
|
||||
</div>
|
||||
|
||||
<div class="field-body">
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select name="registerEnabled">
|
||||
<option value="y" <% if ( it.registerEnabled == "y" ){ %> selected <% } %> >Enabled</option>
|
||||
<option value="n" <% if ( it.registerEnabled != "y" ) { %> selected <% }%> >Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="has-text-right">
|
||||
<button class="button is-success">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="section">
|
||||
<div class="box">
|
||||
<h1 style="border-bottom: 1px solid #eee;"><strong>Users</strong></h1>
|
||||
<br />
|
||||
|
||||
<form method="POST" action="./settings">
|
||||
<input type="hidden" name="action" value="updateUsers">
|
||||
<table class="table" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th>
|
||||
<th>username</th>
|
||||
<th>admin</th>
|
||||
<th>created</th>
|
||||
<th>boards</th>
|
||||
<th>pins</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<thead>
|
||||
<tbody>
|
||||
<% for ( let i = 0; i < it.users.length; ++i ){ let user = it.users[i]; %>
|
||||
|
||||
<tr>
|
||||
<td><%= user.id %></td>
|
||||
<td><%= user.username %></td>
|
||||
<td>
|
||||
<% if (it.userId != user.id) { %>
|
||||
<div class="select is-small">
|
||||
<select name="admin-<%= user.id %>">
|
||||
<option value="y" <% if (user.admin == "y" ){ %> selected <% } %> >y</option>
|
||||
<option value="n" <% if (user.admin != "y" ){ %> selected <% } %> >n</option>
|
||||
</select>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<%= user.admin %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td><%= user.createDate %></td>
|
||||
<td><%= user.boardCount %></td>
|
||||
<td><%= user.pinCount %></td>
|
||||
<td>
|
||||
<% if (it.userId != user.id) { %>
|
||||
<a class="button is-small is-danger is-light" onclick="deleteUser(<%= user.id %>)">Delete User</a>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<a class="button" onclick="createUser()">Create User</a>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<button class="button is-success">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="create-user-modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">tinypin » create account</p>
|
||||
</header>
|
||||
<form method="post" action="./settings" onsubmit="return submitCreateUserForm()">
|
||||
<input type="hidden" name="action" value="createUser" />
|
||||
<section class="modal-card-body">
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="username">username</label>
|
||||
<div class="control">
|
||||
<input class="input" name="username" id="username" type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="password">password</label>
|
||||
<div class="control">
|
||||
<input class="input" name="password" id="password" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="repeatPassword">repeat password</label>
|
||||
<div class="control">
|
||||
<input class="input" name="repeatPassword" id="repeatPassword" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<footer class="modal-card-foot">
|
||||
<button id="submitButton" class="button is-success" type="submit">create account</button>
|
||||
<a id="cancelButton" class="button" onclick="createUserCancel()">Cancel</a>
|
||||
<span id="createUserError"></span>
|
||||
</footer>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="deleteUserForm" action="./settings" method="POST">
|
||||
<input type="hidden" name="action" value="deleteUser" />
|
||||
<input type="hidden" id="deleteUserUid" name="uid" value="" />
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
if ( window.location.hash == "#settings-updated" ){
|
||||
document.getElementById("settings-updated").style.display = "block";
|
||||
} else if ( window.location.hash =="#users-updated" ){
|
||||
document.getElementById("users-updated").style.display = "block";
|
||||
} else if (window.location.hash == "#created-user" ){
|
||||
document.getElementById("created-user").style.display = "block";
|
||||
} else if ( window.location.hash == "#create-user-error" ){
|
||||
document.getElementById("create-user-error").style.display = "block";
|
||||
}
|
||||
|
||||
function createUser(){
|
||||
document.getElementById("create-user-modal").classList.add("is-active");
|
||||
}
|
||||
|
||||
function createUserCancel(){
|
||||
document.getElementById("create-user-modal").classList.remove("is-active");
|
||||
document.getElementById("username").value = "";
|
||||
document.getElementById("password").value = "";
|
||||
document.getElementById("repeatPassword").value = "";
|
||||
}
|
||||
|
||||
function submitCreateUserForm(){
|
||||
|
||||
let createUserError = document.getElementById("createUserError");
|
||||
|
||||
let username = document.getElementById("username").value;
|
||||
let password = document.getElementById("password").value;
|
||||
let repeatPassword = document.getElementById("repeatPassword").value;
|
||||
|
||||
if ( username.trim().length < 1 ){
|
||||
createUserError.innerText = "username is required";
|
||||
return false;
|
||||
} else if ( password.trim().length < 1 ){
|
||||
createUserError.innerText = "password is required";
|
||||
return false;
|
||||
} else if ( password != repeatPassword ){
|
||||
createUserError.innerText = "passwords don't match";
|
||||
return false;
|
||||
}
|
||||
|
||||
createUserError.innerText = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
function deleteUser(uid){
|
||||
|
||||
if ( window.confirm("Are you sure you want to delete this user? All associated data will be deleted.") ){
|
||||
document.getElementById("deleteUserUid").value = uid;
|
||||
document.getElementById("deleteUserForm").submit();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
48
utils/change-password.js
Normal file
48
utils/change-password.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
const crypto = require('crypto');
|
||||
const readline = require("readline");
|
||||
const writable = require('stream').Writable;
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
terminal: true
|
||||
});
|
||||
|
||||
rl.stdoutMuted = true;
|
||||
rl.query = "password: ";
|
||||
rl._writeToOutput = function _writeToOutput(stringToWrite) {
|
||||
if ( stringToWrite == rl.query ){
|
||||
rl.output.write(stringToWrite);
|
||||
}
|
||||
// if (rl.stdoutMuted)
|
||||
// // rl.output.write("");
|
||||
// // rl.output.write("\x1B[2K\x1B[200D"+rl.query+"["+((rl.line.length%2==1)?"=-":"-=")+"]");
|
||||
// else
|
||||
// rl.output.write(stringToWrite);
|
||||
};
|
||||
|
||||
async function deriveKey(salt, pw){
|
||||
return new Promise( (resolve, reject) => {
|
||||
crypto.scrypt(pw, salt, 64, (err, key) => {
|
||||
resolve(key.toString('hex'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createSalt(){
|
||||
return crypto.randomBytes(16).toString('hex');
|
||||
}
|
||||
|
||||
// let username = req.body.username;
|
||||
// let salt = createSalt();
|
||||
// let key = await deriveKey(salt, req.body.password);
|
||||
|
||||
|
||||
rl.question(rl.query, async (password) => {
|
||||
rl.close();
|
||||
console.log(password);
|
||||
let salt = createSalt();
|
||||
let key = await deriveKey(salt, password);
|
||||
console.log("salt: " + salt);
|
||||
console.log("key: " + key);
|
||||
});
|
||||
16
utils/image-test.js
Normal file
16
utils/image-test.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const sharp = require("sharp");
|
||||
|
||||
async function run(){
|
||||
let original = await sharp("/Users/slynn1324/Desktop/IMG_5166.HEIC");
|
||||
|
||||
console.log(await original.metadata());
|
||||
|
||||
let jpg = await original.toFormat("jpg").toBuffer();
|
||||
|
||||
jpg = await sharp(jpg);
|
||||
|
||||
console.log(await jpg.metadata() );
|
||||
|
||||
}
|
||||
|
||||
run();
|
||||
3
utils/rotate-key.js
Normal file
3
utils/rotate-key.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const crypto = require('crypto');
|
||||
|
||||
console.log("cookieKey: " + crypto.randomBytes(32).toString('hex'));
|
||||
Loading…
Add table
Add a link
Reference in a new issue