diff --git a/README.md b/README.md index abdd5c5..7107a4b 100644 --- a/README.md +++ b/README.md @@ -403,6 +403,7 @@ As with all modules you can either pass the constructor function (class) to the cookiePath: '/my/path', cookieSecure: true, // if need secure cookie cookieSameSite: 'strict', // 'strict', 'lax' or 'none' + cookieHttpOnly: true // prevent client-side script access to the cookie // optional conversion function used to modify the detected language code convertDetectedLanguage: 'Iso15897', diff --git a/lib/languageLookups/cookie.js b/lib/languageLookups/cookie.js index bd51a17..56d4a87 100644 --- a/lib/languageLookups/cookie.js +++ b/lib/languageLookups/cookie.js @@ -79,7 +79,7 @@ export default { expires: expirationDate, domain: options.cookieDomain, path: options.cookiePath, - httpOnly: false, + httpOnly: options.cookieHttpOnly ?? false, overwrite: true, sameSite: options.cookieSameSite } diff --git a/test/languageDetector.js b/test/languageDetector.js index 943fb78..fb93585 100644 --- a/test/languageDetector.js +++ b/test/languageDetector.js @@ -48,6 +48,60 @@ describe('language detector', () => { expect(res.headers['Set-Cookie']).to.match(/my=cookie/) expect(res.headers['Set-Cookie']).to.match(/SameSite=None/) }) + + it('does not set httpOnly flag by default', () => { + const ld = new LanguageDetector(i18next.services, { order: ['cookie', 'header'] }) + const req = {} + const res = { + headers: { + 'Set-Cookie': 'my=cookie' + } + } + res.header = (name, value) => { res.headers[name] = value } + ld.cacheUserLanguage(req, res, 'it', ['cookie']) + expect(req).to.eql({}) + expect(res).to.have.property('headers') + expect(res.headers).to.have.property('Set-Cookie') + expect(res.headers['Set-Cookie']).to.match(/Path=\//) + expect(res.headers['Set-Cookie']).to.match(/my=cookie/) + expect(res.headers['Set-Cookie']).not.to.match(/HttpOnly/) + }) + + it('does not set httpOnly flag when cookieHttpOnly is false', () => { + const ld = new LanguageDetector(i18next.services, { order: ['cookie', 'header'], cookieHttpOnly: false }) + const req = {} + const res = { + headers: { + 'Set-Cookie': 'my=cookie' + } + } + res.header = (name, value) => { res.headers[name] = value } + ld.cacheUserLanguage(req, res, 'it', ['cookie']) + expect(req).to.eql({}) + expect(res).to.have.property('headers') + expect(res.headers).to.have.property('Set-Cookie') + expect(res.headers['Set-Cookie']).to.match(/Path=\//) + expect(res.headers['Set-Cookie']).to.match(/my=cookie/) + expect(res.headers['Set-Cookie']).not.to.match(/HttpOnly/) + }) + + it('sets httpOnly flag when cookieHttpOnly is true', () => { + const ld = new LanguageDetector(i18next.services, { order: ['cookie', 'header'], cookieHttpOnly: true }) + const req = {} + const res = { + headers: { + 'Set-Cookie': 'my=cookie' + } + } + res.header = (name, value) => { res.headers[name] = value } + ld.cacheUserLanguage(req, res, 'it', ['cookie']) + expect(req).to.eql({}) + expect(res).to.have.property('headers') + expect(res.headers).to.have.property('Set-Cookie') + expect(res.headers['Set-Cookie']).to.match(/Path=\//) + expect(res.headers['Set-Cookie']).to.match(/my=cookie/) + expect(res.headers['Set-Cookie']).to.match(/HttpOnly/) + }) }) describe('header', () => {