middleware
access-token-paser
src/server/middlewares/csrf.jsconst 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.jsconst 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); }; };
- CSRFとは? サイバー攻撃&対策【分かりやすい解説シリーズ #58】【プログラミング】 external_link
- csrfミドルウェアは、フォーム画面から送信されたリクエストであることを保証するためのもの
- よって外からapiを叩くと通らない
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.jsrouter.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.jsapp.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
ユーザー設定のパスワード更新で取得しているuserData
、serializeUserSecurely
通らずにresponseされているけど大丈夫なのだろうか
accessTokenParser
,loginRequiredStrictly
,csrf
があるからserializeUserSecurely
なくてもいいという判断かな
personal-settings.jsrouter.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.jsconst csrfKey = (req.session && req.session.id) || 'anon';
Commments