Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow customisation of the session object through new getSession config #8097

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/add-get-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-6/auth': major
'@keystone-6/core': major
---

Remove `config.session`, replaced with `config.getSession`
5 changes: 5 additions & 0 deletions .changeset/bump-prisma-now.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': major
---

Upgrade `prisma` to `5.1.0`
5 changes: 5 additions & 0 deletions .changeset/fix-frozen-prisma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': major
---

Changes `keystone prisma` behaviour to not generate Typescript types and prisma schema when using `--frozen`
5 changes: 5 additions & 0 deletions .changeset/more-errors-please.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': minor
---

`KS_PRISMA_ERRORS` are now logged with `console.error` on the server
6 changes: 6 additions & 0 deletions .changeset/move-session-strategies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-6/auth': minor
'@keystone-6/core': major
---

Moves `statelessSessions` and `storedSessions` to the `@keystone-6/auth` package
5 changes: 5 additions & 0 deletions .changeset/no-extend-type.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': major
---

Removes `ExtendGraphqlSchema` type alias, use `(schema: GraphQLSchema) => GraphQLSchema` instead (with `import type { GraphQLSchema } from 'graphql'`).
2 changes: 1 addition & 1 deletion .github/actions/ci-setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ runs:
- uses: actions/setup-node@main
with:
# preferably lts/*, but we hit API limits when querying that
node-version: 18
node-version: 20
registry-url: 'https://registry.npmjs.org'
cache: pnpm

Expand Down
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

19 changes: 18 additions & 1 deletion docs/pages/docs/config/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ const { withAuth } = createAuth({
listKey: 'User',
identityField: 'email',
secretField: 'password',
{% if $nextRelease %}
sessionStrategy: {/*.. Session Config ...*/}
{% /if %}

// Additional options
{% if $nextRelease %}
{% else /%}
sessionData: 'id name email',
{% /if %}
initFirstItem: {
fields: ['email', 'password'],
itemData: { isAdmin: true },
Expand All @@ -46,16 +52,21 @@ export default withAuth(
isAdmin: checkbox(),
},
}),
{% if $nextRelease %}
{% else /%}
session: { /* ... */ },
{% /if %}
},
})
);
```

The function `createAuth` returns a function `withAuth` which should be used to wrap your `config()`.
This wrapper function will modify the config object to inject extra fields, extra GraphQL queries and mutations, and custom Admin UI functionality into the system.
{% if $nextRelease %}
{% else /%}
The `createAuth` function must be used in conjunction with a [session](./session) configuration.

{% /if %}
## Required options

The core functionality of the authentication system provides a GraphQL mutation to authenticate a user and then start a session, and a sign in page in the Admin UI.
Expand Down Expand Up @@ -145,6 +156,11 @@ This page uses the `authenticateUserWithPassword` mutation to let users sign in
The following options add extra functionality to your Keystone authentication system.
By default they are disabled.

{% if $nextRelease %}

Some info on Session

{% else / %}
### sessionData

This option adds support for setting a custom `session.data` value based on the authenticated user.
Expand All @@ -165,6 +181,7 @@ const { withAuth } = createAuth({
sessionData: 'id name isAdmin',
});
```
{% /if %}

### initFirstItem

