"title": "Spus", | "title": "Spus", | ||||
"base_url": "http://example.com" | "base_url": "http://example.com" | ||||
}, | }, | ||||
"scrypt": { | |||||
"maxtime": 1 | |||||
}, | |||||
"minify": true, | "minify": true, | ||||
"session_timeout": 1800000, | "session_timeout": 1800000, | ||||
"dir": { | "dir": { |
str = preprocess(str, { | str = preprocess(str, { | ||||
session: ctx.session, | session: ctx.session, | ||||
arg: args, | |||||
arg: function(key) { | |||||
return ctx.htmlEntities(args[key]); | |||||
}, | |||||
noescape: args, | |||||
env: env, | env: env, | ||||
template: function(key) { | template: function(key) { | ||||
return ctx.template(key); | return ctx.template(key); | ||||
}.bind(this)); | }.bind(this)); | ||||
//Handle sessions | //Handle sessions | ||||
var key; | |||||
if (sessions[this.cookies.session]) { | if (sessions[this.cookies.session]) { | ||||
this.session = sessions[this.cookies.session]; | |||||
key = this.cookies.session; | |||||
} else { | } else { | ||||
var key; | |||||
do { | do { | ||||
key = crypto.randomBytes(64).toString("hex"); | key = crypto.randomBytes(64).toString("hex"); | ||||
} while (sessions[key]); | } while (sessions[key]); | ||||
setTimeout(function() { | setTimeout(function() { | ||||
delete sessions[key]; | delete sessions[key]; | ||||
}, this.conf.session_timeout); | }, 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 = { | module.exports.prototype = { | ||||
else | else | ||||
n -= 1; | n -= 1; | ||||
} | } | ||||
}, | |||||
htmlEntities: function(arg) { | |||||
if (typeof arg === "string") { | |||||
return arg.replace(/&/g, "&") | |||||
.replace(/</g, "<") | |||||
.replace(/>/g, ">") | |||||
.replace(/"/g, """); | |||||
} else { | |||||
return arg; | |||||
} | |||||
} | } | ||||
} | } |
"/profile/style.css": "profile/style.css", | "/profile/style.css": "profile/style.css", | ||||
"/profile/script.js": "profile/script.js", | "/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 | //Viewer | ||||
"/view": "view/index.node.js", | "/view": "view/index.node.js", | ||||
"/view/style.css": "view/style.css", | "/view/style.css": "view/style.css", | ||||
"/api/collection_create": "api/collection_create.node.js", | "/api/collection_create": "api/collection_create.node.js", | ||||
"/api/account_create": "api/account_create.node.js", | "/api/account_create": "api/account_create.node.js", | ||||
"/api/account_login": "api/account_login.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); | var loaded = loader.load(endpoints, conf); |
<div class="collection"> | <div class="collection"> | ||||
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a> | |||||
<span class="date-created">{{arg#date_created}}</span> | <span class="date-created">{{arg#date_created}}</span> | ||||
<a class="name" href="/view?{{arg#id}}">{{arg#name}}</a> | |||||
</div> | </div> |
<li> | <li> | ||||
<a id="navbar-button" href="/profile?{{session#userId}}">{{session#username}}</a> | |||||
<a id="navbar-button" href="/settings">{{session#username}}</a> | |||||
</li> | </li> |
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html> | <html> | ||||
<head> | <head> | ||||
<meta charset="utf-8"> | |||||
{{template#head}} | |||||
</head> | </head> | ||||
<body> | <body> | ||||
404, file not found.<br> | |||||
{{env#url}} | |||||
{{template#body}} | |||||
<div class="container"> | |||||
<div class="title">404 not found.</div> | |||||
{{env#url}} | |||||
</div> | |||||
</body> | </body> | ||||
</html> | </html> |
<div id="uploader" class="container"> | <div id="uploader" class="container"> | ||||
<input type="file" accept="image/*" id="uploader-input" class="hidden" multiple> | <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 | Select Files | ||||
</button> | </button> | ||||
<input type="text" id="uploader-collection-name" placeholder="Collection"> | |||||
<button class="btn btn-default" id="uploader-upload" disabled> | <button class="btn btn-default" id="uploader-upload" disabled> | ||||
Upload | Upload | ||||
</button> | </button> |
<body> | <body> | ||||
{{template#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> | ||||
</div> | </div> | ||||
</body> | </body> |
<body> | <body> | ||||
{{template#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"> | <div class="form-group"> | ||||
<label>Username<br> | <label>Username<br> | ||||
<input type="text" id="register-username"> | <input type="text" id="register-username"> |
<!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> |
{{template#body}} | {{template#body}} | ||||
<div id="viewer" class="container"> | <div id="viewer" class="container"> | ||||
{{arg#images}} | |||||
{{noescape#images}} | |||||
</div> | </div> | ||||
</body> | </body> | ||||
</html> | </html> |
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(); | |||||
} | |||||
); | |||||
} | |||||
); | |||||
} | |||||
} |
if (!data.username || !data.password) | if (!data.username || !data.password) | ||||
return ctx.fail("You must provide a username and a 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) { | scrypt.hash(new Buffer(data.password), params, function(err, hash) { | ||||
if (err) | if (err) | ||||
return ctx.fail(err); | return ctx.fail(err); | ||||
ctx.db.query( | ctx.db.query( | ||||
"INSERT INTO users (username, pass_hash) "+ | "INSERT INTO users (username, pass_hash) "+ | ||||
"VALUES ($1, $2 )"+ | |||||
"VALUES ($1, $2) "+ | |||||
"RETURNING id", | "RETURNING id", | ||||
[data.username, hash.toString("hex")], | [data.username, hash.toString("hex")], | ||||
queryCallback | queryCallback |
margin-bottom: 0px; | margin-bottom: 0px; | ||||
} | } | ||||
.navbar form { | |||||
border: none; | |||||
} | |||||
#login-dropdown .dropdown-menu { | #login-dropdown .dropdown-menu { | ||||
padding: 10px; | padding: 10px; | ||||
} | } | ||||
text-align: right; | text-align: right; | ||||
} | } | ||||
form .submit-container .btn { | 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 { | #notify-box { |
util.htmlEntities = function(str) { | util.htmlEntities = function(str) { | ||||
return str.replace(/&/g, "&") | return str.replace(/&/g, "&") | ||||
.replace(/</g, "<") | .replace(/</g, "<") | ||||
.replace(/>/g, "<") | |||||
.replace(/"/g, """); | |||||
.replace(/>/g, ">") | |||||
.replace(/"/g, """); | |||||
} | } | ||||
util.api = function(name, data, cb, getXhr) { | util.api = function(name, data, cb, getXhr) { | ||||
getXhr(xhr); | getXhr(xhr); | ||||
return xhr; | return xhr; | ||||
}, | |||||
error: function(xhr, status, err) { | |||||
cb(err); | |||||
} | } | ||||
}).done(function(res) { | }).done(function(res) { | ||||
console.log("response from "+name+":"); | console.log("response from "+name+":"); | ||||
util.pad(date.getMinutes().toString(), 2, "0"); | 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 = {}; | ||||
window.display.loggedIn = function() { | |||||
display.loggedIn = function() { | |||||
util.api("template?navbar-loggedin", {}, function(err, res) { | util.api("template?navbar-loggedin", {}, function(err, res) { | ||||
if (err) | if (err) | ||||
return util.error(err); | return util.error(err); | ||||
}); | }); | ||||
} | } | ||||
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() { | $(document).ready(function() { | ||||
$("#login-form").on("submit", function(evt) { | $("#login-form").on("submit", function(evt) { | ||||
evt.stopPropagation(); | |||||
evt.preventDefault(); | |||||
util.prevent(evt); | |||||
var username = $("#login-username").val(); | var username = $("#login-username").val(); | ||||
var password = $("#login-password").val(); | var password = $("#login-password").val(); |
//First, create a collection | //First, create a collection | ||||
util.api("collection_create", { | util.api("collection_create", { | ||||
name: "New Collection" | |||||
name: ($("#uploader-collection-name").val() || "Collection") | |||||
}, function(err, res) { | }, function(err, res) { | ||||
if (err) | if (err) | ||||
return util.error(err); | return util.error(err); | ||||
var collectionId = res.id; | var collectionId = res.id; | ||||
//Go to collection once files are uploaded | //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 | //Loop through files, uploading them | ||||
collectionId: collectionId, | collectionId: collectionId, | ||||
file: f | file: f | ||||
}, function(err, res) { | }, function(err, res) { | ||||
if (err) | |||||
if (err) { | |||||
a("error", true); | |||||
return util.error(err); | return util.error(err); | ||||
} | |||||
a(); | a(); | ||||
}, getXhr); | }, getXhr); |
#uploader-upload { | #uploader-upload { | ||||
float: right; | 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; | |||||
} |
"FROM collections "+ | "FROM collections "+ | ||||
"WHERE user_id = $1", | "WHERE user_id = $1", | ||||
[id], | [id], | ||||
function(err, res) { a("collections", res.rows, err) } | |||||
function(err, res) { a("collections", res, err) } | |||||
); | ); | ||||
ctx.db.query( | ctx.db.query( | ||||
"FROM users "+ | "FROM users "+ | ||||
"WHERE id = $1", | "WHERE id = $1", | ||||
[id], | [id], | ||||
function(err, res) { a("users", res.rows, err) } | |||||
function(err, res) { a("users", res, err) } | |||||
); | ); | ||||
var a = ctx.async(2, function(err, res) { | var a = ctx.async(2, function(err, res) { | ||||
if (err) | if (err) | ||||
return ctx.fail(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) | if (!user) | ||||
return ctx.end(ctx.view("404")); | return ctx.end(ctx.view("404")); | ||||
var collections = ""; | var collections = ""; | ||||
res.collections.forEach(function(row) { | |||||
res.collections.rows.forEach(function(row) { | |||||
var d = new Date(row.date_created); | var d = new Date(row.date_created); | ||||
collections += ctx.template("collection", { | collections += ctx.template("collection", { |
#profile { | |||||
text-align: center; | |||||
} | |||||
#collections { | |||||
text-align: left; | |||||
width: auto; | |||||
display: inline-block; | |||||
} |
$(document).on("ready", function() { | $(document).on("ready", function() { | ||||
$("#register-form").on("submit", function(evt) { | $("#register-form").on("submit", function(evt) { | ||||
console.log(evt); | |||||
evt.preventDefault(); | |||||
evt.stopPropagation(); | |||||
util.prevent(evt); | |||||
var username = $("#register-username").val(); | var username = $("#register-username").val(); | ||||
var password = $("#register-password").val(); | var password = $("#register-password").val(); | ||||
display.loggedIn(); | display.loggedIn(); | ||||
setTimeout(function() { | |||||
location.href = "/profile?"+res.id; | |||||
}, 1000); | |||||
util.redirect("/settings"); | |||||
}); | }); | ||||
}); | }); | ||||
}); | }); |
#register-form { | |||||
text-align: left; | |||||
display: inline-block; | |||||
} | |||||
#register { | |||||
text-align: center; | |||||
} |
module.exports = function(ctx) { | |||||
if (!ctx.session.loggedIn) | |||||
return ctx.end(ctx.view("404")); | |||||
ctx.end(ctx.view("settings")); | |||||
} |
$(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("/"); | |||||
}); | |||||
}); | |||||
}); |