Skip to content

Commit

Permalink
getSession WIP2 rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Calder committed Aug 13, 2023
1 parent d4621f8 commit 498e351
Show file tree
Hide file tree
Showing 31 changed files with 170 additions and 205 deletions.
4 changes: 2 additions & 2 deletions docs/pages/docs/config/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ import type { SessionStrategy } from '@keystone-6/core/types';
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 Down
6 changes: 3 additions & 3 deletions docs/pages/docs/config/session.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ In general, you will use `SessionStrategy` objects from the `@keystone-6/auth/se

```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 Down Expand Up @@ -76,7 +76,7 @@ import { config } from '@keystone-6/core';
{% if $nextRelease %}
import { statelessSessions, storedSessions } from '@keystone-6/auth/session';
{% else /%}
import { statelessSessions, storedSessions } from '@keystone-6/core/session';
import { statelessSessions, storedSessions } from '@keystone-6/auth/session';
{% /if %}

export default config({
Expand Down Expand Up @@ -154,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
6 changes: 3 additions & 3 deletions docs/pages/docs/walkthroughs/lesson-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Having added an authentication method, we need to add a 'session', so that authe
```ts{3,12-500}[4-11]
// auth.ts
import { createAuth } from '@keystone-6/auth';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
const { withAuth } = createAuth({
listKey: 'User',
Expand Down Expand Up @@ -227,7 +227,7 @@ feature in the `auth` package:
```ts{10-12}
// auth.ts
import { createAuth } from '@keystone-6/auth';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
const { withAuth } = createAuth({
listKey: 'User',
Expand Down Expand Up @@ -259,7 +259,7 @@ Now, if you open Admin UI, you can check out the sign in flow. If you have no us
```ts
// auth.ts
import { createAuth } from '@keystone-6/auth';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';

const { withAuth } = createAuth({
listKey: 'User',
Expand Down
21 changes: 10 additions & 11 deletions examples/auth/keystone.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
import { createAuth } from '@keystone-6/auth';
import { fixPrismaPath } from '../example-utils';
import { lists } from './schema';
Expand Down Expand Up @@ -42,9 +42,15 @@ const { withAuth } = createAuth({
isAdmin: true,
},
},

// add isAdmin to the session data
sessionData: 'isAdmin',
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
sessionStrategy: statelessSessions({
// the maxAge option controls how long session cookies are valid for before they expire
maxAge: sessionMaxAge,
// the session secret is used to encrypt cookie data
secret: sessionSecret,
// add isAdmin to the session data
data: 'isAdmin',
}),
});

export default withAuth(
Expand All @@ -63,12 +69,5 @@ export default withAuth(
return session?.data?.isAdmin ?? false;
},
},
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
session: statelessSessions({
// the maxAge option controls how long session cookies are valid for before they expire
maxAge: sessionMaxAge,
// the session secret is used to encrypt cookie data
secret: sessionSecret,
}),
})
);
6 changes: 0 additions & 6 deletions examples/auth/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ type Mutation {
deleteUsers(where: [UserWhereUniqueInput!]!): [User]
endSession: Boolean!
authenticateUserWithPassword(name: String!, password: String!): UserAuthenticationWithPasswordResult
createInitialUser(data: CreateInitialUserInput!): UserAuthenticationWithPasswordSuccess!
}

union UserAuthenticationWithPasswordResult = UserAuthenticationWithPasswordSuccess | UserAuthenticationWithPasswordFailure
Expand All @@ -95,11 +94,6 @@ type UserAuthenticationWithPasswordFailure {
message: String!
}

input CreateInitialUserInput {
name: String
password: String
}

type Query {
users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: UserWhereUniqueInput): [User!]
user(where: UserWhereUniqueInput!): User
Expand Down
2 changes: 1 addition & 1 deletion examples/custom-output-paths/my-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export type TypeInfo<Session = any> = {
type __TypeInfo<Session = any> = TypeInfo<Session>;

export type Lists<Session = any> = {
[Key in keyof TypeInfo['lists']]: import('@keystone-6/core').ListConfig<TypeInfo<Session>['lists'][Key]>
[Key in keyof TypeInfo['lists']]?: import('@keystone-6/core').ListConfig<TypeInfo<Session>['lists'][Key]>
} & Record<string, import('@keystone-6/core').ListConfig<any>>;

export {}
2 changes: 1 addition & 1 deletion examples/custom-session-invalidation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const { withAuth } = createAuth({
We can then change the default `statelessSessions` by passing in a new `start` and `get` functions. In the `start` function, we add the `startTime` to the session and start the session using keystone's `start` session function. we can then customize the `get` function to check this `startTime` on the session and compare it to the `passwordChangedAt` time stored in the `Person` table.

```typescript
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
const maxSessionAge = 60 * 60 * 8; // 8 hours, in seconds

const withTimeData = (
Expand Down
6 changes: 2 additions & 4 deletions examples/custom-session-invalidation/keystone.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '@keystone-6/core';
import { statelessSessions } from '@keystone-6/core/session';
import { statelessSessions } from '@keystone-6/auth/session';
import { createAuth } from '@keystone-6/auth';
import { fixPrismaPath } from '../example-utils';
import { lists, Session } from './schema';
Expand Down Expand Up @@ -29,8 +29,7 @@ const { withAuth } = createAuth({
// the following fields are used by the "Create First User" form
fields: ['name', 'password'],
},

sessionData: 'passwordChangedAt',
sessionStrategy: statelessSessions<Session>(),
});

function withSessionInvalidation(config: Config<Session>) {
Expand Down Expand Up @@ -78,7 +77,6 @@ export default withSessionInvalidation(
},
lists,
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
session: statelessSessions<Session>(),
})
)
);
43 changes: 19 additions & 24 deletions examples/custom-session-jwt/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,27 @@ async function jwtVerify(token: string): Promise<OurJWTClaims | null> {
});
}

