Skip to content

Commit be3e576

Browse files
Merge pull request from GHSA-qrgf-9gpc-vrxw
* fix: enforce hmac key * fix: check only if fastify/cookie is enabled * fix: restored working testts * fix: update README.md Co-authored-by: Uzlopak <[email protected]> * 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 <[email protected]>
1 parent f60148c commit be3e576

File tree

5 files changed

+136
-8
lines changed

5 files changed

+136
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ You can also pass the [cookie serialization](https://github.com/fastify/fastify-
161161
The option `userInfo` is required if `getUserInfo` has been specified in the module option.
162162
The provided `userInfo` is hashed inside the csrf token and it is not directly exposed.
163163
This option is needed to protect against cookie tossing.
164+
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
164165

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

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ async function fastifyCsrfProtection (fastify, opts) {
4242
if (opts.getUserInfo) {
4343
csrfOpts.userInfo = true
4444
}
45+
46+
if (sessionPlugin === '@fastify/cookie' && csrfOpts.userInfo) {
47+
assert(csrfOpts.hmacKey, 'csrfOpts.hmacKey is required')
48+
}
49+
4550
const tokens = new CSRF(csrfOpts)
4651

4752
const isCookieSigned = cookieOpts && cookieOpts.signed

test/user-info.test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ test('Cookies with User-Info', async t => {
1717
await fastify.register(fastifyCsrf, {
1818
getUserInfo (req) {
1919
return userInfoDB[req.body.username]
20+
},
21+
csrfOpts: {
22+
hmacKey: 'foo'
2023
}
2124
})
2225

@@ -73,6 +76,9 @@ test('Session with User-Info', async t => {
7376
sessionPlugin: '@fastify/session',
7477
getUserInfo (req) {
7578
return req.session.username
79+
},
80+
csrfOpts: {
81+
hmacKey: 'foo'
7682
}
7783
})
7884

@@ -122,6 +128,9 @@ test('SecureSession with User-Info', async t => {
122128
sessionPlugin: '@fastify/secure-session',
123129
getUserInfo (req) {
124130
return req.session.get('username')
131+
},
132+
csrfOpts: {
133+
hmacKey: 'foo'
125134
}
126135
})
127136

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

164173
t.equal(response2.statusCode, 200)
165174
})
175+
176+
test('Validate presence of hmac key with User-Info /1', async (t) => {
177+
const fastify = Fastify()
178+
await fastify.register(fastifyCookie)
179+
180+
await t.rejects(fastify.register(fastifyCsrf, {
181+
getUserInfo (req) {
182+
return req.session.get('username')
183+
}
184+
}), Error('csrfOpts.hmacKey is required'))
185+
})
186+
187+
test('Validate presence of hmac key with User-Info /2', async (t) => {
188+
const fastify = Fastify()
189+
await fastify.register(fastifyCookie)
190+
191+
await t.rejects(fastify.register(fastifyCsrf, {
192+
getUserInfo (req) {
193+
return req.session.get('username')
194+
},
195+
sessionPlugin: '@fastify/cookie'
196+
}), Error('csrfOpts.hmacKey is required'))
197+
})
198+
199+
test('Validate presence of hmac key with User-Info /3', async (t) => {
200+
const fastify = Fastify()
201+
await fastify.register(fastifyCookie)
202+
203+
await t.rejects(fastify.register(fastifyCsrf, {
204+
getUserInfo (req) {
205+
return req.session.get('username')
206+
},
207+
csrfOpts: {
208+
hmacKey: undefined
209+
}
210+
}), Error('csrfOpts.hmacKey is required'))
211+
})
212+
213+
test('Validate presence of hmac key with User-Info /4', async (t) => {
214+
const fastify = Fastify()
215+
await fastify.register(fastifyCookie)
216+
217+
await t.rejects(fastify.register(fastifyCsrf, {
218+
getUserInfo (req) {
219+
return req.session.get('username')
220+
},
221+
sessionPlugin: '@fastify/cookie',
222+
csrfOpts: {
223+
hmacKey: undefined
224+
}
225+
}), Error('csrfOpts.hmacKey is required'))
226+
})
227+
228+
test('Validate presence of hmac key with User-Info /5', async (t) => {
229+
const fastify = Fastify()
230+
await fastify.register(fastifySecureSession, { key, cookie: { path: '/', secure: false } })
231+
232+
await t.resolves(fastify.register(fastifyCsrf, {
233+
getUserInfo (req) {
234+
return req.session.get('username')
235+
},
236+
sessionPlugin: '@fastify/secure-session'
237+
}))
238+
})
239+
240+
test('Validate presence of hmac key with User-Info /6', async (t) => {
241+
const fastify = Fastify()
242+
await fastify.register(fastifySecureSession, { key, cookie: { path: '/', secure: false } })
243+
244+
await t.resolves(fastify.register(fastifyCsrf, {
245+
getUserInfo (req) {
246+
return req.session.get('username')
247+
},
248+
sessionPlugin: '@fastify/secure-session',
249+
csrfOpts: {
250+
hmacKey: 'foo'
251+
}
252+
}))
253+
})

types/index.d.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,36 @@ declare namespace fastifyCsrfProtection {
2626
export type CookieSerializeOptions = FastifyCookieSerializeOptions
2727

2828
export type GetTokenFn = (req: FastifyRequest) => string | void;
29-
30-
export interface FastifyCsrfProtectionOptions {
31-
csrfOpts?: CSRFOptions;
29+
30+
interface FastifyCsrfProtectionOptionsBase {
3231
cookieKey?: string;
3332
cookieOpts?: CookieSerializeOptions;
3433
sessionKey?: string;
3534
getUserInfo?: (req: FastifyRequest) => string;
3635
getToken?: GetTokenFn;
37-
sessionPlugin?: '@fastify/cookie' | '@fastify/session' | '@fastify/secure-session';
3836
}
3937

38+
interface FastifyCsrfProtectionOptionsFastifyCookie {
39+
sessionPlugin?: '@fastify/cookie';
40+
csrfOpts: Omit<CSRFOptions, 'hmacKey'> & Required<Pick<CSRFOptions, 'hmacKey'>>;
41+
}
42+
43+
interface FastifyCsrfProtectionOptionsFastifySession {
44+
sessionPlugin: '@fastify/session';
45+
csrfOpts?: CSRFOptions;
46+
}
47+
48+
interface FastifyCsrfProtectionOptionsFastifySecureSession {
49+
sessionPlugin: '@fastify/secure-session';
50+
csrfOpts?: CSRFOptions;
51+
}
52+
53+
export type FastifyCsrfProtectionOptions = FastifyCsrfProtectionOptionsBase & (
54+
FastifyCsrfProtectionOptionsFastifyCookie |
55+
FastifyCsrfProtectionOptionsFastifySession |
56+
FastifyCsrfProtectionOptionsFastifySecureSession
57+
)
58+
4059
/**
4160
* @deprecated Use FastifyCsrfProtectionOptions instead
4261
*/

types/index.test-d.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,28 @@ async function run() {
3131
}
3232

3333

34-
fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 'sha1' } })
34+
fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 'sha1', hmacKey: 'hmac' } })
3535
expectError(fastify.register(FastifyCsrfProtection, { csrfOpts: { algorithm: 1 } }))
3636

