فهرست منبع

impl safeRedirect middleware

Yuki Takei 6 سال پیش
والد
کامیت
05dc6b2d3a

+ 4 - 0
src/server/crowi/express-init.js

@@ -19,6 +19,8 @@ module.exports = function(crowi, app) {
   const i18nSprintf = require('i18next-sprintf-postprocessor');
   const i18nMiddleware = require('i18next-express-middleware');
 
+  const safeRedirect = require('../middleware/safe-redirect');
+
   const avoidSessionRoutes = require('../routes/avoid-session-routes');
   const i18nUserSettingDetector = require('../util/i18nUserSettingDetector');
 
@@ -113,6 +115,8 @@ module.exports = function(crowi, app) {
 
   app.use(flash());
 
+  app.use(safeRedirect);
+
   const middlewares = require('../util/middlewares')(crowi, app);
 
   app.use(middlewares.swigFilters(swig));

+ 1 - 1
src/server/middleware/login-required.js

@@ -42,7 +42,7 @@ module.exports = (crowi, isGuestAllowed = false) => {
       return res.sendStatus(403);
     }
 
-    req.session.jumpTo = req.originalUrl;
+    req.session.redirectTo = req.originalUrl;
     return res.redirect('/login');
   };
 

+ 35 - 0
src/server/middleware/safe-redirect.js

@@ -0,0 +1,35 @@
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:middleware:safe-redirect');
+
+/**
+ * Redirect with prevention from Open Redirect
+ *
+ * Usage: app.use(require('middleware/safe-redirect'))
+ */
+module.exports = function(req, res, next) {
+
+  // extends res object
+  res.safeRedirect = function(redirectTo) {
+    if (redirectTo == null) {
+      return res.redirect('/');
+    }
+
+    // prevention from open redirect
+    try {
+      const redirectUrl = new URL(redirectTo, `${req.protocol}://${req.get('host')}`);
+      if (redirectUrl.hostname === req.hostname) {
+        return res.redirect(redirectUrl);
+      }
+      logger.warn('Requested redirect URL is invalid, redirect to root page');
+    }
+    catch (err) {
+      logger.warn('Requested redirect URL is invalid, redirect to root page', err);
+    }
+
+    return res.redirect('/');
+  };
+
+  next();
+
+};

+ 1 - 19
src/server/routes/login-passport.js

@@ -22,25 +22,7 @@ module.exports = function(crowi, app) {
       }
     });
 
-    const jumpTo = req.session.jumpTo;
-    if (jumpTo) {
-      req.session.jumpTo = null;
-
-      // prevention from open redirect
-      try {
-        const redirectUrl = new URL(jumpTo, `${req.protocol}://${req.get('host')}`);
-        if (redirectUrl.hostname === req.hostname) {
-          return res.redirect(redirectUrl);
-        }
-        logger.warn('Requested redirect URL is invalid, redirect to root page');
-      }
-      catch (err) {
-        logger.warn('Requested redirect URL is invalid, redirect to root page', err);
-        return res.redirect('/');
-      }
-    }
-
-    return res.redirect('/');
+    return res.safeRedirect(req.session.redirectTo);
   };
 
   /**

+ 1 - 19
src/server/routes/login.js

@@ -30,25 +30,7 @@ module.exports = function(crowi, app) {
       return res.redirect('/me/password');
     }
 
-    const jumpTo = req.session.jumpTo;
-    if (jumpTo) {
-      req.session.jumpTo = null;
-
-      // prevention from open redirect
-      try {
-        const redirectUrl = new URL(jumpTo, `${req.protocol}://${req.get('host')}`);
-        if (redirectUrl.hostname === req.hostname) {
-          return res.redirect(redirectUrl);
-        }
-        logger.warn('Requested redirect URL is invalid, redirect to root page');
-      }
-      catch (err) {
-        logger.warn('Requested redirect URL is invalid, redirect to root page', err);
-        return res.redirect('/');
-      }
-    }
-
-    return res.redirect('/');
+    return res.safeRedirect(req.session.redirectTo);
   };
 
   const loginFailure = function(req, res) {

+ 1 - 1
src/server/routes/logout.js

@@ -8,7 +8,7 @@ module.exports = function(crowi, app) {
       // parse referer url
       const referer = new URL(req.headers.referer);
       // redirect
-      return res.redirect(`${referer.pathname}${referer.search}${referer.hash}`);
+      return res.safeRedirect(`${referer.pathname}${referer.search}${referer.hash}`);
     },
   };
 };

+ 4 - 4
src/test/middleware/login-required.test.js

@@ -101,7 +101,7 @@ describe('loginRequired', () => {
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('/login');
       expect(result).toBe('redirect');
-      expect(req.session.jumpTo).toBe('original url 1');
+      expect(req.session.redirectTo).toBe('original url 1');
     });
 
     test('pass user who logged in', () => {
@@ -119,7 +119,7 @@ describe('loginRequired', () => {
       expect(res.redirect).not.toHaveBeenCalled();
       expect(next).toHaveBeenCalledTimes(1);
       expect(result).toBe('next');
-      expect(req.session.jumpTo).toBe(undefined);
+      expect(req.session.redirectTo).toBe(undefined);
     });
 
     /* eslint-disable indent */
@@ -142,7 +142,7 @@ describe('loginRequired', () => {
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith(expectedPath);
       expect(result).toBe('redirect');
-      expect(req.session.jumpTo).toBe(undefined);
+      expect(req.session.redirectTo).toBe(undefined);
     });
     /* eslint-disable indent */
 
@@ -163,7 +163,7 @@ describe('loginRequired', () => {
       expect(res.redirect).toHaveBeenCalledTimes(1);
       expect(res.redirect).toHaveBeenCalledWith('/login');
       expect(result).toBe('redirect');
-      expect(req.session.jumpTo).toBe('original url 1');
+      expect(req.session.redirectTo).toBe('original url 1');
     });
 
   });