middleware

access-token-paser

src/server/middlewares/csrf.js
const loggerFactory = require('@alias/logger'); const { serializeUserSecurely } = require('../models/serializers/user-serializer'); const logger = loggerFactory('growi:middleware:access-token-parser'); module.exports = (crowi) => { return async(req, res, next) => { // TODO: comply HTTP header of RFC6750 / Authorization: Bearer const accessToken = req.query.access_token || req.body.access_token || null; if (accessToken == null || typeof accessToken !== 'string') { return next(); } const User = crowi.model('User'); logger.debug('accessToken is', accessToken); const user = await User.findUserByApiToken(accessToken); if (user == null) { logger.debug('The access token is invalid'); return next(); } // transforming attributes req.user = serializeUserSecurely(user); req.skipCsrfVerify = true; logger.debug('Access token parsed: skipCsrfVerify'); return next(); }; };

csrf (Cross-site request forgery)

src/server/middlewares/csrf.js
const loggerFactory = require('@alias/logger'); const logger = loggerFactory('growi:middleware:csrf'); module.exports = (crowi) => { return async(req, res, next) => { const token = req.body._csrf || req.query._csrf || null; const csrfKey = (req.session && req.session.id) || 'anon'; logger.debug('req.skipCsrfVerify', req.skipCsrfVerify); if (req.skipCsrfVerify) { logger.debug('csrf verify skipped'); return next(); } if (crowi.getTokens().verify(csrfKey, token)) { logger.debug('csrf successfully verified'); return next(); } logger.warn('csrf verification failed. return 403', csrfKey, token); return res.sendStatus(403); }; };

access-token-paser と csrfの関係

  • access-token-paserで req.skipCsrfVerify = true;にしている。
  • つまり、アクセストークンを持っていたらskipCsrfVerifyされて、外からもapiが叩けるようになるということ。
  • middlewareに access-token-paser と csrfの両方が入る場合、順番としてはaccess-token-paser -> csrf の順番で書く必要がある。

apiV3FormValidator

  • middlewareにオリジナルのvalidater(e.g. validator.hoge)を追加したときに、そのvalidationが引っかかった場合に適切にエラーを吐くようにapiV3FormValidatorを追加するという認識。
apiv3/export.js
router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, csrf, async(req, res) => { // TODO: add express validator const { fileName } = req.params; try { const zipFile = exportService.getFile(fileName); fs.unlinkSync(zipFile); // TODO: use res.apiv3 return res.status(200).send({ ok: true }); } catch (err) { // TODO: use ApiV3Error logger.error(err); return res.status(500).send({ ok: false }); } });

疑問点

1

  • csrfがmiddlewareにセットされている時は、access-token-parserもセットであると思いきやそういうわけではなさそう
    • なんでaccess-token-paserがない?avoidされている
routes/index.js
app.post('/_api/pages.remove' , loginRequiredStrictly , csrf, page.api.remove); // (Avoid from API Token) app.post('/_api/pages.revertRemove' , loginRequiredStrictly , csrf, page.api.revertRemove); // (Avoid from API Token) app.post('/_api/pages.unlink' , loginRequiredStrictly , csrf, page.api.unlink); // (Avoid from API Token)

2

ユーザー設定のパスワード更新で取得しているuserDataserializeUserSecurely通らずにresponseされているけど大丈夫なのだろうか

  • accessTokenParser, loginRequiredStrictly, csrf があるからserializeUserSecurelyなくてもいいという判断かな
personal-settings.js
router.put('/password', accessTokenParser, loginRequiredStrictly, csrf, validator.password, apiV3FormValidator, async(req, res) => { const { body, user } = req; const { oldPassword, newPassword } = body; if (user.isPasswordSet() && !user.isPasswordValid(oldPassword)) { return res.apiv3Err('wrong-current-password', 400); } try { const userData = await user.updatePassword(newPassword); return res.apiv3({ userData }); } catch (err) { logger.error(err); return res.apiv3Err('update-password-failed'); } });

3

  • anonってanonymousの略だと思うんだけど、nullにしていないのは なんでだろう
src/server/middlewares/csrf.js
const csrfKey = (req.session && req.session.id) || 'anon';

Commments