mirror of
https://github.com/sasjs/server.git
synced 2026-01-07 06:30:06 +00:00
fix: csrf package is changed to pillarjs-csrf
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import path from 'path'
|
||||
import express, { ErrorRequestHandler } from 'express'
|
||||
import csrf, { CookieOptions } from 'csurf'
|
||||
import express, { ErrorRequestHandler, CookieOptions } from 'express'
|
||||
import cookieParser from 'cookie-parser'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
@@ -39,15 +38,7 @@ export const cookieOptions: CookieOptions = {
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
}
|
||||
|
||||
/***********************************
|
||||
* CSRF Protection *
|
||||
***********************************/
|
||||
export const csrfProtection = csrf({ cookie: cookieOptions })
|
||||
|
||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN')
|
||||
return res.status(400).send('Invalid CSRF token!')
|
||||
|
||||
console.error(err.stack)
|
||||
res.status(500).send('Something broke!')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RequestHandler, Request, Response, NextFunction } from 'express'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import { csrfProtection } from '../app'
|
||||
import { csrfProtection } from './'
|
||||
import {
|
||||
fetchLatestAutoExec,
|
||||
ModeType,
|
||||
|
||||
@@ -10,9 +10,7 @@ import { getPath, isPublicRoute } from '../utils'
|
||||
export const authorize: RequestHandler = async (req, res, next) => {
|
||||
const { user } = req
|
||||
|
||||
if (!user) {
|
||||
return res.sendStatus(401)
|
||||
}
|
||||
if (!user) return res.sendStatus(401)
|
||||
|
||||
// no need to check for permissions when user is admin
|
||||
if (user.isAdmin) return next()
|
||||
|
||||
32
api/src/middlewares/csrfProtection.ts
Normal file
32
api/src/middlewares/csrfProtection.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { RequestHandler } from 'express'
|
||||
import csrf from 'csrf'
|
||||
|
||||
const csrfTokens = new csrf()
|
||||
const secret = csrfTokens.secretSync()
|
||||
|
||||
export const generateCSRFToken = () => csrfTokens.create(secret)
|
||||
|
||||
export const csrfProtection: RequestHandler = (req, res, next) => {
|
||||
if (req.method === 'GET') return next()
|
||||
|
||||
// The default value is a function that reads the token from the following locations, in order:
|
||||
// req.body._csrf - typically generated by the body-parser module.
|
||||
// req.query._csrf - a built-in from Express.js to read from the URL query string.
|
||||
// req.headers['csrf-token'] - the CSRF-Token HTTP request header.
|
||||
// req.headers['xsrf-token'] - the XSRF-Token HTTP request header.
|
||||
// req.headers['x-csrf-token'] - the X-CSRF-Token HTTP request header.
|
||||
// req.headers['x-xsrf-token'] - the X-XSRF-Token HTTP request header.
|
||||
|
||||
const token =
|
||||
req.body?._csrf ||
|
||||
req.query?._csrf ||
|
||||
req.headers['csrf-token'] ||
|
||||
req.headers['xsrf-token'] ||
|
||||
req.headers['x-csrf-token'] ||
|
||||
req.headers['x-xsrf-token']
|
||||
|
||||
if (!csrfTokens.verify(secret, token)) {
|
||||
return res.status(400).send('Invalid CSRF token!')
|
||||
}
|
||||
next()
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './authenticateToken'
|
||||
export * from './authorize'
|
||||
export * from './csrfProtection'
|
||||
export * from './desktop'
|
||||
export * from './verifyAdmin'
|
||||
export * from './verifyAdminIfNeeded'
|
||||
export * from './authorize'
|
||||
|
||||
@@ -49,10 +49,9 @@ describe('web', () => {
|
||||
|
||||
describe('SASLogon/login', () => {
|
||||
let csrfToken: string
|
||||
let cookies: string
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ csrfToken, cookies } = await getCSRF(app))
|
||||
;({ csrfToken } = await getCSRF(app))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -66,7 +65,6 @@ describe('web', () => {
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.set('Cookie', cookies)
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({
|
||||
username: user.username,
|
||||
@@ -82,15 +80,45 @@ describe('web', () => {
|
||||
isAdmin: user.isAdmin
|
||||
})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if CSRF Token is not present', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual('Invalid CSRF token!')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if CSRF Token is invalid', async () => {
|
||||
await userController.createUser(user)
|
||||
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.set('x-xsrf-token', 'INVALID_CSRF_TOKEN')
|
||||
.send({
|
||||
username: user.username,
|
||||
password: user.password
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual('Invalid CSRF token!')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
describe('SASLogon/authorize', () => {
|
||||
let csrfToken: string
|
||||
let cookies: string
|
||||
let authCookies: string
|
||||
|
||||
beforeAll(async () => {
|
||||
;({ csrfToken, cookies } = await getCSRF(app))
|
||||
;({ csrfToken } = await getCSRF(app))
|
||||
|
||||
await userController.createUser(user)
|
||||
|
||||
@@ -99,12 +127,7 @@ describe('web', () => {
|
||||
password: user.password
|
||||
}
|
||||
|
||||
;({ cookies: authCookies } = await performLogin(
|
||||
app,
|
||||
credentials,
|
||||
cookies,
|
||||
csrfToken
|
||||
))
|
||||
;({ authCookies } = await performLogin(app, credentials, csrfToken))
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -116,17 +139,28 @@ describe('web', () => {
|
||||
it('should respond with authorization code', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('Cookie', [authCookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({ clientId })
|
||||
|
||||
expect(res.body).toHaveProperty('code')
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if CSRF Token is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies].join('; '))
|
||||
.send({ clientId })
|
||||
.expect(400)
|
||||
|
||||
expect(res.text).toEqual('Invalid CSRF token!')
|
||||
expect(res.body).toEqual({})
|
||||
})
|
||||
|
||||
it('should respond with Bad Request if clientId is missing', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('Cookie', [authCookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({})
|
||||
.expect(400)
|
||||
@@ -138,7 +172,7 @@ describe('web', () => {
|
||||
it('should respond with Forbidden if clientId is incorrect', async () => {
|
||||
const res = await request(app)
|
||||
.post('/SASLogon/authorize')
|
||||
.set('Cookie', [authCookies, cookies].join('; '))
|
||||
.set('Cookie', [authCookies].join('; '))
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send({
|
||||
clientId: 'WrongClientID'
|
||||
@@ -153,27 +187,22 @@ describe('web', () => {
|
||||
|
||||
const getCSRF = async (app: Express) => {
|
||||
// make request to get CSRF
|
||||
const { header, text } = await request(app).get('/')
|
||||
const cookies = header['set-cookie'].join()
|
||||
const { text } = await request(app).get('/')
|
||||
|
||||
const csrfToken = extractCSRF(text)
|
||||
return { csrfToken, cookies }
|
||||
return { csrfToken: extractCSRF(text) }
|
||||
}
|
||||
|
||||
const performLogin = async (
|
||||
app: Express,
|
||||
credentials: { username: string; password: string },
|
||||
cookies: string,
|
||||
csrfToken: string
|
||||
) => {
|
||||
const { header } = await request(app)
|
||||
.post('/SASLogon/login')
|
||||
.set('Cookie', cookies)
|
||||
.set('x-xsrf-token', csrfToken)
|
||||
.send(credentials)
|
||||
|
||||
const newCookies: string = header['set-cookie'].join()
|
||||
return { cookies: newCookies }
|
||||
return { authCookies: header['set-cookie'].join() }
|
||||
}
|
||||
|
||||
const extractCSRF = (text: string) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import path from 'path'
|
||||
import express, { Request } from 'express'
|
||||
import { authenticateAccessToken } from '../../middlewares'
|
||||
import { authenticateAccessToken, generateCSRFToken } from '../../middlewares'
|
||||
import { folderExists } from '@sasjs/utils'
|
||||
|
||||
import { addEntryToAppStreamConfig, getFilesFolder } from '../../utils'
|
||||
@@ -13,7 +13,7 @@ const router = express.Router()
|
||||
router.get('/', authenticateAccessToken, async (req, res) => {
|
||||
const content = appStreamHtml(process.appStreamConfig)
|
||||
|
||||
res.cookie('XSRF-TOKEN', req.csrfToken())
|
||||
res.cookie('XSRF-TOKEN', generateCSRFToken())
|
||||
|
||||
return res.send(content)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import webRouter from './web'
|
||||
import apiRouter from './api'
|
||||
import appStreamRouter from './appStream'
|
||||
|
||||
import { csrfProtection } from '../app'
|
||||
import { csrfProtection } from '../middlewares'
|
||||
|
||||
export const setupRoutes = (app: Express) => {
|
||||
app.use('/SASjsApi', apiRouter)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import express from 'express'
|
||||
import { generateCSRFToken } from '../../middlewares'
|
||||
import { WebController } from '../../controllers'
|
||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
||||
|
||||
@@ -15,7 +16,7 @@ sas9WebRouter.get('/', async (req, res) => {
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import express from 'express'
|
||||
import { generateCSRFToken } from '../../middlewares'
|
||||
import { WebController } from '../../controllers/web'
|
||||
|
||||
const sasViyaWebRouter = express.Router()
|
||||
@@ -11,7 +12,7 @@ sasViyaWebRouter.get('/', async (req, res) => {
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import express from 'express'
|
||||
import { generateCSRFToken } from '../../middlewares'
|
||||
import { WebController } from '../../controllers/web'
|
||||
import { authenticateAccessToken, desktopRestrict } from '../../middlewares'
|
||||
import { authorizeValidation, loginWebValidation } from '../../utils'
|
||||
@@ -13,7 +14,7 @@ webRouter.get('/', async (req, res) => {
|
||||
} catch (_) {
|
||||
response = '<html><head></head><body>Web Build is not present</body></html>'
|
||||
} finally {
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${generateCSRFToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
|
||||
const injectedContent = response?.replace(
|
||||
'</head>',
|
||||
`${codeToInject}</head>`
|
||||
|
||||
Reference in New Issue
Block a user