Browse Source

lots of new things, including settings and some CSS

master
mort 8 years ago
parent
commit
a5d83fe6ad

+ 3
- 0
conf.json.example View File

@@ -16,6 +16,9 @@
"title": "Spus",
"base_url": "http://example.com"
},
"scrypt": {
"maxtime": 1
},
"minify": true,
"session_timeout": 1800000,
"dir": {

+ 26
- 5
lib/context.js View File

@@ -9,7 +9,10 @@ function templatify(str, args, ctx, env) {

str = preprocess(str, {
session: ctx.session,
arg: args,
arg: function(key) {
return ctx.htmlEntities(args[key]);
},
noescape: args,
env: env,
template: function(key) {
return ctx.template(key);
@@ -39,10 +42,10 @@ module.exports = function(options) {
}.bind(this));

//Handle sessions
var key;
if (sessions[this.cookies.session]) {
this.session = sessions[this.cookies.session];
key = this.cookies.session;
} else {
var key;
do {
key = crypto.randomBytes(64).toString("hex");
} while (sessions[key]);
@@ -54,9 +57,16 @@ module.exports = function(options) {
setTimeout(function() {
delete sessions[key];
}, this.conf.session_timeout);

this.session = sessions[key];
}
this.session = sessions[key];

//Reset session delete timer
if (this.session._timeout)
clearTimeout(this.session._timeout);

this.session._timeout = setTimeout(function() {
delete sessions[key];
}, this.conf.session_timeout);
}

module.exports.prototype = {
@@ -173,5 +183,16 @@ module.exports.prototype = {
else
n -= 1;
}
},

htmlEntities: function(arg) {
if (typeof arg === "string") {
return arg.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
} else {
return arg;
}
}
}

+ 7
- 1
server.js View File

@@ -31,6 +31,11 @@ var endpoints = {
"/profile/style.css": "profile/style.css",
"/profile/script.js": "profile/script.js",

//Settings
"/settings": "settings/index.node.js",
"/settings/style.css": "settings/style.css",
"/settings/script.js": "settings/script.js",

//Viewer
"/view": "view/index.node.js",
"/view/style.css": "view/style.css",
@@ -44,7 +49,8 @@ var endpoints = {
"/api/collection_create": "api/collection_create.node.js",
"/api/account_create": "api/account_create.node.js",
"/api/account_login": "api/account_login.node.js",
"/api/account_logout": "api/account_logout.node.js"
"/api/account_logout": "api/account_logout.node.js",
"/api/account_change_password": "api/account_change_password.node.js"
}

var loaded = loader.load(endpoints, conf);

+ 1
- 1
templates/collection.html View File

@@ -1,4 +1,4 @@
<div class="collection">
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a>
<span class="date-created">{{arg#date_created}}</span>
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a>
</div>

+ 1
- 1
templates/navbar-loggedin.html View File

@@ -1,3 +1,3 @@
<li>
<a id="navbar-button" href="/profile?{{session#userId}}">{{session#username}}</a>
<a id="navbar-button" href="/settings">{{session#username}}</a>
</li>

+ 6
- 3
views/404.html View File

@@ -1,10 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
{{template#head}}
</head>
<body>
404, file not found.<br>
{{env#url}}
{{template#body}}
<div class="container">
<div class="title">404 not found.</div>
{{env#url}}
</div>
</body>
</html>

+ 2
- 1
views/index.html View File

@@ -9,9 +9,10 @@
<div id="uploader" class="container">
<input type="file" accept="image/*" id="uploader-input" class="hidden" multiple>

<button class="btn btn-default" onclick="$('#uploader-input').click()">
<button class="btn btn-default" onclick="$('#uploader-input').click()" id="uploader-select-files">
Select Files
</button>
<input type="text" id="uploader-collection-name" placeholder="Collection">
<button class="btn btn-default" id="uploader-upload" disabled>
Upload
</button>

+ 4
- 4
views/profile.html View File

@@ -6,10 +6,10 @@
<body>
{{template#body}}

<div id="profile" class="container">
<div id="collections" class="container">
<div class="name">{{arg#username}}</div>
{{arg#collections}}
<div id="profile" class="container small-width-container">
<div id="collections" class="container small-width">
<div class="title">{{arg#username}}</div>
{{noescape#collections}}
</div>
</div>
</body>

+ 2
- 2
views/register.html View File

@@ -6,8 +6,8 @@
<body>
{{template#body}}

<div class="container" id="register">
<form id="register-form">
<div id="register" class="container small-width-container">
<form id="register-form" class="container small-width">
<div class="form-group">
<label>Username<br>
<input type="text" id="register-username">

+ 42
- 0
views/settings.html View File

@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
{{template#head}}
</head>
<body>
{{template#body}}

<div id="settings" class="container small-width-container">
<a class="title" href="/profile?{{session#userId}}">{{session#username}}</a>

<form id="password-form" class="container small-width">
<div class="title">Change Password</div>
<div class="form-group">
<label>Old Password<br>
<input type="password" id="password-old">
</label>
</div>
<div class="form-group">
<label>New Password<br>
<input type="password" id="password-new">
</label>
</div>
<div class="form-group">
<label>Repeat Password<br>
<input type="password" id="password-repeat">
</label>
</div>
<div class="submit-container">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</form>

<form id="logout-form" class="container small-width">
<div class="title">Log Out</div>
<div class="submit-container">
<button type="submit" class="btn btn-default">Log Out</button>
</div>
</form>
</div>
</body>
</html>

+ 1
- 1
views/view.html View File

@@ -7,7 +7,7 @@
{{template#body}}

<div id="viewer" class="container">
{{arg#images}}
{{noescape#images}}
</div>
</body>
</html>

+ 68
- 0
web/api/account_change_password.node.js View File

@@ -0,0 +1,68 @@
var scrypt = require("scrypt");

module.exports = function(ctx) {
ctx.getPostData(function(err, data) {
if (err)
return ctx.fail(err);

if (!data.oldPassword || !data.newPassword)
return ctx.fail("You must provide passwords.");

if (!ctx.session.loggedIn)
return ctx.fail("You're not logged in.");

ctx.db.query(
"SELECT id, pass_hash "+
"FROM users "+
"WHERE id = $1",
[ctx.session.userId],
queryCallback
);
});

function queryCallback(err, res) {
if (err)
return ctx.fail(err);

var user = res.rows[0];

if (!user)
return ctx.fail("User doesn't exist.");

scrypt.verify(
new Buffer(user.pass_hash, "hex"),
new Buffer(ctx.postData.data.oldPassword),
function(err, success) {
if (!success)
return ctx.fail("Wrong password.");

updatePassword();
}
);
}

function updatePassword() {
var params = scrypt.params(ctx.conf.scrypt.maxtime);
scrypt.hash(
new Buffer(ctx.postData.data.newPassword),
params,
function(err, hash) {
if (err)
return ctx.fail(err);

ctx.db.query(
"UPDATE users "+
"SET pass_hash = $1 "+
"WHERE id = $2",
[hash.toString("hex"), ctx.session.userId],
function(err, res) {
if (err)
return ctx.fail(err);

ctx.succeed();
}
);
}
);
}
}

+ 5
- 2
web/api/account_create.node.js View File

@@ -8,14 +8,17 @@ module.exports = function(ctx) {
if (!data.username || !data.password)
return ctx.fail("You must provide a username and a password.");

var params = scrypt.params(1);
if (!/^[a-zA-Z0-9_\-]+$/.test(data.username))
return ctx.fail("Username contains illegal characters.");

var params = scrypt.params(ctx.conf.scrypt.maxtime);
scrypt.hash(new Buffer(data.password), params, function(err, hash) {
if (err)
return ctx.fail(err);

ctx.db.query(
"INSERT INTO users (username, pass_hash) "+
"VALUES ($1, $2 )"+
"VALUES ($1, $2) "+
"RETURNING id",
[data.username, hash.toString("hex")],
queryCallback

+ 34
- 1
web/global.css View File

@@ -6,6 +6,10 @@
margin-bottom: 0px;
}

.navbar form {
border: none;
}

#login-dropdown .dropdown-menu {
padding: 10px;
}
@@ -23,7 +27,36 @@ form .submit-container {
text-align: right;
}
form .submit-container .btn {
width: 75px;
width: 100%;
}
form input[type="text"],
form input[type="password"] {
width: 100%;
}
form label {
width: 100%;
}
form.container {
border: 1px solid #CCC;
border-radius: 4px;
padding: 10px !important;
margin-bottom: 10px;
}

.title {
font-weight: bold;
font-size: 1.2em;
margin-bottom: 6px;
}

.small-width-container {
text-align: center !important;
}
.small-width {
width: auto !important;
text-align: left !important;
max-width: 400px !important;
width: 90% !important;
}

#notify-box {

+ 29
- 5
web/global.js View File

@@ -40,8 +40,8 @@
util.htmlEntities = function(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&lt;")
.replace(/"/g, "&quot");
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

util.api = function(name, data, cb, getXhr) {
@@ -64,6 +64,9 @@
getXhr(xhr);

return xhr;
},
error: function(xhr, status, err) {
cb(err);
}
}).done(function(res) {
console.log("response from "+name+":");
@@ -114,9 +117,20 @@
util.pad(date.getMinutes().toString(), 2, "0");
}

util.prevent = function(evt) {
evt.preventDefault();
evt.stopPropagation();
}

util.redirect = function(url, timeout) {
setTimeout(function() {
location.href = url;
}, timeout || 1000);
}

window.display = {};

window.display.loggedIn = function() {
display.loggedIn = function() {
util.api("template?navbar-loggedin", {}, function(err, res) {
if (err)
return util.error(err);
@@ -127,10 +141,20 @@
});
}

display.logIn = function() {
util.api("template?navbar-login", {}, function(err, res) {
if (err)
return util.error(err);

$("#navbar-profile-container").html(res.html);

util.notify("Logged Out", "You are now logged out.");
});
}

$(document).ready(function() {
$("#login-form").on("submit", function(evt) {
evt.stopPropagation();
evt.preventDefault();
util.prevent(evt);

var username = $("#login-username").val();
var password = $("#login-password").val();

+ 9
- 6
web/index/script.js View File

@@ -67,7 +67,7 @@ $(document).on("ready", function() {

//First, create a collection
util.api("collection_create", {
name: "New Collection"
name: ($("#uploader-collection-name").val() || "Collection")
}, function(err, res) {
if (err)
return util.error(err);
@@ -75,10 +75,11 @@ $(document).on("ready", function() {
var collectionId = res.id;

//Go to collection once files are uploaded
var a = util.async(files.length, function() {
setTimeout(function() {
location.href = "/view?"+collectionId;
}, 1000);
var a = util.async(files.length, function(res) {
if (res.error)
util.redirect("/", 5000);
else
util.redirect("/view?"+collectionId);
});

//Loop through files, uploading them
@@ -108,8 +109,10 @@ $(document).on("ready", function() {
collectionId: collectionId,
file: f
}, function(err, res) {
if (err)
if (err) {
a("error", true);
return util.error(err);
}

a();
}, getXhr);

+ 13
- 0
web/index/style.css View File

@@ -23,3 +23,16 @@
#uploader-upload {
float: right;
}

#uploader-select-files, #uploader-upload {
width: 100px;
}
#uploader-select-files {
float: left;
}
#uploader-collection-name {
width: calc(100% - 200px);
border: 1px solid #CCC;
border-radius: 4px;
padding: 6px 12px;
}

+ 7
- 4
web/profile/index.node.js View File

@@ -6,7 +6,7 @@ module.exports = function(ctx) {
"FROM collections "+
"WHERE user_id = $1",
[id],
function(err, res) { a("collections", res.rows, err) }
function(err, res) { a("collections", res, err) }
);

ctx.db.query(
@@ -14,19 +14,22 @@ module.exports = function(ctx) {
"FROM users "+
"WHERE id = $1",
[id],
function(err, res) { a("users", res.rows, err) }
function(err, res) { a("users", res, err) }
);

var a = ctx.async(2, function(err, res) {
if (err)
return ctx.fail(err);

var user = res.users[0];
if (!res.collections || !res.users)
return ctx.end(ctx.view("404"));

var user = res.users.rows[0];
if (!user)
return ctx.end(ctx.view("404"));

var collections = "";
res.collections.forEach(function(row) {
res.collections.rows.forEach(function(row) {
var d = new Date(row.date_created);

collections += ctx.template("collection", {

+ 0
- 9
web/profile/style.css View File

@@ -1,9 +0,0 @@
#profile {
text-align: center;
}

#collections {
text-align: left;
width: auto;
display: inline-block;
}

+ 2
- 6
web/register/script.js View File

@@ -1,8 +1,6 @@
$(document).on("ready", function() {
$("#register-form").on("submit", function(evt) {
console.log(evt);
evt.preventDefault();
evt.stopPropagation();
util.prevent(evt);

var username = $("#register-username").val();
var password = $("#register-password").val();
@@ -26,9 +24,7 @@ $(document).on("ready", function() {

display.loggedIn();

setTimeout(function() {
location.href = "/profile?"+res.id;
}, 1000);
util.redirect("/settings");
});
});
});

+ 0
- 8
web/register/style.css View File

@@ -1,8 +0,0 @@
#register-form {
text-align: left;
display: inline-block;
}

#register {
text-align: center;
}

+ 6
- 0
web/settings/index.node.js View File

@@ -0,0 +1,6 @@
module.exports = function(ctx) {
if (!ctx.session.loggedIn)
return ctx.end(ctx.view("404"));

ctx.end(ctx.view("settings"));
}

+ 36
- 0
web/settings/script.js View File

@@ -0,0 +1,36 @@
$(document).on("ready", function() {
$("#password-form").on("submit", function(evt) {
util.prevent(evt);

var oldPass = $("#password-old").val();
var newPass = $("#password-new").val();
var repeatPass = $("#password-repeat").val();

if (newPass !== repeatPass)
return util.error("Passwords don't match.");

util.api("account_change_password", {
oldPassword: oldPass,
newPassword: newPass
}, function(err, res) {
if (err)
return util.error(err);

util.notify("Password changed!");
});
});

$("#logout-form").on("submit", function(evt) {
evt.preventDefault();
evt.stopPropagation();

util.api("account_logout", {}, function(err, res) {
if (err)
return util.error(err);

display.logIn();

util.redirect("/");
});
});
});

+ 0
- 0
web/settings/style.css View File


Loading…
Cancel
Save