Expand Down
66 changes: 29 additions & 37 deletions docs/pages/docs/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export default config({
db: { /* ... */ },
ui: { /* ... */ },
server: { /* ... */ },
{% if $nextRelease %}
getSession: { /* ... */ },
{% else /%}
session: { /* ... */ },
{% /if %}
graphql: { /* ... */ },
extendGraphqlSchema: { /* ... */ },
storage: { /* ... */ },
Expand All @@ -35,12 +39,10 @@ This type definition should be considered the source of truth for the available
## lists

The `lists` config option is where you define the data model, or schema, of the Keystone system.
It has a TypeScript type of `ListSchemaConfig`.
This is where you define and configure the `lists` and their `fields` of the data model.
See the [Lists API](./lists) docs for details on how to use this function.

```typescript
import type { ListSchemaConfig } from '@keystone-6/core/types';
import { config } from '@keystone-6/core';

export default config({
Expand All @@ -51,12 +53,7 @@ export default config({

## db

```
import type { DatabaseConfig } from '@keystone-6/core/types';
```

The `db` config option configures the database used to store data in your Keystone system.
It has a TypeScript type of `DatabaseConfig`.
Keystone supports the database types **PostgreSQL**, **MySQL** and **SQLite**.
These database types are powered by their corresponding Prisma database providers; `postgresql`, `mysql` and `sqlite`.

Expand Down Expand Up @@ -133,12 +130,7 @@ The `sqlite` provider is not intended to be used in production systems, and has

## ui

```ts
import type { AdminUIConfig } from '@keystone-6/core/types';
```

The `ui` config option configures the Admin UI which is provided by Keystone.
It has a TypeScript type of `AdminUIConfig`.
This config option is for top level configuration of the Admin UI.
Fine grained configuration of how lists and fields behave in the Admin UI is handled in the `lists` definition (see the [Lists API](./lists) for more details).

Expand Down Expand Up @@ -194,10 +186,6 @@ export default config({

## server

```
import type { ServerConfig } from '@keystone-6/core/types';
```

The `dev` and `start` commands from the Keystone [command line](../guides/cli) will start an Express web-server for you.
This server is configured via the `server` configuration option.

Expand Down Expand Up @@ -314,20 +302,37 @@ export default config({

_Note_: when using `keystone dev`, `extendHttpServer` is only called once on startup - you will need to restart your process for any updates

## session
{% if $nextRelease %}
## getSession

The `getSession` config option allows you to configure how Keystone retieves a `session` based on a given `context`. `getSession` will generally check the original request `req` on the `context` to validate and return a `session` that is then added to the `context` for that request.

```typescript
export default config({
getSession: async ({ context }) => {
if (!context.req) return
return await getValidSession(context);
}
},
/* ... */
});
```
import type { SessionStrategy } from '@keystone-6/core/types';
```

Whatever you return here will what is available in `context.session`, returning `undefined` here will make the `session` undefined, making the request essentially `unauthenticated` depending on your access-control.

If using `getSession` with `@keystone-6/auth`, `getSession` is called *after* the authentication package has validated the session and added it to `context` that is passed in.

See the [Session Docs](./session) for more details on how to configure session management using `@keystone-6/auth` in Keystone.
{% else /%}
## session

The `session` config option allows you to configure session management of your Keystone system.
It has a TypeScript type of `SessionStrategy<any>`.

In general you will use `SessionStrategy` objects from the `@keystone-6/core/session` package, rather than writing this yourself.
In general you will use `SessionStrategy` objects from the `@keystone-6/auth/session` package, rather than writing this yourself.


```typescript
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';

export default config({
session: statelessSessions({ /* ... */ }),
Expand All @@ -336,15 +341,10 @@ export default config({
```

See the [Session API](./session) for more details on how to configure session management in Keystone.

{% /if %}
## graphql

```ts
import type { GraphQLConfig } from '@keystone-6/core/types';
```

The `graphql` config option allows you to configure certain aspects of your GraphQL API.
It has a TypeScript type of `GraphQLConfig`.

Options:

Expand Down Expand Up @@ -374,12 +374,8 @@ export default config({

## extendGraphqlSchema

```ts
import type { ExtendGraphqlSchema } from '@keystone-6/core/types';
```

The `extendGraphqlSchema` config option allows you to extend the GraphQL API which is generated by Keystone based on your schema definition.
It has a TypeScript type of `ExtendGraphqlSchema`.
It has a TypeScript type of `(schema: import("graphql").GraphQLSchema) => import("graphql").GraphQLSchema`.

`extendGraphqlSchema` expects a function that takes the GraphQL Schema generated by Keystone and returns a valid GraphQL Schema

Expand All @@ -399,10 +395,6 @@ See the [schema extension guide](../guides/schema-extension) for more details an

## storage (images and files)

```ts
import type { StorageConfig } from '@keystone-6/core/types'
```

The `storage` config option provides configuration which is used by the [`file`](../fields/file) field type or the
[`image`](../fields/image) field type. You provide an object whose property is a `StorageConfig` object, fields then reference this `storage` by the key.
Each storage is configured separately using the options below.
Expand Down
66 changes: 62 additions & 4 deletions docs/pages/docs/config/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,36 @@ title: "Session"
description: "Reference docs for the session property of Keystone’s system configuration object."
---

{% if $nextRelease %}
The `getSession` config option allows you to configure how Keystone retrieves a ` session`` based on a given `context`. `getSession`will generally check the original request`req`on the`context`to validate and return a`session`that is then added to the`context` for that request.

```typescript
export default config({
getSession: async ({ context }) => {
if (!context.req) return
return await getValidSession(context);
}
},
/* ... */
});
```

Whatever you return here is what is available in `context.session`, returning `undefined` here will make the `session` undefined, making the request essentially `unauthenticated` depending on your access-control.

## SessionStrategy in Auth

The `sessionStrategy` property of the [auth configuration](./auth) object allows you to configure session management of your Keystone system when using `@keystone-6/auth`. It has a TypeScript type of `SessionStrategy<any>`.
In general, you will use `SessionStrategy` objects from the `@keystone-6/auth/session` package, rather than writing this yourself.

{% else /%}
The `session` property of the [system configuration](./config) object allows you to configure session management of your Keystone system.
It has a TypeScript type of `SessionStrategy<any>`.
In general you will use `SessionStrategy` objects from the `@keystone-6/core/session` package, rather than writing this yourself.
In general, you will use `SessionStrategy` objects from the `@keystone-6/auth/session` package, rather than writing this yourself.
{% /if %}

```typescript
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';

export default config({
session: statelessSessions({
Expand All @@ -20,11 +43,24 @@ export default config({
path: '/',
domain: 'localhost',
sameSite: 'lax',
{% if $nextRelease %}
data: 'id name email'
{% /if %}
}),
/* ... */
});
```

{% if $nextRelease %}

## Overriding session using `getSession`

You can use this auth `sessionStrategy` in combination with `getSession` to customise your session configuration. `getSession` is called after the Auth package validates the session, this is passed through to `getSession` as `context.session`.

See the [Custom Session Example](https://github.com/keystonejs/keystone/tree/main/examples/custom-session-validation) which demonstrates invalidating a session after a password change.

{% /if %}

## Stateless vs stored sessions

Keystone supports both stateless and stored sessions.
Expand All @@ -37,7 +73,11 @@ Both `statelessSessions()` and `storedSessions()` accept a common set of argumen

```typescript
import { config } from '@keystone-6/core';
import { statelessSessions, storedSessions } from '@keystone-6/core/session';
{% if $nextRelease %}
import { statelessSessions, storedSessions } from '@keystone-6/auth/session';
{% else /%}
import { statelessSessions, storedSessions } from '@keystone-6/auth/session';
{% /if %}

export default config({
// Stateless
Expand All @@ -51,6 +91,10 @@ export default config({

Options

{% if $nextRelease %}

- `data` (default: `'id'`): Graphql Query to populate the default `context.session.data` object.
{% /if %}
- `secret` (required): The secret used by `@hapi/iron` for encrypting the cookie data. Must be at least 32 characters long.
- `ironOptions`: Additional options to be passed to `Iron.seal()` and `Iron.unseal()` when encrypting and decrypting the cookies.
See the [`@hapi/iron` docs](https://hapi.dev/module/iron/api/?v=6.0.0#options) for details.
Expand All @@ -67,6 +111,20 @@ Options
- `sameSite` (default: `'lax'`): Controls whether the cookie is sent with cross-origin requests. Can be one of `true`, `false`, `'strict'`, `'lax'` or `'none'`. See [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes) for more details on the `sameSite` cookie attribute.
**Note**: The `secure` attribute must also be set when `sameSite` is set to `none`!

{% if $nextRelease %}

### Session `data`

This option adds support for setting a custom `session.data` value based on the authenticated user.

The authentication mutations will set the values `{ listKey, itemId }` on the `context.session` object.
You will often need to know more than just the `itemId` of the authenticated user, such as when performing [access-control](../guides/auth-and-access-control) or using [hooks](../guides/hooks).
Configuring `data` will add an `session.data` based on the `itemId`, populated by the fields given in `sessionData.query`.

The value is a GraphQL query string which indicates which fields should be populated on the `session.data` object

{% /if %}

### Session stores

When using `storedSessions` you need to pass in a session store as the `store` option.
Expand Down Expand Up @@ -96,7 +154,7 @@ Interface:
If you configure your Keystone session with session management then the [`KeystoneContext`](../context/overview) type will include the following session related properties.

- `session`: An object representing the session data. The value will depend on the value passed into `context.sessionStrategy.start()`.
- `sessionStrategy`: an object that, when using `statelessSessions` or `storedSessions` from `@keystone-6/core/session` includes the following functions:
- `sessionStrategy`: an object that, when using `statelessSessions` or `storedSessions` from `@keystone-6/auth/session` includes the following functions:
- `get({context})`: a function that returns a `session` object based on `context` - this needs to be a `context` with a valid `req` (using `context.withRequest`). This function is called by Keystone to get the value of `context.session`
- `start({context, data})`: a function that, given a valid `context.res` starts a new session containing what is passed into `data`.
- `end({context})`: a function that, given a valid `context.res` ends a session.
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/docs/guides/auth-and-access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Your entire Keystone config should now look like this:
```ts
import { config, list } from '@keystone-6/core';
import { checkbox, password, text } from '@keystone-6/core/fields';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
import { createAuth } from '@keystone-6/auth';

const db = {
Expand Down
Loading
Loading