first pass at CSRF protection

This commit is contained in:
shayneczyzewski 2022-06-13 15:05:45 -04:00
parent 5cc092d5ca
commit 0762bc9b8b
7 changed files with 47 additions and 9 deletions

View File

@ -6,6 +6,15 @@ const api = axios.create({
withCredentials: true
})
api.interceptors.request.use(request => {
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
if (csrfToken) {
request.headers['csrf-token'] = csrfToken
}
return request
})
/**
* Takes an error returned by the app's API (as returned by axios), and transforms into a more
* standard format to be further used by the client. It is also assumed that given API

View File

@ -2,6 +2,8 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { QueryClientProvider } from 'react-query'
import api from './api.js'
import config from './config.js'
import router from './router'
import {
@ -24,6 +26,8 @@ async function startApp() {
{=/ doesClientSetupFnExist =}
initializeQueryClient()
await setCsrfToken()
await render()
// If you want your app to work offline and load faster, you can change
@ -32,6 +36,16 @@ async function startApp() {
serviceWorker.unregister()
}
// TODO: Chat on options. Pretty hacky.
async function setCsrfToken() {
const token = await api.get(config.apiUrl + '/csrf-token')
const meta = document.createElement('meta')
meta.name = "csrf-token"
meta.content = token.data
document.getElementsByTagName('head')[0].appendChild(meta)
}
async function render() {
const queryClient = await queryClientInitialized
ReactDOM.render(

View File

@ -29,7 +29,6 @@ app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
{=# isAuthEnabled =}
// TODO: Add CSRF protection.
useSession(app)
{=/ isAuthEnabled =}

View File

@ -15,7 +15,7 @@ const config = {
databaseUrl: process.env.DATABASE_URL,
{=# isAuthEnabled =}
session: {
name: process.env.SESSION_NAME || 'wasp',
name: process.env.SESSION_NAME || 'wasp_sid',
secret: undefined,
cookie: {
maxAge: parseInt(process.env.SESSION_COOKIE_MAX_AGE) || 7 * 24 * 60 * 60 * 1000, // ms

View File

@ -14,6 +14,10 @@ router.get('/', function (req, res, next) {
{=# isAuthEnabled =}
router.use('/auth', auth)
router.get('/csrf-token', function (req, res) {
res.json(req.csrfToken())
})
{=/ isAuthEnabled =}
router.use('/{= operationsRouteInRootRouter =}', operations)

View File

@ -1,11 +1,12 @@
{{={= =}=}}
import session from 'express-session'
import { PrismaSessionStore } from '@quixo3/prisma-session-store'
import csrf from 'csurf'
import config from './config.js'
import prisma from './dbClient.js'
const sess = {
const sessionConfig = {
name: config.session.name,
secret: config.session.secret,
// NOTE: The two options below are kinda finiky with PrismaSessionStore.
@ -25,9 +26,19 @@ const sess = {
})
}
export function useSession(app) {
if (app.get('env') === 'production') {
sess.cookie.secure = true
}
app.use(session(sess))
const csrfConfig = {
cookie: {
key: 'wasp_csrf',
httpOnly: true,
},
}
export function useSession(app) {
if (config.env === 'production') {
sessionConfig.cookie.secure = true
csurfConfig.cookie.secure = true
}
app.use(session(sessionConfig))
app.use(csrf(csrfConfig))
}

View File

@ -136,7 +136,8 @@ depsRequiredBySessions spec =
if isAuthEnabled spec
then
[ ("express-session", "~1.17.3"),
("@quixo3/prisma-session-store", "~3.1.5")
("@quixo3/prisma-session-store", "~3.1.5"),
("csurf", "~1.11.0")
]
else []
in AS.Dependency.make <$> deps