const jwtSessionStrategy = {
async get({ context }: { context: Context }): Promise<Session | undefined> {
if (!context.req) return;
const jwtSessionStrategy = async ({
context,
}: {
context: Context;
}): Promise<Session | undefined> => {
if (!context.req) return;

const { cookie = '' } = context.req.headers;
const [cookieName, jwt] = cookie.split('=');
if (cookieName !== 'user') return;
const { cookie = '' } = context.req.headers;
const [cookieName, jwt] = cookie.split('=');
if (cookieName !== 'user') return;

const jwtResult = await jwtVerify(jwt);
if (!jwtResult) return;
const jwtResult = await jwtVerify(jwt);
if (!jwtResult) return;

const { id } = jwtResult;
const who = await context.sudo().db.User.findOne({ where: { id } });
if (!who) return;
return {
id,
admin: who.admin,
};
},

// we don't need these unless we want to support the functions
// context.sessionStrategy.start
// context.sessionStrategy.end
//
async start() {},
async end() {},
const { id } = jwtResult;
const who = await context.sudo().db.User.findOne({ where: { id } });
if (!who) return;
return {
id,
admin: who.admin,
};
};

export default config<TypeInfo>({
Expand All @@ -98,5 +93,5 @@ export default config<TypeInfo>({
},
},
lists,
session: jwtSessionStrategy,
getSession: jwtSessionStrategy,
});
2 changes: 1 addition & 1 deletion examples/custom-session-next-auth/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@ export default config<TypeInfo<Session>>({
},
lists,
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
session: nextAuthSessionStrategy,
getSession: nextAuthSessionStrategy,
});
1 change: 0 additions & 1 deletion examples/custom-session-next-auth/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ type Mutation {
updateAuthors(data: [AuthorUpdateArgs!]!): [Author]
deleteAuthor(where: AuthorWhereUniqueInput!): Author
deleteAuthors(where: [AuthorWhereUniqueInput!]!): [Author]
endSession: Boolean!
}

