mirror of https://github.com/Budibase/budibase.git
31 changed files with 239 additions and 78 deletions
@ -0,0 +1,78 @@ |
|||
const { Headers } = require("../constants") |
|||
const { buildMatcherRegex, matches } = require("./matchers") |
|||
|
|||
/** |
|||
* GET, HEAD and OPTIONS methods are considered safe operations |
|||
* |
|||
* POST, PUT, PATCH, and DELETE methods, being state changing verbs, |
|||
* should have a CSRF token attached to the request |
|||
*/ |
|||
const EXCLUDED_METHODS = ["GET", "HEAD", "OPTIONS"] |
|||
|
|||
/** |
|||
* There are only three content type values that can be used in cross domain requests. |
|||
* If any other value is used, e.g. application/json, the browser will first make a OPTIONS |
|||
* request which will be protected by CORS. |
|||
*/ |
|||
const INCLUDED_CONTENT_TYPES = [ |
|||
"application/x-www-form-urlencoded", |
|||
"multipart/form-data", |
|||
"text/plain", |
|||
] |
|||
|
|||
/** |
|||
* Validate the CSRF token generated aganst the user session. |
|||
* Compare the token with the x-csrf-token header. |
|||
* |
|||
* If the token is not found within the request or the value provided |
|||
* does not match the value within the user session, the request is rejected. |
|||
* |
|||
* CSRF protection provided using the 'Synchronizer Token Pattern' |
|||
* https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern
|
|||
* |
|||
*/ |
|||
module.exports = (opts = { noCsrfPatterns: [] }) => { |
|||
const noCsrfOptions = buildMatcherRegex(opts.noCsrfPatterns) |
|||
return async (ctx, next) => { |
|||
// don't apply for excluded paths
|
|||
const found = matches(ctx, noCsrfOptions) |
|||
if (found) { |
|||
return next() |
|||
} |
|||
|
|||
// don't apply for the excluded http methods
|
|||
if (EXCLUDED_METHODS.indexOf(ctx.method) !== -1) { |
|||
return next() |
|||
} |
|||
|
|||
// don't apply when the content type isn't supported
|
|||
let contentType = ctx.get("content-type") |
|||
? ctx.get("content-type").toLowerCase() |
|||
: "" |
|||
if ( |
|||
!INCLUDED_CONTENT_TYPES.filter(type => contentType.includes(type)).length |
|||
) { |
|||
return next() |
|||
} |
|||
|
|||
// don't apply csrf when the internal api key has been used
|
|||
if (ctx.internal) { |
|||
return next() |
|||
} |
|||
|
|||
// apply csrf when there is a token in the session (new logins)
|
|||
// in future there should be a hard requirement that the token is present
|
|||
const userToken = ctx.user.csrfToken |
|||
if (!userToken) { |
|||
return next() |
|||
} |
|||
|
|||
// reject if no token in request or mismatch
|
|||
const requestToken = ctx.get(Headers.CSRF_TOKEN) |
|||
if (!requestToken || requestToken !== userToken) { |
|||
ctx.throw(403, "Invalid CSRF token") |
|||
} |
|||
|
|||
return next() |
|||
} |
|||
} |
|||
@ -1 +1,2 @@ |
|||
exports.TENANT_ID = "default" |
|||
exports.CSRF_TOKEN = "e3727778-7af0-4226-b5eb-f43cbe60a306" |
|||
|
|||
Loading…
Reference in new issue