mirror of https://github.com/calzoneman/sync.git
Add csrf prevention
This commit is contained in:
parent
420e77963b
commit
afc0ea0a58
|
@ -12,6 +12,7 @@ var $util = require("../utilities");
|
||||||
var Config = require("../config");
|
var Config = require("../config");
|
||||||
var Server = require("../server");
|
var Server = require("../server");
|
||||||
var session = require("../session");
|
var session = require("../session");
|
||||||
|
var csrf = require("./csrf");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a GET request for /account/edit
|
* Handles a GET request for /account/edit
|
||||||
|
@ -28,6 +29,8 @@ function handleAccountEditPage(req, res) {
|
||||||
* Handles a POST request to edit a user"s account
|
* Handles a POST request to edit a user"s account
|
||||||
*/
|
*/
|
||||||
function handleAccountEdit(req, res) {
|
function handleAccountEdit(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
var action = req.body.action;
|
var action = req.body.action;
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case "change_password":
|
case "change_password":
|
||||||
|
@ -204,6 +207,8 @@ function handleAccountChannelPage(req, res) {
|
||||||
* Handles a POST request to modify a user"s channels
|
* Handles a POST request to modify a user"s channels
|
||||||
*/
|
*/
|
||||||
function handleAccountChannel(req, res) {
|
function handleAccountChannel(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
var action = req.body.action;
|
var action = req.body.action;
|
||||||
switch(action) {
|
switch(action) {
|
||||||
case "new_channel":
|
case "new_channel":
|
||||||
|
@ -394,6 +399,8 @@ function handleAccountProfilePage(req, res) {
|
||||||
* Handles a POST request to edit a profile
|
* Handles a POST request to edit a profile
|
||||||
*/
|
*/
|
||||||
function handleAccountProfile(req, res) {
|
function handleAccountProfile(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return sendJade(res, "account-profile", {
|
return sendJade(res, "account-profile", {
|
||||||
profileImage: "",
|
profileImage: "",
|
||||||
|
@ -442,6 +449,8 @@ function handlePasswordResetPage(req, res) {
|
||||||
* Handles a POST request to reset a user's password
|
* Handles a POST request to reset a user's password
|
||||||
*/
|
*/
|
||||||
function handlePasswordReset(req, res) {
|
function handlePasswordReset(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
var name = req.body.name,
|
var name = req.body.name,
|
||||||
email = req.body.email;
|
email = req.body.email;
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,14 @@ var db = require("../database");
|
||||||
var Config = require("../config");
|
var Config = require("../config");
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var session = require("../session");
|
var session = require("../session");
|
||||||
|
var csrf = require("./csrf");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes a login request. Sets a cookie upon successful authentication
|
* Processes a login request. Sets a cookie upon successful authentication
|
||||||
*/
|
*/
|
||||||
function handleLogin(req, res) {
|
function handleLogin(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
var name = req.body.name;
|
var name = req.body.name;
|
||||||
var password = req.body.password;
|
var password = req.body.password;
|
||||||
var rememberMe = req.body.remember;
|
var rememberMe = req.body.remember;
|
||||||
|
@ -119,7 +122,10 @@ function handleLoginPage(req, res) {
|
||||||
* Handles a request for /logout. Clears auth cookie
|
* Handles a request for /logout. Clears auth cookie
|
||||||
*/
|
*/
|
||||||
function handleLogout(req, res) {
|
function handleLogout(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
res.clearCookie("auth");
|
res.clearCookie("auth");
|
||||||
|
req.user = res.user = null;
|
||||||
// Try to find an appropriate redirect
|
// Try to find an appropriate redirect
|
||||||
var dest = req.query.dest || req.header("referer");
|
var dest = req.query.dest || req.header("referer");
|
||||||
dest = dest && dest.match(/login|logout|account/) ? null : dest;
|
dest = dest && dest.match(/login|logout|account/) ? null : dest;
|
||||||
|
@ -159,6 +165,8 @@ function handleRegisterPage(req, res) {
|
||||||
* Processes a registration request.
|
* Processes a registration request.
|
||||||
*/
|
*/
|
||||||
function handleRegister(req, res) {
|
function handleRegister(req, res) {
|
||||||
|
csrf.verify(req);
|
||||||
|
|
||||||
var name = req.body.name;
|
var name = req.body.name;
|
||||||
var password = req.body.password;
|
var password = req.body.password;
|
||||||
var email = req.body.email;
|
var email = req.body.email;
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Adapted from https://github.com/expressjs/csurf
|
||||||
|
*/
|
||||||
|
|
||||||
|
var csrf = require("csrf");
|
||||||
|
var createError = require("http-errors");
|
||||||
|
|
||||||
|
var tokens = csrf();
|
||||||
|
|
||||||
|
exports.init = function csrfInit(req, res, next) {
|
||||||
|
var secret = req.signedCookies._csrf;
|
||||||
|
if (!secret) {
|
||||||
|
secret = tokens.secretSync();
|
||||||
|
res.cookie("_csrf", secret, { signed: true, httpOnly: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
var token;
|
||||||
|
|
||||||
|
req.csrfToken = function csrfToken() {
|
||||||
|
if (token) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
token = tokens.create(secret);
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.verify = function csrfVerify(req) {
|
||||||
|
var secret = req.signedCookies._csrf;
|
||||||
|
var token = req.body._csrf || req.query._csrf;
|
||||||
|
|
||||||
|
if (!tokens.verify(secret, token)) {
|
||||||
|
throw createError(403, 'invalid csrf token', {
|
||||||
|
code: 'EBADCSRFTOKEN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -14,7 +14,8 @@ function merge(locals, res) {
|
||||||
siteDescription: Config.get("html-template.description"),
|
siteDescription: Config.get("html-template.description"),
|
||||||
siteAuthor: "Calvin 'calzoneman' 'cyzon' Montgomery",
|
siteAuthor: "Calvin 'calzoneman' 'cyzon' Montgomery",
|
||||||
loginDomain: Config.get("https.enabled") ? Config.get("https.full-address")
|
loginDomain: Config.get("https.enabled") ? Config.get("https.full-address")
|
||||||
: Config.get("http.full-address")
|
: Config.get("http.full-address"),
|
||||||
|
csrfToken: res.req.csrfToken()
|
||||||
};
|
};
|
||||||
if (typeof locals !== "object") {
|
if (typeof locals !== "object") {
|
||||||
return _locals;
|
return _locals;
|
||||||
|
|
|
@ -14,6 +14,7 @@ var cookieParser = require("cookie-parser");
|
||||||
var static = require("serve-static");
|
var static = require("serve-static");
|
||||||
var morgan = require("morgan");
|
var morgan = require("morgan");
|
||||||
var session = require("../session");
|
var session = require("../session");
|
||||||
|
var csrf = require("./csrf");
|
||||||
|
|
||||||
const LOG_FORMAT = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
|
const LOG_FORMAT = ':real-address - :remote-user [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"';
|
||||||
morgan.token('real-address', function (req) { return req._ip; });
|
morgan.token('real-address', function (req) { return req._ip; });
|
||||||
|
@ -190,6 +191,7 @@ module.exports = {
|
||||||
Logger.errlog.log("YOU SHOULD CHANGE THE VALUE OF cookie-secret IN config.yaml");
|
Logger.errlog.log("YOU SHOULD CHANGE THE VALUE OF cookie-secret IN config.yaml");
|
||||||
}
|
}
|
||||||
app.use(cookieParser(Config.get("http.cookie-secret")));
|
app.use(cookieParser(Config.get("http.cookie-secret")));
|
||||||
|
app.use(csrf.init);
|
||||||
app.use(morgan(LOG_FORMAT, {
|
app.use(morgan(LOG_FORMAT, {
|
||||||
stream: require("fs").createWriteStream(path.join(__dirname, "..", "..",
|
stream: require("fs").createWriteStream(path.join(__dirname, "..", "..",
|
||||||
"http.log"), {
|
"http.log"), {
|
||||||
|
@ -253,6 +255,10 @@ module.exports = {
|
||||||
return res.status(413).end();
|
return res.status(413).end();
|
||||||
} else if (err.message && err.message.match(/bad request/i)) {
|
} else if (err.message && err.message.match(/bad request/i)) {
|
||||||
return res.status(400).end("Bad Request");
|
return res.status(400).end("Bad Request");
|
||||||
|
} else if (err.message && err.message.match(/invalid csrf token/i)) {
|
||||||
|
res.status(403);
|
||||||
|
sendJade(res, 'csrferror', { path: req.path });
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
Logger.errlog.log(err.stack);
|
Logger.errlog.log(err.stack);
|
||||||
res.status(500).end();
|
res.status(500).end();
|
||||||
|
|
|
@ -9,13 +9,15 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bcrypt": "^0.8.1",
|
"bcrypt": "^0.8.1",
|
||||||
"body-parser": "^1.10.2",
|
"body-parser": "^1.10.2",
|
||||||
"cheerio" : "^0.18.0",
|
"cheerio": "^0.18.0",
|
||||||
"compression": "^1.3.0",
|
"compression": "^1.3.0",
|
||||||
"cookie-parser": "^1.3.3",
|
"cookie-parser": "^1.3.3",
|
||||||
|
"csrf": "^2.0.6",
|
||||||
"cytubefilters": "git://github.com/calzoneman/cytubefilters#33b7693c",
|
"cytubefilters": "git://github.com/calzoneman/cytubefilters#33b7693c",
|
||||||
"express": "^4.11.1",
|
"express": "^4.11.1",
|
||||||
"express-minify": "^0.1.3",
|
"express-minify": "^0.1.3",
|
||||||
"graceful-fs": "^3.0.5",
|
"graceful-fs": "^3.0.5",
|
||||||
|
"http-errors": "^1.3.1",
|
||||||
"jade": "^1.9.1",
|
"jade": "^1.9.1",
|
||||||
"json-typecheck": "^0.1.3",
|
"json-typecheck": "^0.1.3",
|
||||||
"morgan": "^1.5.1",
|
"morgan": "^1.5.1",
|
||||||
|
|
|
@ -39,6 +39,7 @@ html(lang="en")
|
||||||
tr
|
tr
|
||||||
th
|
th
|
||||||
form.form-inline.pull-right(action="/account/channels", method="post", onsubmit="return confirm('Are you sure you want to delete #{c.name}? This cannot be undone');")
|
form.form-inline.pull-right(action="/account/channels", method="post", onsubmit="return confirm('Are you sure you want to delete #{c.name}? This cannot be undone');")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
input(type="hidden", name="action", value="delete_channel")
|
input(type="hidden", name="action", value="delete_channel")
|
||||||
input(type="hidden", name="name", value="#{c.name}")
|
input(type="hidden", name="name", value="#{c.name}")
|
||||||
button.btn.btn-xs.btn-danger(type="submit") Delete
|
button.btn.btn-xs.btn-danger(type="submit") Delete
|
||||||
|
@ -51,6 +52,7 @@ html(lang="en")
|
||||||
strong Channel Registration Failed
|
strong Channel Registration Failed
|
||||||
p= newChannelError
|
p= newChannelError
|
||||||
form(action="/account/channels", method="post")
|
form(action="/account/channels", method="post")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
input(type="hidden", name="action", value="new_channel")
|
input(type="hidden", name="action", value="new_channel")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="channelname") Channel Name
|
label.control-label(for="channelname") Channel Name
|
||||||
|
|
|
@ -29,6 +29,7 @@ html(lang="en")
|
||||||
p= errorMessage
|
p= errorMessage
|
||||||
h3 Change Password
|
h3 Change Password
|
||||||
form(action="/account/edit", method="post", onsubmit="return validatePasswordChange()")
|
form(action="/account/edit", method="post", onsubmit="return validatePasswordChange()")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
input(type="hidden", name="action", value="change_password")
|
input(type="hidden", name="action", value="change_password")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="username") Username
|
label.control-label(for="username") Username
|
||||||
|
@ -46,6 +47,7 @@ html(lang="en")
|
||||||
hr
|
hr
|
||||||
h3 Change Email
|
h3 Change Email
|
||||||
form(action="/account/edit", method="post", onsubmit="return submitEmail()")
|
form(action="/account/edit", method="post", onsubmit="return submitEmail()")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
input(type="hidden", name="action", value="change_email")
|
input(type="hidden", name="action", value="change_email")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="username2") Username
|
label.control-label(for="username2") Username
|
||||||
|
|
|
@ -25,6 +25,7 @@ html(lang="en")
|
||||||
strong Error
|
strong Error
|
||||||
p= resetErr
|
p= resetErr
|
||||||
form(action="/account/passwordreset", method="post", role="form")
|
form(action="/account/passwordreset", method="post", role="form")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="username") Username
|
label.control-label(for="username") Username
|
||||||
input#username.form-control(type="text", name="name")
|
input#username.form-control(type="text", name="name")
|
||||||
|
|
|
@ -32,6 +32,7 @@ html(lang="en")
|
||||||
p= profileText
|
p= profileText
|
||||||
h3 Edit Profile
|
h3 Edit Profile
|
||||||
form(action="/account/profile", method="post", role="form")
|
form(action="/account/profile", method="post", role="form")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="profileimage") Image
|
label.control-label(for="profileimage") Image
|
||||||
input#profileimage.form-control(type="text", name="image")
|
input#profileimage.form-control(type="text", name="image")
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
doctype html
|
||||||
|
html(lang="en")
|
||||||
|
head
|
||||||
|
include head
|
||||||
|
mixin head()
|
||||||
|
body
|
||||||
|
#wrap
|
||||||
|
nav.navbar.navbar-inverse.navbar-fixed-top(role="navigation")
|
||||||
|
include nav
|
||||||
|
mixin navheader()
|
||||||
|
#nav-collapsible.collapse.navbar-collapse
|
||||||
|
ul.nav.navbar-nav
|
||||||
|
mixin navdefaultlinks(path)
|
||||||
|
mixin navloginlogout(path)
|
||||||
|
|
||||||
|
section#mainpage.container
|
||||||
|
.col-md-12
|
||||||
|
.alert.alert-danger
|
||||||
|
h1 Invalid Session
|
||||||
|
p Your browser attempted to submit form data to <code>#{path}</code> with an invalid authentication token. This may be because:
|
||||||
|
ul
|
||||||
|
li Your session has expired
|
||||||
|
li Your request was missing the authentication token
|
||||||
|
li A malicious user has attempted to tamper with your session
|
||||||
|
li Your browser does not support cookies, or they are not enabled
|
||||||
|
| If the problem persists, please contact an administrator.
|
||||||
|
a(href=path) Return to previous page
|
||||||
|
|
||||||
|
include footer
|
||||||
|
mixin footer()
|
|
@ -26,6 +26,7 @@ html(lang="en")
|
||||||
p= loginError
|
p= loginError
|
||||||
h2 Login
|
h2 Login
|
||||||
form(role="form", action="/login", method="post")
|
form(role="form", action="/login", method="post")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
if redirect
|
if redirect
|
||||||
input(type="hidden", name="dest", value=redirect)
|
input(type="hidden", name="dest", value=redirect)
|
||||||
.form-group
|
.form-group
|
||||||
|
|
|
@ -47,7 +47,8 @@ mixin navloginform(redirect)
|
||||||
- loginDomain = ""
|
- loginDomain = ""
|
||||||
.visible-lg
|
.visible-lg
|
||||||
form#loginform.navbar-form.navbar-right(action="#{loginDomain}/login", method="post")
|
form#loginform.navbar-form.navbar-right(action="#{loginDomain}/login", method="post")
|
||||||
input(type="hidden", name="dest", value=redirect)
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
|
input(type="hidden", name="dest", value=encodeURIComponent(redirect))
|
||||||
.form-group
|
.form-group
|
||||||
input#username.form-control(type="text", name="name", placeholder="Username")
|
input#username.form-control(type="text", name="name", placeholder="Username")
|
||||||
.form-group
|
.form-group
|
||||||
|
@ -60,7 +61,7 @@ mixin navloginform(redirect)
|
||||||
button#login.btn.btn-default(type="submit") Login
|
button#login.btn.btn-default(type="submit") Login
|
||||||
.visible-md
|
.visible-md
|
||||||
p#loginform.navbar-text.pull-right
|
p#loginform.navbar-text.pull-right
|
||||||
a#login.navbar-link(href="#{loginDomain}/login?dest=#{redirect}") Log in
|
a#login.navbar-link(href="#{loginDomain}/login?dest=#{encodeURIComponent(redirect)}") Log in
|
||||||
span ·
|
span ·
|
||||||
a#register.navbar-link(href="/register") Register
|
a#register.navbar-link(href="/register") Register
|
||||||
|
|
||||||
|
@ -69,4 +70,4 @@ mixin navlogoutform(redirect)
|
||||||
p#logoutform.navbar-text.pull-right
|
p#logoutform.navbar-text.pull-right
|
||||||
span#welcome Welcome, #{loginName}
|
span#welcome Welcome, #{loginName}
|
||||||
span ·
|
span ·
|
||||||
a#logout.navbar-link(href="/logout?dest=#{redirect}") Logout
|
a#logout.navbar-link(href="/logout?dest=#{encodeURIComponent(redirect)}&_csrf=#{csrfToken}") Logout
|
||||||
|
|
|
@ -29,6 +29,7 @@ html(lang="en")
|
||||||
p= registerError
|
p= registerError
|
||||||
h2 Register
|
h2 Register
|
||||||
form(role="form", action="/register", method="post", onsubmit="return verify()")
|
form(role="form", action="/register", method="post", onsubmit="return verify()")
|
||||||
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label(for="username") Username
|
label.control-label(for="username") Username
|
||||||
input#username.form-control(type="text", name="name")
|
input#username.form-control(type="text", name="name")
|
||||||
|
|
Loading…
Reference in New Issue