Skip to content

Commit

Permalink
Merge pull request from GHSA-qrgf-9gpc-vrxw
Browse files Browse the repository at this point in the history
* fix: enforce hmac key

* fix: check only if fastify/cookie is enabled

* fix: restored working testts

* fix: update README.md

Co-authored-by: Uzlopak <aras.abbasi@googlemail.com>

* Update index.d.ts

* Update index.test-d.ts

* Update index.d.ts

* Update index.test-d.ts

* fix unit tests

---------

Co-authored-by: Uzlopak <aras.abbasi@googlemail.com>
  • Loading branch information
marco-ippolito and Uzlopak committed Apr 20, 2023
1 parent f60148c commit be3e576
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -161,6 +161,7 @@ You can also pass the [cookie serialization](https://github.com/fastify/fastify-
The option `userInfo` is required if `getUserInfo` has been specified in the module option.
The provided `userInfo` is hashed inside the csrf token and it is not directly exposed.
This option is needed to protect against cookie tossing.
The option `csrfOpts.hmacKey` is required if `getUserInfo` has been specified in the module option in combination with using [@fastify/cookie](https://github.com/fastify/fastify-cookie) as sessionPlugin

### `fastify.csrfProtection(request, reply, next)`

Expand Down
5 changes: 5 additions & 0 deletions index.js
Expand Up @@ -42,6 +42,11 @@ async function fastifyCsrfProtection (fastify, opts) {
if (opts.getUserInfo) {
csrfOpts.userInfo = true
}

if (sessionPlugin === '@fastify/cookie' && csrfOpts.userInfo) {
assert(csrfOpts.hmacKey, 'csrfOpts.hmacKey is required')
}

const tokens = new CSRF(csrfOpts)

const isCookieSigned = cookieOpts && cookieOpts.signed
Expand Down
88 changes: 88 additions & 0 deletions test/user-info.test.js
Expand Up @@ -17,6 +17,9 @@ test('Cookies with User-Info', async t => {
await fastify.register(fastifyCsrf, {
getUserInfo (req) {
return userInfoDB[req.body.username]
},
csrfOpts: {
hmacKey: 'foo'
}
})

Expand Down Expand Up @@ -73,6 +76,9 @@ test('Session with User-Info', async t => {
sessionPlugin: '@fastify/session',
getUserInfo (req) {
return req.session.username
},
csrfOpts: {
hmacKey: 'foo'
}
})

Expand Down Expand Up @@ -122,6 +128,9 @@ test('SecureSession with User-Info', async t => {
sessionPlugin: '@fastify/secure-session',
getUserInfo (req) {
return req.session.get('username')
},
csrfOpts: {
hmacKey: 'foo'
}
})

Expand Down Expand Up @@ -163,3 +172,82 @@ test('SecureSession with User-Info', async t => {

t.equal(response2.statusCode, 200)
})

test('Validate presence of hmac key with User-Info /1', async (t) => {
const fastify = Fastify()
await fastify.register(fastifyCookie)

await t.rejects(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
}
}), Error('csrfOpts.hmacKey is required'))
})

test('Validate presence of hmac key with User-Info /2', async (t) => {
const fastify = Fastify()
await fastify.register(fastifyCookie)

await t.rejects(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
},
sessionPlugin: '@fastify/cookie'
}), Error('csrfOpts.hmacKey is required'))
})

test('Validate presence of hmac key with User-Info /3', async (t) => {
const fastify = Fastify()
await fastify.register(fastifyCookie)

await t.rejects(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
},
csrfOpts: {
hmacKey: undefined
}
}), Error('csrfOpts.hmacKey is required'))
})

test('Validate presence of hmac key with User-Info /4', async (t) => {
const fastify = Fastify()
await fastify.register(fastifyCookie)

await t.rejects(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
},
sessionPlugin: '@fastify/cookie',
csrfOpts: {
hmacKey: undefined
}
}), Error('csrfOpts.hmacKey is required'))
})

