Repository navigation
bug: require login, ownership and CSRF token on todo routes (sfind-ff2cbbbc) - #1104
devin-ai-integration[bot] wants to merge 2 commits into
Conversation
…ate/destroy/import Todo routes were registered without isLoggedIn and destroy/update loaded any todo by URL id, so anonymous visitors could rewrite or delete every todo (destroy was a GET, so an <img> could trigger it). - create/edit/update/destroy/import now go through isLoggedIn - destroy is POST-only; mutating routes check a per-session CSRF token - todos store an owner (session username, set at login) and edit/update/destroy only match the caller's own todos (404 otherwise) - views show edit/delete controls only for the owner's todos Refs: sfind-ff2cbbbc371c404c904ae667c8abf99c
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
There was a problem hiding this comment.
Devin Review found 3 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
| function currentUsername(req) { | ||
| var username = req.session && req.session.username; | ||
| return req.session && req.session.loggedIn === 1 && typeof username === 'string' && username ? username : null; |
There was a problem hiding this comment.
🟡 Existing sessions lose todo controls
After deployment, currentUsername rejects existing logged-in sessions without a username. isLoggedIn still accepts them, so todo controls disappear and edits return 404 until the user logs in again.
Learn more
Sessions created before this change have loggedIn but no username, because login did not previously store it. isLoggedIn accepts these sessions, while the new username check fails. The index then hides the create and ownership controls, and the ownership lookup returns no todo. An API client that submits a valid token to create/import can also persist a todo with owner: null.
Example: Alice logs in before deployment and keeps her session cookie. After deployment, she visits / and sees a login prompt instead of her todo controls. /edit/<her-id> returns 404 until she signs in again.
Recommended fix: Handle the missing username as an expired session and require reauthentication before allowing todo writes; alternatively migrate sessions safely. Ensure the write routes reject a missing owner rather than saving null.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (err) return next(err); | ||
| res.redirect('/'); | ||
| }); | ||
| }); | ||
| }; | ||
|
|
||
| exports.edit = function (req, res, next) { | ||
| Todo. | ||
| find({}). | ||
| sort('-updated_at'). | ||
| exec(function (err, todos) { | ||
| if (err) return next(err); | ||
| findOwnedTodo(req, function (err, owned) { | ||
| if (err) return next(err); |
Validação no navegador — 46f5e1dRodei o app real localmente com um stand-in de mongoose em memória. Persistência com MongoDB real não foi validada: o mongod 7 local não fala com o mongoose 4.2.4 e o download de uma versão compatível foi bloqueado pela rede.
|
Resumo
Corrige a falta de autorização nas rotas de todo (finding
sfind-ff2cbbbc, severidade média,routes/index.js:152,190,191,203,218,219,241).Antes,
app.jsregistrava/create,/destroy/:id,/edit/:id,/update/:ide/importsemroutes.isLoggedIn.destroyeupdatecarregavam qualquer todo pelo id da URL, sem checar o dono./destroy/:idera um GET, então uma tag<img>em outro site já apagava um todo. Um visitante anônimo podia apagar ou reescrever todos os todos.Mudanças:
app.js: as 5 rotas passam porroutes.isLoggedIn;create/destroy/update/importtambém passam porroutes.verifyCsrf./destroy/:idagora é sóPOST(GET dá 404).mongoose-db.js:Todoganha o campoowner(String).routes/index.js:session.username;createeimportgravamowner;edit/update/destroyusamfindOwnedTodo(Todo.findOne({ _id, owner })) e devolvem 404 se o todo não é do usuário, se o id é inválido ou se a sessão não tem username;verifyCsrfcompara_csrf(body) ouX-CSRF-Tokencom o token da sessão viacrypto.timingSafeEqual; sem token ou com token errado dá 403;current_usercria o token e expõecsrfToken/currentUseremres.locals._csrfhidden. O delete virou um form POST com botão. Editar e apagar só aparecem nos todos do próprio usuário. Visitante anônimo vê a lista, mais um link "Log in".screen.cssestiliza o botão de delete como o link antigo.tests/todo-authz.test.js(node:test, sem dependências novas): 11 casos (inclui token CSRF não-ASCII e sessão anônima sem token).Mudanças de contrato da API:
GET /destroy/:idfoi removido. Agora éPOST /destroy/:idcom_csrf.POST /create,/update/:ide/importexigem sessão logada e_csrf.owner, ficam só leitura.exploits/(ms-exploits.sh,mongoose-exploits.sh,moment-exploit.sh) chamam/createe/importsem sessão. Agora eles recebem redirect para/. Para reproduzir esses demos, é preciso enviar cookie de sessão e_csrf.Como testar
NODE_OPTIONS=--openssl-legacy-provider node --test tests/todo-authz.test.js: 11/11 neste branch. Emmainfalha, porqueverifyCsrfnão existe edestroy/updatenão checam o dono.app.jsreal (node -r memdb.js app.js, um Mongo em memória, porque o mongod 7 local não é compatível com mongoose 4.2.4). alice =[REDACTED SECRET]@snyk.io, bob = segundo usuário:GET /destroy/<id da alice>POST /destroy/<id>/, nada apagadoPOST /update/<id>/, sem mudançaPOST /create/POST /import/, nada criadoGET /edit/<id>/POST /update/POST /destroy/GET /editno todo da alicePOST /destroysem_csrfou com_csrferrado_csrfnpm test(=snyk test) não roda aqui: precisa de autenticação/rede no Snyk.Devin-Org: engineering
Link to Devin session: https://devin-gtm.devinenterprise.com/sessions/75569c389ead44e0b4e2b3b54e482e59
Open in Devin Desktop: https://devin-gtm.devinenterprise.com/desktop/session/75569c389ead44e0b4e2b3b54e482e59?variant=devin
Devin Review