From 14e57312a14f7b97dca0c6f183114c22a196055f Mon Sep 17 00:00:00 2001 From: ropaolle Date: Sun, 29 Mar 2020 12:51:59 +0200 Subject: [PATCH 1/4] Session cookie sameSite option --- .changeset/purple-poets-heal.md | 12 ++++++++++ packages/keystone/README.md | 31 +++++++++++++------------ packages/keystone/lib/Keystone/index.js | 2 ++ packages/session/lib/session.js | 4 +++- 4 files changed, 33 insertions(+), 16 deletions(-) create mode 100644 .changeset/purple-poets-heal.md diff --git a/.changeset/purple-poets-heal.md b/.changeset/purple-poets-heal.md new file mode 100644 index 00000000000..7cc0e4f5320 --- /dev/null +++ b/.changeset/purple-poets-heal.md @@ -0,0 +1,12 @@ +--- +'@keystonejs/keystone': minor +'@keystonejs/session': minor +--- + + Session cookie option `cookieSameSite` added. `cookieSameSite` defaults to `false` and will not set the `SameSite` attribute. + + ```javascript + const keystone = new Keystone({ + cookieSameSite: true, + }); + ``` diff --git a/packages/keystone/README.md b/packages/keystone/README.md index c3476eec9cb..5928a3fc448 100644 --- a/packages/keystone/README.md +++ b/packages/keystone/README.md @@ -18,21 +18,22 @@ const keystone = new Keystone({ ## Config -| Option | Type | Default | Description | -| ---------------- | ---------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| `name` | `String` | `null` | The name of the project. Appears in the Admin UI. | -| `adapter` | `Object` | Required | The database storage adapter. See the [Adapter Framework](https://keystonejs.com/keystonejs/keystone/lib/adapters/) page for more details. | -| `adapters` | `Object` | `{}` | A list of named database adapters. Use the format `{ name: adapterObject }`. | -| `defaultAdapter` | `String` | `null` | The name of the database adapter to use by default if multiple are provided. | -| `defaultAccess` | `Object` | `{}` | Default list and field access. See the [Access Control](https://www.keystonejs.com/api/access-control#defaults) page for more details. | -| `onConnect` | `Function` | `null` | Callback that executes once `keystone.connect()` complete. Takes no arguments. | -| `cookieSecret` | `String` | `qwerty` | The secret used to sign session ID cookies. Should be long and unguessable. Don't use this default in production! | -| `cookieMaxAge` | `Int` | 30 days | The maximum time, in milliseconds, session ID cookies remain valid. | -| `secureCookies` | `Boolean` | Variable | Defaults to true in production mode, false otherwise. See below for important details. | -| `sessionStore` | `Object` | `null` | A compatible Express session middleware. | -| `schemaNames` | `Array` | `['public']` | | -| `queryLimits` | `Object` | `{}` | Configures global query limits | -| `appVersion` | `Object` | `{ version: '1.0.0', addVersionToHttpHeaders: true, access: true }` | Configure the application version and where it is made available | +| Option | Type | Default | Description | +| ---------------- | ---------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | `String` | `null` | The name of the project. Appears in the Admin UI. | +| `adapter` | `Object` | Required | The database storage adapter. See the [Adapter Framework](https://keystonejs.com/keystonejs/keystone/lib/adapters/) page for more details. | +| `adapters` | `Object` | `{}` | A list of named database adapters. Use the format `{ name: adapterObject }`. | +| `defaultAdapter` | `String` | `null` | The name of the database adapter to use by default if multiple are provided. | +| `defaultAccess` | `Object` | `{}` | Default list and field access. See the [Access Control](https://www.keystonejs.com/api/access-control#defaults) page for more details. | +| `onConnect` | `Function` | `null` | Callback that executes once `keystone.connect()` complete. Takes no arguments. | +| `cookieSecret` | `String` | `qwerty` | The secret used to sign session ID cookies. Should be long and unguessable. Don't use this default in production! | +| `cookieMaxAge` | `Int` | 30 days | The maximum time, in milliseconds, session ID cookies remain valid. | +| `cookieSameSite` | `Boolean` or `String` | false | Defaults to false. | +| `secureCookies` | `Boolean` | Variable | Defaults to true in production mode, false otherwise. See below for important details. | +| `sessionStore` | `Object` | `null` | A compatible Express session middleware. | +| `schemaNames` | `Array` | `['public']` | | +| `queryLimits` | `Object` | `{}` | Configures global query limits | +| `appVersion` | `Object` | `{ version: '1.0.0', addVersionToHttpHeaders: true, access: true }` | Configure the application version and where it is made available | ### `secureCookies` diff --git a/packages/keystone/lib/Keystone/index.js b/packages/keystone/lib/Keystone/index.js index 3daaf84263a..f45b782a7c9 100644 --- a/packages/keystone/lib/Keystone/index.js +++ b/packages/keystone/lib/Keystone/index.js @@ -47,6 +47,7 @@ module.exports = class Keystone { queryLimits = {}, secureCookies = process.env.NODE_ENV === 'production', // Default to true in production cookieMaxAge = 1000 * 60 * 60 * 24 * 30, // 30 days + cookieSameSite = false, schemaNames = ['public'], appVersion = { version: '1.0.0', @@ -65,6 +66,7 @@ module.exports = class Keystone { cookieSecret, secureCookies, cookieMaxAge, + cookieSameSite, sessionStore, }); this.eventHandlers = { onConnect }; diff --git a/packages/session/lib/session.js b/packages/session/lib/session.js index 8455f0d8bb1..223935a73a9 100644 --- a/packages/session/lib/session.js +++ b/packages/session/lib/session.js @@ -7,11 +7,13 @@ class SessionManager { cookieSecret = 'qwerty', secureCookies = process.env.NODE_ENV === 'production', // Default to true in production cookieMaxAge = 1000 * 60 * 60 * 24 * 30, // 30 days + cookieSameSite = false, sessionStore, }) { this._cookieSecret = cookieSecret; this._secureCookies = secureCookies; this._cookieMaxAge = cookieMaxAge; + this._cookieSameSite = cookieSameSite; this._sessionStore = sessionStore; } @@ -63,7 +65,7 @@ class SessionManager { resave: false, saveUninitialized: false, name: COOKIE_NAME, - cookie: { secure: this._secureCookies, maxAge: this._cookieMaxAge }, + cookie: { secure: this._secureCookies, maxAge: this._cookieMaxAge, sameSite: this._cookieSameSite }, store: this._sessionStore, }); From d958f3a8c74a3bdbb404d60c62b382c3fce4dcef Mon Sep 17 00:00:00 2001 From: ropaolle Date: Sun, 29 Mar 2020 13:02:52 +0200 Subject: [PATCH 2/4] Prettier failed --- packages/session/lib/session.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/session/lib/session.js b/packages/session/lib/session.js index 223935a73a9..8ce030af455 100644 --- a/packages/session/lib/session.js +++ b/packages/session/lib/session.js @@ -65,7 +65,11 @@ class SessionManager { resave: false, saveUninitialized: false, name: COOKIE_NAME, - cookie: { secure: this._secureCookies, maxAge: this._cookieMaxAge, sameSite: this._cookieSameSite }, + cookie: { + secure: this._secureCookies, + maxAge: this._cookieMaxAge, + sameSite: this._cookieSameSite, + }, store: this._sessionStore, }); From 9da930fce7edbf39520ebaa63b93eb59d7bc08dc Mon Sep 17 00:00:00 2001 From: ropaolle Date: Wed, 1 Apr 2020 13:18:16 +0200 Subject: [PATCH 3/4] cookie object added --- .changeset/purple-poets-heal.md | 14 +++++++++++-- docs/guides/production.md | 12 ++++++++++- packages/keystone/README.md | 27 ++++++++++++++++++++----- packages/keystone/lib/Keystone/index.js | 12 +++++------ packages/session/lib/session.js | 14 +++---------- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/.changeset/purple-poets-heal.md b/.changeset/purple-poets-heal.md index 7cc0e4f5320..15859f4fd2e 100644 --- a/.changeset/purple-poets-heal.md +++ b/.changeset/purple-poets-heal.md @@ -3,10 +3,20 @@ '@keystonejs/session': minor --- - Session cookie option `cookieSameSite` added. `cookieSameSite` defaults to `false` and will not set the `SameSite` attribute. +Cookie configuration moved to an object to allow us to pass it directly to the express-session middleware. We where prviously only able to set `secure` and `maxAge`, bat are now also able to set `domain`, `expires`, `httpOnly`, `path` and `sameSite` + + #### Default ```javascript const keystone = new Keystone({ - cookieSameSite: true, + cookie: { + // domain: undefined, + // expires: undefined, + // httpOnly: true, + maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days + sameSite: false, + // path: '/', + secure: process.env.NODE_ENV === 'production', // Defaults to true in production + }, }); ``` diff --git a/docs/guides/production.md b/docs/guides/production.md index efdaf63b641..a7b0606db85 100644 --- a/docs/guides/production.md +++ b/docs/guides/production.md @@ -10,7 +10,17 @@ Yes, Keystone can be (and is!) used for production websites. Here's a handy list ## Secure cookies -In production builds, [Keystone's `secureCookies`](/packages/keystone/README.md#config) defaults to true. Make sure your server is HTTPS-enabled when `secureCookies` is enabled or you will be unable to log in. +In production builds, [Keystone's `cookie` object](/packages/keystone/README.md#config) defaults to + +```js +cookie = { + secure: process.env.NODE_ENV === 'production', // Defaults to true in production + maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days + sameSite: false, +}; +``` + +Make sure your server is HTTPS-enabled when `secure` is enabled or you will be unable to log in. ## Session handling diff --git a/packages/keystone/README.md b/packages/keystone/README.md index 3a8598443ae..9005a0f5ccc 100644 --- a/packages/keystone/README.md +++ b/packages/keystone/README.md @@ -23,14 +23,12 @@ const keystone = new Keystone({ | `adapter` | `Object` | Required | The database storage adapter. See the [Adapter Framework](https://keystonejs.com/keystonejs/keystone/lib/adapters/) page for more details. | | `adapters` | `Object` | `{}` | A list of named database adapters. Use the format `{ name: adapterObject }`. | | `appVersion` | `Object` | See: [`appVersion`](#appversion) | Configure the application version and where it is made available | -| `cookieMaxAge` | `Int` | 30 days | The maximum time, in milliseconds, session ID cookies remain valid. | -| `cookieSecret` | `String` | `qwerty` | The secret used to sign session ID cookies. Should be long and unguessable. Don't use this default in production! | +| `cookie` | `Object` | See: [`cookie`](#cookie) | Cookie object used to configure the [express-session middleware](https://github.com/expressjs/session#cookie). | | `defaultAccess` | `Object` | `{}` | Default list and field access. See the [Access Control](https://www.keystonejs.com/api/access-control#defaults) page for more details. | | `defaultAdapter` | `String` | `null` | The name of the database adapter to use by default if multiple are provided. | | `name` | `String` | `null` | The name of the project. Appears in the Admin UI. | | `onConnect` | `Function` | `null` | Callback that executes once `keystone.connect()` complete. Takes no arguments. | | `queryLimits` | `Object` | `{}` | Configures global query limits | -| `secureCookies` | `Boolean` | Variable | Defaults to true in production mode, false otherwise. See below for important details. | | `sessionStore` | `Object` | `null` | A compatible Express session middleware. | | `schemaNames` | `Array` | `['public']` | | @@ -83,12 +81,31 @@ const keystone = new Keystone({ Note that `maxTotalResults` applies to the total results of all relationship queries separately, even if some are nested inside others. -### `secureCookies` +### `cookie` -A secure cookie is only sent to the server with an encrypted request over the HTTPS protocol. If `secureCookies` is set to true (as is the default with a **production** build) for a KeystoneJS project running on a non-HTTPS server (such as localhost), you will **not** be able to log in. In that case, be sure you set `secureCookies` to false. This does not affect development builds since this value is already false. +_**Default:**_ see Usage. + +A description of the cookie properties is included in the [express-session documentation](https://github.com/expressjs/session#cookie). + +#### `secure` + +A secure cookie is only sent to the server with an encrypted request over the HTTPS protocol. If `secure` is set to true (as is the default with a **production** build) for a KeystoneJS project running on a non-HTTPS server (such as localhost), you will **not** be able to log in. In that case, be sure you set `secure` to false. This does not affect development builds since this value is already false. You can read more about secure cookies on the [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies). +#### Usage + +```javascript +const keystone = new Keystone({ + /* ...config */ + cookie: { + secure = process.env.NODE_ENV === 'production', // Default to true in production + maxAge = 1000 * 60 * 60 * 24 * 30, // 30 days + sameSite = false, + }, +}); +``` + ### `sessionStore` Sets the Express server's [session middleware](https://github.com/expressjs/session). This should be configured before deploying your app. diff --git a/packages/keystone/lib/Keystone/index.js b/packages/keystone/lib/Keystone/index.js index f45b782a7c9..026deae826c 100644 --- a/packages/keystone/lib/Keystone/index.js +++ b/packages/keystone/lib/Keystone/index.js @@ -45,9 +45,11 @@ module.exports = class Keystone { cookieSecret = 'qwerty', sessionStore, queryLimits = {}, - secureCookies = process.env.NODE_ENV === 'production', // Default to true in production - cookieMaxAge = 1000 * 60 * 60 * 24 * 30, // 30 days - cookieSameSite = false, + cookie = { + secure: process.env.NODE_ENV === 'production', // Default to true in production + maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days + sameSite: false, + }, schemaNames = ['public'], appVersion = { version: '1.0.0', @@ -64,9 +66,7 @@ module.exports = class Keystone { this._schemas = {}; this._sessionManager = new SessionManager({ cookieSecret, - secureCookies, - cookieMaxAge, - cookieSameSite, + cookie, sessionStore, }); this.eventHandlers = { onConnect }; diff --git a/packages/session/lib/session.js b/packages/session/lib/session.js index 8ce030af455..4f995cd0e9e 100644 --- a/packages/session/lib/session.js +++ b/packages/session/lib/session.js @@ -5,15 +5,11 @@ const cookie = require('cookie'); class SessionManager { constructor({ cookieSecret = 'qwerty', - secureCookies = process.env.NODE_ENV === 'production', // Default to true in production - cookieMaxAge = 1000 * 60 * 60 * 24 * 30, // 30 days - cookieSameSite = false, + cookie, sessionStore, }) { this._cookieSecret = cookieSecret; - this._secureCookies = secureCookies; - this._cookieMaxAge = cookieMaxAge; - this._cookieSameSite = cookieSameSite; + this._cookie = cookie; this._sessionStore = sessionStore; } @@ -65,11 +61,7 @@ class SessionManager { resave: false, saveUninitialized: false, name: COOKIE_NAME, - cookie: { - secure: this._secureCookies, - maxAge: this._cookieMaxAge, - sameSite: this._cookieSameSite, - }, + cookie: this._cookie, store: this._sessionStore, }); From fc9f22a6e5f793830c409b4c54ee77907b21173e Mon Sep 17 00:00:00 2001 From: ropaolle Date: Wed, 1 Apr 2020 13:28:29 +0200 Subject: [PATCH 4/4] prettier --- packages/session/lib/session.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/session/lib/session.js b/packages/session/lib/session.js index 4f995cd0e9e..eca1df01f94 100644 --- a/packages/session/lib/session.js +++ b/packages/session/lib/session.js @@ -3,11 +3,7 @@ const expressSession = require('express-session'); const cookie = require('cookie'); class SessionManager { - constructor({ - cookieSecret = 'qwerty', - cookie, - sessionStore, - }) { + constructor({ cookieSecret = 'qwerty', cookie, sessionStore }) { this._cookieSecret = cookieSecret; this._cookie = cookie; this._sessionStore = sessionStore;