mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[web] add infrastructure level zod change (#36190)
* [web] add infrastructure level zod change * [web] set req validation mode for tests to 'enforce' GitOrigin-RevId: 6dbc8bff7ed9f89045c398df93d84bca589de4e9
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
diff --git a/lib/request.js b/lib/request.js
|
||||
index f42960ca2b94b784497392cfb85ef31e4e63a1ac..7feea2ed29873e519aee0ba5e1f5b36e09fe865d 100644
|
||||
--- a/lib/request.js
|
||||
+++ b/lib/request.js
|
||||
@@ -6,6 +6,30 @@
|
||||
const InvalidArgumentError = require('./errors/invalid-argument-error');
|
||||
/* type-is: https://github.com/jshttp/type-is */
|
||||
const typeis = require('type-is');
|
||||
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||
+// The constructor below destructures query/body directly off the raw
|
||||
+// framework request object it's handed (see AuthenticationController.mjs's
|
||||
+// `new Oauth2Server.Request(req)`), which is otherwise indistinguishable
|
||||
+// from unvalidated application code reading raw request input under
|
||||
+// Overleaf's request-input lockdown (see libraries/validation-tools and the
|
||||
+// patched express in .yarn/patches/express-npm-*.patch). The lockdown
|
||||
+// captures the original parsed values in symbol-keyed fields, registered in
|
||||
+// the global symbol registry (the same ones @overleaf/validation-tools'
|
||||
+// lockdown.js reads), and only turns the *public* req.query/req.body
|
||||
+// properties into throwing accessors -- reading through these raw fields
|
||||
+// instead sidesteps that, and is identical to plain req.query/req.body when
|
||||
+// the lockdown isn't installed (i.e. for every consumer of this package
|
||||
+// outside this monorepo).
|
||||
+const __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||
+const __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||
+const __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody');
|
||||
+function __overleafRawQuery(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||
+}
|
||||
+function __overleafRawBody(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||
+}
|
||||
+// --- end Overleaf patch ---
|
||||
|
||||
/**
|
||||
* Wrapper for webserver's request.
|
||||
@@ -29,7 +53,12 @@ class Request {
|
||||
* @param otherOptions {...object} any other properties that should be assigned to the request by your webserver
|
||||
* @throws {InvalidArgumentError} if one of headers, method or query are missing.
|
||||
*/
|
||||
- constructor({ headers, method, query, body, ...otherOptions } = {}) {
|
||||
+ constructor(req = {}) {
|
||||
+ const headers = req.headers;
|
||||
+ const method = req.method;
|
||||
+ const query = __overleafRawQuery(req);
|
||||
+ const body = __overleafRawBody(req);
|
||||
+
|
||||
if (!headers) {
|
||||
throw new InvalidArgumentError('Missing parameter: `headers`');
|
||||
}
|
||||
@@ -52,7 +81,16 @@ class Request {
|
||||
this.headers[header.toLowerCase()] = value;
|
||||
});
|
||||
|
||||
- // Store additional properties of the request object passed in
|
||||
+ // Store additional properties of the request object passed in (mirrors
|
||||
+ // the original `...otherOptions` rest destructuring, but built off
|
||||
+ // Object.assign so query/body -- already handled above via the raw
|
||||
+ // lockdown fields -- aren't read a second time through the throwing
|
||||
+ // accessors).
|
||||
+ const otherOptions = Object.assign({}, req);
|
||||
+ delete otherOptions.headers;
|
||||
+ delete otherOptions.method;
|
||||
+ delete otherOptions.query;
|
||||
+ delete otherOptions.body;
|
||||
Object.entries(otherOptions)
|
||||
.filter(([property]) => !this[property])
|
||||
.forEach(([property, value]) => {
|
||||
@@ -0,0 +1,99 @@
|
||||
diff --git a/lib/strategy.js b/lib/strategy.js
|
||||
index 4de162f7e92a20b1ccd4d34dd9bcc0916f4c8c43..c67bb4e0b0287d615eac4c01b84c83d2bac91446 100644
|
||||
--- a/lib/strategy.js
|
||||
+++ b/lib/strategy.js
|
||||
@@ -5,6 +5,29 @@ const passport_strategy_1 = require("passport-strategy");
|
||||
const assert_1 = require("assert");
|
||||
const url = require("url");
|
||||
const node_saml_1 = require("@node-saml/node-saml");
|
||||
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||
+// This library reads req.query/req.body directly to parse the SAML protocol
|
||||
+// fields (SAMLResponse, SAMLRequest, RelayState) it's built around, which is
|
||||
+// otherwise indistinguishable from unvalidated application code reading raw
|
||||
+// request input under Overleaf's request-input lockdown (see
|
||||
+// libraries/validation-tools and the patched express in
|
||||
+// .yarn/patches/express-npm-*.patch). The lockdown captures the original
|
||||
+// parsed values in symbol-keyed fields, registered in the global symbol
|
||||
+// registry (the same ones @overleaf/validation-tools' lockdown.js reads), and
|
||||
+// only turns the *public* req.query/req.body properties into throwing
|
||||
+// accessors -- reading through these raw fields instead sidesteps that, and
|
||||
+// is identical to plain req.query/req.body when the lockdown isn't installed
|
||||
+// (i.e. for every consumer of this package outside this monorepo).
|
||||
+const __overleafLockdownInstalled = Symbol.for("overleaf.lockdown.installed");
|
||||
+const __overleafLockdownRawQuery = Symbol.for("overleaf.lockdown.rawQuery");
|
||||
+const __overleafLockdownRawBody = Symbol.for("overleaf.lockdown.rawBody");
|
||||
+function __overleafRawQuery(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||
+}
|
||||
+function __overleafRawBody(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||
+}
|
||||
+// --- end Overleaf patch ---
|
||||
class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
constructor(options, signonVerify, logoutVerify) {
|
||||
super();
|
||||
@@ -52,7 +75,7 @@ class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
catch (_c) {
|
||||
userMatch = false;
|
||||
}
|
||||
- const RelayState = ((_a = req.query) === null || _a === void 0 ? void 0 : _a.RelayState) || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.RelayState);
|
||||
+ const RelayState = ((_a = __overleafRawQuery(req)) === null || _a === void 0 ? void 0 : _a.RelayState) || ((_b = __overleafRawBody(req)) === null || _b === void 0 ? void 0 : _b.RelayState);
|
||||
if (this._saml == null) {
|
||||
return this.error(new Error("Can't get logout response URL without a SAML provider defined."));
|
||||
}
|
||||
@@ -125,22 +148,22 @@ class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
this.redirect(url);
|
||||
}
|
||||
};
|
||||
- if (((_a = req.query) === null || _a === void 0 ? void 0 : _a.SAMLResponse) || ((_b = req.query) === null || _b === void 0 ? void 0 : _b.SAMLRequest)) {
|
||||
+ if (((_a = __overleafRawQuery(req)) === null || _a === void 0 ? void 0 : _a.SAMLResponse) || ((_b = __overleafRawQuery(req)) === null || _b === void 0 ? void 0 : _b.SAMLRequest)) {
|
||||
const originalQuery = (_c = url.parse(req.url).query) !== null && _c !== void 0 ? _c : "";
|
||||
this._saml
|
||||
- .validateRedirectAsync(req.query, originalQuery)
|
||||
+ .validateRedirectAsync(__overleafRawQuery(req), originalQuery)
|
||||
.then(validateCallback)
|
||||
.catch((err) => this.error(err));
|
||||
}
|
||||
- else if ((_d = req.body) === null || _d === void 0 ? void 0 : _d.SAMLResponse) {
|
||||
+ else if ((_d = __overleafRawBody(req)) === null || _d === void 0 ? void 0 : _d.SAMLResponse) {
|
||||
this._saml
|
||||
- .validatePostResponseAsync(req.body)
|
||||
+ .validatePostResponseAsync(__overleafRawBody(req))
|
||||
.then(validateCallback)
|
||||
.catch((err) => this.error(err));
|
||||
}
|
||||
- else if ((_e = req.body) === null || _e === void 0 ? void 0 : _e.SAMLRequest) {
|
||||
+ else if ((_e = __overleafRawBody(req)) === null || _e === void 0 ? void 0 : _e.SAMLRequest) {
|
||||
this._saml
|
||||
- .validatePostRequestAsync(req.body)
|
||||
+ .validatePostRequestAsync(__overleafRawBody(req))
|
||||
.then(validateCallback)
|
||||
.catch((err) => this.error(err));
|
||||
}
|
||||
@@ -151,7 +174,7 @@ class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
if (this._saml == null) {
|
||||
throw new Error("Can't process login request without a SAML provider defined.");
|
||||
}
|
||||
- const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState);
|
||||
+ const RelayState = (__overleafRawQuery(req) && __overleafRawQuery(req).RelayState) || (__overleafRawBody(req) && __overleafRawBody(req).RelayState);
|
||||
const host = req.headers && req.headers.host;
|
||||
if (this._saml.options.authnRequestBinding === "HTTP-POST") {
|
||||
const data = await this._saml.getAuthorizeFormAsync(RelayState, host);
|
||||
@@ -172,7 +195,7 @@ class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
throw new Error("Can't process logout request without a SAML provider defined.");
|
||||
}
|
||||
try {
|
||||
- const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState);
|
||||
+ const RelayState = (__overleafRawQuery(req) && __overleafRawQuery(req).RelayState) || (__overleafRawBody(req) && __overleafRawBody(req).RelayState);
|
||||
// Defaults to HTTP-Redirect
|
||||
this.redirect(await this._saml.getLogoutUrlAsync(req.user, RelayState, options));
|
||||
}
|
||||
@@ -188,7 +211,7 @@ class AbstractStrategy extends passport_strategy_1.Strategy {
|
||||
if (this._saml == null) {
|
||||
throw new Error("Can't logout without a SAML provider defined.");
|
||||
}
|
||||
- const RelayState = (req.query && req.query.RelayState) || (req.body && req.body.RelayState);
|
||||
+ const RelayState = (__overleafRawQuery(req) && __overleafRawQuery(req).RelayState) || (__overleafRawBody(req) && __overleafRawBody(req).RelayState);
|
||||
this._saml
|
||||
.getLogoutUrlAsync(req.user, RelayState, {})
|
||||
.then((url) => callback(null, url))
|
||||
@@ -0,0 +1,46 @@
|
||||
diff --git a/index.js b/index.js
|
||||
index 367bdbafbc71b61daf3907913c3861d155140f07..b31ff2d98186ac29446718edfccc03a3562e796a 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -17,6 +17,30 @@ var Cookie = require('cookie')
|
||||
var createError = require('http-errors')
|
||||
var sign = require('cookie-signature').sign
|
||||
var Tokens = require('csrf')
|
||||
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||
+// The default value getter reads req.body/req.query directly to find the
|
||||
+// CSRF token submitted alongside a mutating request -- otherwise
|
||||
+// indistinguishable from unvalidated application code reading raw request
|
||||
+// input under Overleaf's request-input lockdown (see
|
||||
+// libraries/validation-tools and the patched express in
|
||||
+// .yarn/patches/express-npm-*.patch). The lockdown captures the original
|
||||
+// parsed values in symbol-keyed fields, registered in the global symbol
|
||||
+// registry (the same ones @overleaf/validation-tools' lockdown.js reads),
|
||||
+// and only turns the *public* req.query/req.body properties into throwing
|
||||
+// accessors -- reading through these raw fields instead sidesteps that, and
|
||||
+// is identical to plain req.query/req.body when the lockdown isn't
|
||||
+// installed (i.e. for every consumer of this package outside this
|
||||
+// monorepo).
|
||||
+var __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed')
|
||||
+var __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery')
|
||||
+var __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody')
|
||||
+function __overleafRawQuery (req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query
|
||||
+}
|
||||
+function __overleafRawBody (req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body
|
||||
+}
|
||||
+// --- end Overleaf patch ---
|
||||
|
||||
/**
|
||||
* Module exports.
|
||||
@@ -128,8 +152,8 @@ function csurf (options) {
|
||||
*/
|
||||
|
||||
function defaultValue (req) {
|
||||
- return (req.body && req.body._csrf) ||
|
||||
- (req.query && req.query._csrf) ||
|
||||
+ return (__overleafRawBody(req) && __overleafRawBody(req)._csrf) ||
|
||||
+ (__overleafRawQuery(req) && __overleafRawQuery(req)._csrf) ||
|
||||
(req.headers['csrf-token']) ||
|
||||
(req.headers['xsrf-token']) ||
|
||||
(req.headers['x-csrf-token']) ||
|
||||
@@ -0,0 +1,55 @@
|
||||
diff --git a/index.js b/index.js
|
||||
index 49f181005f74cf11db3bd5b0ecf512520163ec71..d47ad4270883b90766152342c99495a07cb952e0 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -1,5 +1,29 @@
|
||||
const parseCookie = require('cookie').parse;
|
||||
const decodeCookie = require('cookie-parser').signedCookie;
|
||||
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||
+// This middleware is mounted globally, before any router split, and reads
|
||||
+// req.query/req.body directly to look for a bearer token -- otherwise
|
||||
+// indistinguishable from unvalidated application code reading raw request
|
||||
+// input under Overleaf's request-input lockdown (see
|
||||
+// libraries/validation-tools and the patched express in
|
||||
+// .yarn/patches/express-npm-*.patch). The lockdown captures the original
|
||||
+// parsed values in symbol-keyed fields, registered in the global symbol
|
||||
+// registry (the same ones @overleaf/validation-tools' lockdown.js reads),
|
||||
+// and only turns the *public* req.query/req.body properties into throwing
|
||||
+// accessors -- reading through these raw fields instead sidesteps that, and
|
||||
+// is identical to plain req.query/req.body when the lockdown isn't
|
||||
+// installed (i.e. for every consumer of this package outside this
|
||||
+// monorepo).
|
||||
+const __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||
+const __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||
+const __overleafLockdownRawBody = Symbol.for('overleaf.lockdown.rawBody');
|
||||
+function __overleafRawQuery(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||
+}
|
||||
+function __overleafRawBody(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawBody] : req.body;
|
||||
+}
|
||||
+// --- end Overleaf patch ---
|
||||
|
||||
const getCookie = (serialized_cookies, key) => parseCookie(serialized_cookies)[key] || false;
|
||||
|
||||
@@ -32,16 +56,16 @@ module.exports = opts => {
|
||||
let error;
|
||||
|
||||
// query
|
||||
- if (req.query && req.query[queryKey]) {
|
||||
- token = req.query[queryKey];
|
||||
+ if (__overleafRawQuery(req) && __overleafRawQuery(req)[queryKey]) {
|
||||
+ token = __overleafRawQuery(req)[queryKey];
|
||||
}
|
||||
|
||||
// body
|
||||
- if (req.body && req.body[bodyKey]) {
|
||||
+ if (__overleafRawBody(req) && __overleafRawBody(req)[bodyKey]) {
|
||||
if (token) {
|
||||
error = true;
|
||||
}
|
||||
- token = req.body[bodyKey];
|
||||
+ token = __overleafRawBody(req)[bodyKey];
|
||||
}
|
||||
|
||||
// headers
|
||||
@@ -0,0 +1,25 @@
|
||||
diff --git a/lib/strategy.js b/lib/strategy.js
|
||||
index e54e92a86c8e3d40338ed8c6e57e8729393d85a3..cad017ef2027ad676782515a9803835138efa6b3 100644
|
||||
--- a/lib/strategy.js
|
||||
+++ b/lib/strategy.js
|
||||
@@ -66,10 +66,18 @@ util.inherits(Strategy, passport.Strategy);
|
||||
* @param {Object} req
|
||||
* @api protected
|
||||
*/
|
||||
+var LOCKDOWN_INSTALLED = Symbol.for('overleaf.lockdown.installed');
|
||||
+var LOCKDOWN_RAW_BODY = Symbol.for('overleaf.lockdown.rawBody');
|
||||
+var LOCKDOWN_RAW_QUERY = Symbol.for('overleaf.lockdown.rawQuery');
|
||||
+
|
||||
Strategy.prototype.authenticate = function(req, options) {
|
||||
options = options || {};
|
||||
- var username = lookup(req.body, this._usernameField) || lookup(req.query, this._usernameField);
|
||||
- var password = lookup(req.body, this._passwordField) || lookup(req.query, this._passwordField);
|
||||
+ // with the Overleaf request-input lockdown installed, read the raw
|
||||
+ // symbol-keyed fields; the public req.body/req.query accessors warn/throw
|
||||
+ var body = req[LOCKDOWN_INSTALLED] ? req[LOCKDOWN_RAW_BODY] : req.body;
|
||||
+ var query = req[LOCKDOWN_INSTALLED] ? req[LOCKDOWN_RAW_QUERY] : req.query;
|
||||
+ var username = lookup(body, this._usernameField) || lookup(query, this._usernameField);
|
||||
+ var password = lookup(body, this._passwordField) || lookup(query, this._passwordField);
|
||||
|
||||
if (!username || !password) {
|
||||
return this.fail({ message: options.badRequestMessage || 'Missing credentials' }, 400);
|
||||
@@ -1,10 +1,82 @@
|
||||
diff --git a/lib/strategy.js b/lib/strategy.js
|
||||
index b05aacce9a16fd32743a989b68f179bb35b20545..de1293a75c82452089eea37ab1031d4bbe42849c 100644
|
||||
--- a/lib/strategy.js
|
||||
+++ b/lib/strategy.js
|
||||
@@ -14,6 +14,25 @@ var passport = require('passport-strategy')
|
||||
, AuthorizationError = require('./errors/authorizationerror')
|
||||
, TokenError = require('./errors/tokenerror')
|
||||
, InternalOAuthError = require('./errors/internaloautherror');
|
||||
+// --- Overleaf patch: request-input lockdown compatibility ---
|
||||
+// This strategy reads req.query directly to parse the OAuth2 protocol
|
||||
+// fields (error/error_description/error_uri, code, state) it's built
|
||||
+// around, which is otherwise indistinguishable from unvalidated application
|
||||
+// code reading raw request input under Overleaf's request-input lockdown
|
||||
+// (see libraries/validation-tools and the patched express in
|
||||
+// .yarn/patches/express-npm-*.patch). The lockdown captures the original
|
||||
+// parsed values in symbol-keyed fields, registered in the global symbol
|
||||
+// registry (the same ones @overleaf/validation-tools' lockdown.js reads),
|
||||
+// and only turns the *public* req.query property into a throwing accessor
|
||||
+// -- reading through this raw field instead sidesteps that, and is
|
||||
+// identical to plain req.query when the lockdown isn't installed (i.e. for
|
||||
+// every consumer of this package outside this monorepo).
|
||||
+var __overleafLockdownInstalled = Symbol.for('overleaf.lockdown.installed');
|
||||
+var __overleafLockdownRawQuery = Symbol.for('overleaf.lockdown.rawQuery');
|
||||
+function __overleafRawQuery(req) {
|
||||
+ return req[__overleafLockdownInstalled] ? req[__overleafLockdownRawQuery] : req.query;
|
||||
+}
|
||||
+// --- end Overleaf patch ---
|
||||
|
||||
|
||||
/**
|
||||
@@ -131,11 +150,11 @@ OAuth2Strategy.prototype.authenticate = function(req, options) {
|
||||
options = options || {};
|
||||
var self = this;
|
||||
|
||||
- if (req.query && req.query.error) {
|
||||
- if (req.query.error == 'access_denied') {
|
||||
- return this.fail({ message: req.query.error_description });
|
||||
+ if (__overleafRawQuery(req) && __overleafRawQuery(req).error) {
|
||||
+ if (__overleafRawQuery(req).error == 'access_denied') {
|
||||
+ return this.fail({ message: __overleafRawQuery(req).error_description });
|
||||
} else {
|
||||
- return this.error(new AuthorizationError(req.query.error_description, req.query.error, req.query.error_uri));
|
||||
+ return this.error(new AuthorizationError(__overleafRawQuery(req).error_description, __overleafRawQuery(req).error, __overleafRawQuery(req).error_uri));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,14 +175,14 @@ OAuth2Strategy.prototype.authenticate = function(req, options) {
|
||||
callbackURL: callbackURL
|
||||
}
|
||||
|
||||
- if (req.query && req.query.code) {
|
||||
+ if (__overleafRawQuery(req) && __overleafRawQuery(req).code) {
|
||||
function loaded(err, ok, state) {
|
||||
if (err) { return self.error(err); }
|
||||
if (!ok) {
|
||||
return self.fail(state, 403);
|
||||
}
|
||||
|
||||
- var code = req.query.code;
|
||||
+ var code = __overleafRawQuery(req).code;
|
||||
|
||||
var params = self.tokenParams(options);
|
||||
params.grant_type = 'authorization_code';
|
||||
@@ -213,7 +232,7 @@ OAuth2Strategy.prototype.authenticate = function(req, options) {
|
||||
);
|
||||
}
|
||||
|
||||
- var state = req.query.state;
|
||||
+ var state = __overleafRawQuery(req).state;
|
||||
try {
|
||||
var arity = this._stateStore.verify.length;
|
||||
if (arity == 4) {
|
||||
diff --git a/lib/utils.js b/lib/utils.js
|
||||
index 486f9e1..4584507 100644
|
||||
--- a/lib/utils.js
|
||||
+++ b/lib/utils.js
|
||||
@@ -24,7 +24,7 @@ exports.originalURL = function(req, options) {
|
||||
var trustProxy = options.proxy;
|
||||
|
||||
|
||||
var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
|
||||
- , tls = req.connection.encrypted || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0])
|
||||
+ , tls = (req.connection && req.connection.encrypted) || (trustProxy && 'https' == proto.split(/\s*,\s*/)[0])
|
||||
|
||||
Reference in New Issue
Block a user