3737
fastify.register(FastifySession)
38-
fastify.register(FastifyCsrfProtection, { getUserInfo(req) {
39-
return req.session.get('username')
40-
}})
38+
fastify.register(FastifyCsrfProtection, {
39+
csrfOpts: {
40+
hmacKey: '123'
41+
},
42+
getUserInfo(req) {
43+
return req.session.get('username')
44+
}
45+
})
4146
expectError(fastify.register(FastifyCsrfProtection, { getUserInfo: 'invalid' }))
4247

48+
fastify.register(FastifyCsrfProtection, { csrfOpts: { hmacKey: 'hmac' }, sessionPlugin: '@fastify/cookie' })
49+
fastify.register(FastifyCsrfProtection, { csrfOpts: { hmacKey: 'hmac' } })
50+
expectError(fastify.register(FastifyCsrfProtection, { }))
51+
expectError(fastify.register(FastifyCsrfProtection, { csrfOpts: { }}))
52+
expectError(fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/cookie', csrfOpts: { }}))
53+
fastify.register(FastifyCsrfProtection, { csrfOpts: { }, sessionPlugin: '@fastify/session' })
54+
fastify.register(FastifyCsrfProtection, { csrfOpts: { }, sessionPlugin: '@fastify/secure-session' })
55+
fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/session' })
56+
fastify.register(FastifyCsrfProtection, { sessionPlugin: '@fastify/secure-session' })
57+
4358
expectDeprecated({} as FastifyCsrfOptions)

0 commit comments

Comments
 (0)