start auth

This commit is contained in:
slynn1324 committed 2021-01-23 18:05:39 -06:00
1 parent 3cee573bdb
commit 446cfe17c6
16 files changed
+391 -24

No files matched your search

+22
View File
@@ -10,6 +10,7 @@
"dependencies": {
"better-sqlite3": "^7.1.2",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"node-fetch": "^2.6.1",
"sharp": "^0.27.0",
@@ -452,6 +453,18 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
"dependencies": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -1920,6 +1933,15 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6"
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+1
View File
@@ -11,6 +11,7 @@
"dependencies": {
"better-sqlite3": "^7.1.2",
"body-parser": "^1.19.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"node-fetch": "^2.6.1",
"sharp": "^0.27.0",
File renamed without changes.
+112
View File
@@ -0,0 +1,112 @@
<!DOCTYPE html>
<html>
<head>
<title>tinypin</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="icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="icons/site.webmanifest">
<link rel="mask-icon" href="icons/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="icons/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="icons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="bulma-custom.css" />
</head>
<body>
<div class="modal is-active">
<div class="modal-background" data-onclick="aboutModal.close"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">tinypin &raquo; create account</p>
</header>
<form method="post" action="http://localhost:3000/create-account">
<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>
</section>
<footer class="modal-card-foot">
<input type="submit">send</input>
<button id="submitButton" class="button is-success" disabled type="submit">create account</button>
<a class="button" href="login.html">login</a>
</footer>
</form>
</div>
</div>
<script>
const username = document.getElementById("username");
const password = document.getElementById("password");
const submitButton = document.getElementById("submitButton");
const validate = () => {
let valid = true;
if ( username.value.length < 1 ){
if ( username.getAttribute("data-visited") == "y" ){
username.classList.add("is-danger");
}
valid = false;
} else {
username.classList.remove("is-danger");
}
if ( password.value.length < 1 ){
if ( password.getAttribute("data-visited") == "y" ){
password.classList.add("is-danger");
}
valid = false;
} else {
password.classList.remove('is-danger');
}
if ( valid ){
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
}
// document.addEventListener('input', validate);
// document.addEventListener('focusin', (evt) => {
// if ( evt.target == username ){
// username.setAttribute("data-visited", "y");
// } else if ( evt.target == password ){
// password.setAttribute("data-visited", "y");
// }
// });
// document.addEventListener('focusout', (evt) => {
// validate();
// });
</script>
</body>
</html>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+113
View File
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html>
<head>
<title>tinypin</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="icons/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="icons/favicon-16x16.png">
<link rel="manifest" href="icons/site.webmanifest">
<link rel="mask-icon" href="icons/safari-pinned-tab.svg" color="#5bbad5">
<link rel="shortcut icon" href="icons/favicon.ico">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-config" content="icons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="bulma-custom.css" />
</head>
<body>
<div class="modal is-active">
<div class="modal-background" data-onclick="aboutModal.close"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">tinypin &raquo; login</p>
</header>
<form method="post" action="http://localhost:3000/login">
<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>
</section>
<footer class="modal-card-foot">
<button id="submitButton" class="button is-success" type="submit">login</button>
<a class="button" href="create-account.html">create account</a>
<span id="nope"></span>
</footer>
</form>
</div>
</div>
<script>
if ( window.location.hash == "#nope" ){
document.getElementById("nope").innerText = "nope.";
}
const username = document.getElementById("username");
const password = document.getElementById("password");
const submitButton = document.getElementById("submitButton");
const validate = () => {
let valid = true;
if ( username.value.length < 1 ){
if ( username.getAttribute("data-visited") == "y" ){
username.classList.add("is-danger");
}
valid = false;
} else {
username.classList.remove("is-danger");
}
if ( password.value.length < 1 ){
if ( password.getAttribute("data-visited") == "y" ){
password.classList.add("is-danger");
}
valid = false;
} else {
password.classList.remove('is-danger');
}
if ( valid ){
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
}
// document.addEventListener('input', validate);
// document.addEventListener('focusin', (evt) => {
// if ( evt.target == username ){
// username.setAttribute("data-visited", "y");
// } else if ( evt.target == password ){
// password.setAttribute("data-visited", "y");
// }
// });
// document.addEventListener('focusout', (evt) => {
// validate();
// });
</script>
</body>
</html>
+143 -24
View File
@@ -9,6 +9,9 @@ const sharp = require('sharp');
const fs = require('fs').promises;
const path = require('path');
const fetch = require('node-fetch');
const crypto = require('crypto');
const cookieParser = require('cookie-parser');
const { send } = require('process');
process.on('SIGINT', () => {
console.info('ctrl+c detected, exiting tinypin');
@@ -71,11 +74,64 @@ console.log('');
const db = betterSqlite3(DB_PATH);
// express config
const app = express();
app.use(express.static('static'));
app.use(express.static(IMAGE_PATH));
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
app.set('json spaces', 2);
app.use(cookieParser());
app.post("/login", (req, res) => {
let username = req.body.username;
let passhash = hashPassword(req.body.password);
let result = db.prepare("SELECT * FROM users WHERE username = @username AND passhash = @passhash").get({username: username, passhash: passhash});
if ( result ){
console.log(`login ok user ${username}`);
res.cookie('s', JSON.stringify({
i: result.id,
u: result.username,
d: new Date().toISOString()
}));
res.redirect("./");
} else {
console.log(`login failed for user ${username}`);
res.redirect("/login.html#nope");
}
});
// auth -- if the cookie is set exctract the user info, otherwise redirect to /login.html
app.use( (req, res, next) => {
// todo - allow basic auth for apis?
let s = req.cookies.s;
if ( s ){
s = JSON.parse(s);
req.user = {
id: s.i,
name: s.u
}
next();
} else {
console.log("not logged in");
res.redirect("/login.html"); // this means we have issues with a context path, but is needed for image redirects to work
}
});
app.use(express.static('static'));
app.use(express.static(IMAGE_PATH));
//emulate slow down
if ( SLOW ){
@@ -93,6 +149,7 @@ const ALREADY_EXISTS = {status: "error", error: "already exists"};
const SERVER_ERROR = {status: "error", error: "server error"};
initDb();
const passwordSalt = getPasswordSalt();
// list boards
app.get("/api/boards", async (req, res) => {
@@ -100,7 +157,7 @@ app.get("/api/boards", async (req, res) => {
let boards = db.prepare("SELECT * FROM boards").all();
for( let i = 0; i < boards.length; ++i ){
let result = db.prepare("SELECT id FROM pins WHERE boardId = ? order by createDate limit 1").get(boards[i].id);
let result = db.prepare("SELECT id FROM pins WHERE userId = @userId and boardId = @boardId order by createDate limit 1").get({userId:req.user.id, boardId:boards[i].id});
if ( result ) {
boards[i].titlePinId = result.id;
} else {
@@ -119,10 +176,10 @@ app.get("/api/boards", async (req, res) => {
app.get("/api/boards/:boardId", async (req, res) => {
try{
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(req.params.boardId);
let board = db.prepare("SELECT * FROM boards WHERE userId = @userId and id = @boardId").get({userId:req.user.id, boardId:req.params.boardId});
if ( board ){
board.pins = db.prepare("SELECT * FROM pins WHERE boardId = ?").all(req.params.boardId);
board.pins = db.prepare("SELECT * FROM pins WHERE userId = @userId and boardId = @boardId").all({userId:req.user.id, boardId:req.params.boardId});
res.send(board);
} else {
@@ -137,9 +194,9 @@ app.get("/api/boards/:boardId", async (req, res) => {
// create board
app.post('/api/boards', (req, res) => {
try{
let result = db.prepare("INSERT INTO boards (name, createDate) VALUES (@name, @createDate)").run({name: req.body.name, createDate: new Date().toISOString()});
let result = db.prepare("INSERT INTO boards (name, userId, createDate) VALUES (@name, @userId, @createDate)").run({name: req.body.name, userId: req.user.id, createDate: new Date().toISOString()});
let id = result.lastInsertRowid;
let board = db.prepare("SELECT * FROM boards WHERE id = ?").get(id);
let board = db.prepare("SELECT * FROM boards WHERE userId = @userId and id = @boardId").get({userId: req.user.id, boardId: id});
board.titlePinId = 0;
res.send(board);
console.log(`Created board#${id} ${req.body.name}`);
@@ -157,7 +214,7 @@ app.post('/api/boards', (req, res) => {
// update board
app.post("/api/boards/:boardId", (req, res) =>{
try{
let result = db.prepare("UPDATE boards SET name = @name WHERE id = @boardId").run({name: req.body.name, boardId: req.params.boardId});
let result = db.prepare("UPDATE boards SET name = @name WHERE userId = @userId and id = @boardId").run({name: req.body.name, userId: req.user.id, boardId: req.params.boardId});
if ( result.changes == 1 ){
res.send(OK);
} else {
@@ -173,14 +230,14 @@ app.post("/api/boards/:boardId", (req, res) =>{
app.delete("/api/boards/:boardId", async (req, res) => {
try{
let pins = db.prepare("SELECT id FROM pins WHERE boardId = ?").all(req.params.boardId);
let pins = db.prepare("SELECT id FROM pins WHERE userId = @userId and boardId = @boardId").all({userId:req.user.id, boardId:req.params.boardId});
for ( let i = 0; i < pins.length; ++i ){
await fs.unlink(getThumbnailImagePath(pins[i].id).file);
await fs.unlink(getOriginalImagePath(pins[i].id).file);
}
let result = db.prepare("DELETE FROM pins WHERE boardId = ?").run(req.params.boardId);
result = db.prepare("DELETE FROM boards WHERE id = ?").run(req.params.boardId);
let result = db.prepare("DELETE FROM pins WHERE userId = @userId and boardId = @boardId").run({userId:req.user.id, boardId:req.params.boardId});
result = db.prepare("DELETE FROM boards WHERE userId = @userId and id = @boardId").run({userId: req.user.id, boardId:req.params.boardId});
if ( result.changes == 1 ){
res.send(OK);
@@ -196,7 +253,7 @@ app.delete("/api/boards/:boardId", async (req, res) => {
// get pin
app.get("/api/pins/:pinId", (req, res) => {
try {
let pin = db.prepare('SELECT * FROM pins WHERE id = ?').get(req.params.pinId);
let pin = db.prepare('SELECT * FROM pins WHERE userId = @userId and id = @pinId').get({userId: req.user.id, pinId:req.params.pinId});
if ( pin ){
res.send(pin);
} else {
@@ -224,6 +281,7 @@ app.post("/api/pins", async (req, res) => {
originalWidth,
thumbnailHeight,
thumbnailWidth,
userId,
createDate
) VALUES (
@boardId,
@@ -235,6 +293,7 @@ app.post("/api/pins", async (req, res) => {
@originalWidth,
@thumbnailHeight,
@thumbnailWidth,
@userId,
@createDate)
`).run({
boardId: req.body.boardId,
@@ -246,6 +305,7 @@ app.post("/api/pins", async (req, res) => {
originalWidth: image.original.width,
thumbnailHeight: image.thumbnail.height,
thumbnailWidth: image.thumbnail.width,
userId: req.user.id,
createDate: new Date().toISOString()
});
@@ -262,7 +322,7 @@ app.post("/api/pins", async (req, res) => {
console.log(`Saved thumbnail to: ${thumbnailImagePath.file}`);
// return the newly created row
let pin = db.prepare("SELECT * FROM pins WHERE id = ?").get(id);
let pin = db.prepare("SELECT * FROM pins WHERE userId = @userId and id = @pinId").get({userId: req.user.id, pinId: id});
res.send(pin);
} catch (err) {
@@ -279,8 +339,9 @@ app.post("/api/pins/:pinId", (req,res) => {
siteUrl = @siteUrl,
description = @description,
sortOrder = @sortOrder
WHERE id = @pinId
WHERE userId = @userId and id = @pinId
`).run({
userId: req.user.id,
pinId: req.params.pinId,
boardId: req.body.boardId,
siteUrl: req.body.siteUrl,
@@ -304,12 +365,12 @@ app.post("/api/pins/:pinId", (req,res) => {
app.delete("/api/pins/:pinId", async (req, res) => {
try {
await fs.unlink(getThumbnailImagePath(req.params.pinId).file);
await fs.unlink(getOriginalImagePath(req.params.pinId).file);
let result = db.prepare('DELETE FROM pins WHERE id = ?').run(req.params.pinId);
let result = db.prepare('DELETE FROM pins WHERE userId = @userId and id = @pinId').run({userId: req.user.id, pinId:req.params.pinId});
if ( result.changes == 1 ){
await fs.unlink(getThumbnailImagePath(req.params.pinId).file);
await fs.unlink(getOriginalImagePath(req.params.pinId).file);
console.log(`deleted pin#${req.params.pinId}`);
res.send(OK);
} else {
@@ -322,6 +383,38 @@ app.delete("/api/pins/:pinId", async (req, res) => {
});
app.post("/create-account", (req, res) => {
console.log(`creating user '${req.body.username}'`);
let passhash = hashPassword(req.body.password);
let result = db.prepare('INSERT INTO users (username, passhash) VALUES (@username, @passhash)').run({username: req.body.username, passhash: passhash});
console.log(` user pk = ${result.lastInsertRowid}`);
let c = {
i: result.lastInsertRowid,
u: req.body.username,
d: new Date().toISOString()
}
res.cookie('s', JSON.stringify(c));
res.redirect("create-account.html");
});
app.get("/whoami", (req, res) => {
res.send(req.user);
});
function hashPassword(pw){
return crypto.createHash('sha256', passwordSalt).update(pw).digest('hex');
}
// start listening
app.listen(PORT, () => {
console.log(`tinypin is running at http://localhost:${PORT}`);
@@ -346,14 +439,34 @@ function initDb(){
console.log(" running migration v1");
db.prepare(`
CREATE TABLE IF NOT EXISTS boards (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
createDate TEXT)
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
passhash TEXT NOT NULL,
createDate TEXT
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS pins (
CREATE TABLE properties (
key TEXT NOT NULL PRIMARY KEY,
value TEXT NOT NULL
)
`).run();
db.prepare(`
CREATE TABLE boards (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
userId INTEGER NOT NULL,
createDate TEXT,
FOREIGN KEY (userId) REFERENCES users(id)
)
`).run();
db.prepare(`
CREATE TABLE pins (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
boardId INTEGER NOT NULL,
imageUrl TEXT,
@@ -364,12 +477,15 @@ function initDb(){
originalWidth INTEGER,
thumbnailHeight INTEGER,
thumbnailWidth INTEGER,
userId INTEGER NOT NULL,
createDate TEXT,
FOREIGN KEY (boardId) REFERENCES boards(id)
FOREIGN KEY (boardId) REFERENCES boards(id),
FOREIGN KEY (userId) REFERENCES users(id)
)
`).run();
db.prepare("INSERT INTO properties (key, value) VALUES (@key, @value)").run({key: 'pwsalt', value: crypto.randomBytes(32).toString('hex')});
db.prepare("INSERT INTO migrations (id, createDate) VALUES ( @id, @createDate )").run({id:1, createDate: new Date().toISOString()});
schemaVersion = 1;
@@ -377,7 +493,10 @@ function initDb(){
console.log(`database ready - schema version v${schemaVersion}`);
console.log('');
}
function getPasswordSalt(){
return db.prepare('SELECT value FROM properties WHERE key = ?').get('pwsalt').value;
}
async function downloadImage(imageUrl){