test('Validate presence of hmac key with User-Info /5', async (t) => {
const fastify = Fastify()
await fastify.register(fastifySecureSession, { key, cookie: { path: '/', secure: false } })

await t.resolves(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
},
sessionPlugin: '@fastify/secure-session'
}))
})

test('Validate presence of hmac key with User-Info /6', async (t) => {
const fastify = Fastify()
await fastify.register(fastifySecureSession, { key, cookie: { path: '/', secure: false } })

await t.resolves(fastify.register(fastifyCsrf, {
getUserInfo (req) {
return req.session.get('username')
},
sessionPlugin: '@fastify/secure-session',
csrfOpts: {
hmacKey: 'foo'
}
}))
})
27 changes: 23 additions & 4 deletions types/index.d.ts
Expand Up @@ -26,17 +26,36 @@ declare namespace fastifyCsrfProtection {
export type CookieSerializeOptions = FastifyCookieSerializeOptions

export type GetTokenFn = (req: FastifyRequest) => string | void;

export interface FastifyCsrfProtectionOptions {
csrfOpts?: CSRFOptions;

interface FastifyCsrfProtectionOptionsBase {
cookieKey?: string;
cookieOpts?: CookieSerializeOptions;
sessionKey?: string;
getUserInfo?: (req: FastifyRequest) => string;
getToken?: GetTokenFn;
sessionPlugin?: '@fastify/cookie' | '@fastify/session' | '@fastify/secure-session';
}

interface FastifyCsrfProtectionOptionsFastifyCookie {
sessionPlugin?: '@fastify/cookie';
csrfOpts: Omit<CSRFOptions, 'hmacKey'> & Required<Pick<CSRFOptions, 'hmacKey'>>;
}

interface FastifyCsrfProtectionOptionsFastifySession {
sessionPlugin: '@fastify/session';
csrfOpts?: CSRFOptions;
}

interface FastifyCsrfProtectionOptionsFastifySecureSession {
sessionPlugin: '@fastify/secure-session';
csrfOpts?: CSRFOptions;
}

export type FastifyCsrfProtectionOptions = FastifyCsrfProtectionOptionsBase & (
FastifyCsrfProtectionOptionsFastifyCookie |
FastifyCsrfProtectionOptionsFastifySession |
FastifyCsrfProtectionOptionsFastifySecureSession
)

/**
* @deprecated Use FastifyCsrfProtectionOptions instead
*/
Expand Down
23 changes: 19 additions & 4 deletions types/index.test-d.ts
Expand Up @@ -31,13 +31,28 @@ async function run() {
}


fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 'sha1' } })
fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 'sha1', hmacKey: 'hmac' } })
expectError(fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 1 } }))

fastify.register(FastifySession)
fastify.register(FastifyCsrfProtection, { getUserInfo(req) {
return req.session.get('username')
}})
fastify.register(FastifyCsrfProtection, {
csrfOpts: {
hmacKey: '123'
},
getUserInfo(req) {
return req.session.get('username')
}
})
expectError(fastify.register(FastifyCsrfProtection, { getUserInfo: 'invalid' }))

fastify.register(FastifyCsrfProtection, { csrfOpts: { hmacKey: 'hmac' }, sessionPlugin: '@fastify/cookie' })
fastify.register(FastifyCsrfProtection, { csrfOpts: { hmacKey: 'hmac' } })
expectError(fastify.register(FastifyCsrfProtection, { }))
expectError(fastify.register(FastifyCsrfProtection, { csrfOpts: { }}))
expectError(fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/cookie', csrfOpts: { }}))
fastify.register(FastifyCsrfProtection, { csrfOpts: { }, sessionPlugin: '@fastify/session' })
fastify.register(FastifyCsrfProtection, { csrfOpts: { }, sessionPlugin: '@fastify/secure-session' })
fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/session' })
fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/secure-session' })

expectDeprecated({} as FastifyCsrfOptions)

0 comments on commit be3e576

Please sign in to comment.