type Query {
Expand Down
56 changes: 23 additions & 33 deletions examples/custom-session-next-auth/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,38 +86,28 @@ export type Session = {
id: string;
};

export const nextAuthSessionStrategy = {
async get({ context }: { context: Context }) {
const { req, res } = context;
const { headers } = req ?? {};
if (!headers?.cookie || !res) return;

// next-auth needs a different cookies structure
const cookies: Record<string, string> = {};
for (const part of headers.cookie.split(';')) {
const [key, value] = part.trim().split('=');
cookies[key] = decodeURIComponent(value);
}

const nextAuthSession = await getServerSession(
{ headers, cookies } as any,
res,
nextAuthOptions
);
if (!nextAuthSession) return;

const { authId } = nextAuthSession.keystone;
if (!authId) return;

const author = await context.sudo().db.Author.findOne({
where: { authId },
});
if (!author) return;

return { id: author.id };
},
export const nextAuthSessionStrategy = async ({ context }: { context: Context }) => {
const { req, res } = context;
const { headers } = req ?? {};
if (!headers?.cookie || !res) return;

// next-auth needs a different cookies structure
const cookies: Record<string, string> = {};
for (const part of headers.cookie.split(';')) {
const [key, value] = part.trim().split('=');
cookies[key] = decodeURIComponent(value);
}

const nextAuthSession = await getServerSession({ headers, cookies } as any, res, nextAuthOptions);
if (!nextAuthSession) return;

const { authId } = nextAuthSession.keystone;
if (!authId) return;

const author = await context.sudo().db.Author.findOne({
where: { authId },
});
if (!author) return;

// we don't need these as next-auth handle start and end for us
async start() {},
async end() {},
return { id: author.id };
};
5 changes: 3 additions & 2 deletions examples/custom-session-redis/keystone.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { config } from '@keystone-6/core';
import { storedSessions } from '@keystone-6/core/session';
import { storedSessions } from '@keystone-6/auth/session';
import { createAuth } from '@keystone-6/auth';
import { createClient } from '@redis/client';
import { fixPrismaPath } from '../example-utils';
Expand Down Expand Up @@ -30,6 +30,8 @@ const { withAuth } = createAuth({
// the following fields are used by the "Create First User" form
fields: ['name', 'password'],
},

sessionStrategy: redisSessionStrategy(),
});

const redis = createClient();
Expand Down Expand Up @@ -70,6 +72,5 @@ export default withAuth(
...fixPrismaPath,
},
lists,
session: redisSessionStrategy(),
})
);
47 changes: 21 additions & 26 deletions examples/custom-session/keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,28 @@ import { fixPrismaPath } from '../example-utils';
import { lists, Session } from './schema';
import type { Context, TypeInfo } from '.keystone/types';

const sillySessionStrategy = {
async get({ context }: { context: Context }): Promise<Session | undefined> {
if (!context.req) return;
const sillySessionStrategy = async ({
context,
}: {
context: Context;
}): Promise<Session | undefined> => {
if (!context.req) return;

// WARNING: for demonstrative purposes only, this has no authentication
// use `Cookie:user=clh9v6pcn0000sbhm9u0j6in0` for Alice (admin)
// use `Cookie:user=clh9v762w0002sbhmhhyc0340` for Bob
//
// in practice, you should use authentication for your sessions, such as OAuth or JWT
const { cookie = '' } = context.req.headers;
const [cookieName, id] = cookie.split('=');
if (cookieName !== 'user') return;

const who = await context.sudo().db.User.findOne({ where: { id } });
if (!who) return;
return {
id,
admin: who.admin,
};
},

// we don't need these unless we want to support the functions
// context.sessionStrategy.start
// context.sessionStrategy.end
// WARNING: for demonstrative purposes only, this has no authentication
// use `Cookie:user=clh9v6pcn0000sbhm9u0j6in0` for Alice (admin)
// use `Cookie:user=clh9v762w0002sbhmhhyc0340` for Bob
//
async start() {},
async end() {},
// in practice, you should use authentication for your sessions, such as OAuth or JWT
const { cookie = '' } = context.req.headers;
const [cookieName, id] = cookie.split('=');
if (cookieName !== 'user') return;

const who = await context.sudo().db.User.findOne({ where: { id } });
if (!who) return;
return {
id,
admin: who.admin,
};
};

export default config<TypeInfo>({
Expand All @@ -41,5 +36,5 @@ export default config<TypeInfo>({
...fixPrismaPath,
},
lists,
session: sillySessionStrategy,
getSession: sillySessionStrategy,
});
Loading

0 comments on commit 498e351

Please sign in to comment.