@opensaas/keystone-nextjs-auth 25.0.0 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,21 +14,18 @@ async function findMatchingIdentity(identityField, identity, queryAPI) {
14
14
  where: {
15
15
  [identityField]: identity
16
16
  }
17
- }); // Identity failures with helpful errors
18
-
17
+ });
18
+ // Identity failures with helpful errors
19
19
  let code;
20
-
21
20
  if (!item) {
22
21
  code = 'IDENTITY_NOT_FOUND';
23
22
  }
24
-
25
23
  if (code) {
26
24
  return {
27
25
  success: false,
28
26
  code
29
27
  };
30
28
  }
31
-
32
29
  return {
33
30
  success: true,
34
31
  item
@@ -40,14 +37,12 @@ async function validateNextAuth(identityField, identity, protectIdentities, item
40
37
  const {
41
38
  item
42
39
  } = match;
43
-
44
40
  if (item) {
45
41
  return {
46
42
  success: true,
47
43
  item
48
44
  };
49
45
  }
50
-
51
46
  return {
52
47
  success: false,
53
48
  code: protectIdentities ? 'FAILURE' : 'SUBJECT_NOT_FOUND'
@@ -69,17 +64,14 @@ function NextAuthPage(props) {
69
64
  sessionData,
70
65
  sessionSecret
71
66
  } = props;
72
-
73
67
  if (!query) {
74
68
  console.error('NextAuthPage got no query.');
75
69
  return null;
76
70
  }
77
-
78
71
  if (!providers || !providers.length) {
79
72
  console.error('You need to provide at least one provider.');
80
73
  return null;
81
74
  }
82
-
83
75
  const list = query[listKey];
84
76
  const protectIdentities = true;
85
77
  return NextAuth__default["default"]({
@@ -97,7 +89,6 @@ function NextAuthPage(props) {
97
89
  profile
98
90
  } = _ref;
99
91
  let identity;
100
-
101
92
  if (typeof user.id === 'string') {
102
93
  identity = user.id;
103
94
  } else if (typeof user.id === 'number') {
@@ -105,23 +96,20 @@ function NextAuthPage(props) {
105
96
  } else {
106
97
  identity = 0;
107
98
  }
108
-
109
99
  const userInput = resolver ? await resolver({
110
100
  user,
111
101
  account,
112
102
  profile
113
103
  }) : {};
114
- const result = await validateNextAuth(identityField, identity, protectIdentities, list); // ID
115
-
104
+ const result = await validateNextAuth(identityField, identity, protectIdentities, list);
105
+ // ID
116
106
  const data = _objectSpread({
117
107
  [identityField]: identity
118
108
  }, userInput);
119
-
120
109
  if (!result.success) {
121
110
  if (!autoCreate) {
122
111
  return false;
123
112
  }
124
-
125
113
  const createUser = await list.createOne({
126
114
  data
127
115
  }).then(returned => {
@@ -135,7 +123,6 @@ function NextAuthPage(props) {
135
123
  });
136
124
  return createUser.success;
137
125
  }
138
-
139
126
  const updateUser = await list.updateOne({
140
127
  where: {
141
128
  id: result.item.id
@@ -152,25 +139,20 @@ function NextAuthPage(props) {
152
139
  });
153
140
  return updateUser.success;
154
141
  },
155
-
156
142
  async redirect(_ref2) {
157
143
  let {
158
144
  url
159
145
  } = _ref2;
160
146
  return url;
161
147
  },
162
-
163
148
  async session(_ref3) {
164
149
  let {
165
150
  session,
166
151
  token
167
152
  } = _ref3;
168
153
  let returnSession = session;
169
-
170
154
  if (!token.itemId) {
171
- return {
172
- expires: '0'
173
- };
155
+ return session;
174
156
  } else {
175
157
  returnSession = _objectSpread(_objectSpread({}, session), {}, {
176
158
  data: token.data,
@@ -179,19 +161,19 @@ function NextAuthPage(props) {
179
161
  itemId: token.itemId
180
162
  });
181
163
  }
182
-
183
164
  return returnSession;
184
165
  },
185
-
186
166
  async jwt(_ref4) {
187
167
  let {
188
168
  token
189
169
  } = _ref4;
190
170
  const identity = token.sub;
171
+ if (!identity) {
172
+ return token;
173
+ }
191
174
  const result = await validateNextAuth(identityField, identity, protectIdentities, list);
192
-
193
175
  if (!result.success) {
194
- token.itemId = null;
176
+ token.itemId = undefined;
195
177
  } else {
196
178
  token.itemId = result.item.id;
197
179
  const data = await query[listKey].findOne({
@@ -202,15 +184,12 @@ function NextAuthPage(props) {
202
184
  });
203
185
  token.data = data;
204
186
  }
205
-
206
187
  const returnToken = _objectSpread(_objectSpread({}, token), {}, {
207
188
  subject: token.sub,
208
189
  listKey
209
190
  });
210
-
211
191
  return returnToken;
212
192
  }
213
-
214
193
  }
215
194
  });
216
195
  }
@@ -6,21 +6,18 @@ async function findMatchingIdentity(identityField, identity, queryAPI) {
6
6
  where: {
7
7
  [identityField]: identity
8
8
  }
9
- }); // Identity failures with helpful errors
10
-
9
+ });
10
+ // Identity failures with helpful errors
11
11
  let code;
12
-
13
12
  if (!item) {
14
13
  code = 'IDENTITY_NOT_FOUND';
15
14
  }
16
-
17
15
  if (code) {
18
16
  return {
19
17
  success: false,
20
18
  code
21
19
  };
22
20
  }
23
-
24
21
  return {
25
22
  success: true,
26
23
  item
@@ -32,14 +29,12 @@ async function validateNextAuth(identityField, identity, protectIdentities, item
32
29
  const {
33
30
  item
34
31
  } = match;
35
-
36
32
  if (item) {
37
33
  return {
38
34
  success: true,
39
35
  item
40
36
  };
41
37
  }
42
-
43
38
  return {
44
39
  success: false,
45
40
  code: protectIdentities ? 'FAILURE' : 'SUBJECT_NOT_FOUND'
@@ -61,17 +56,14 @@ function NextAuthPage(props) {
61
56
  sessionData,
62
57
  sessionSecret
63
58
  } = props;
64
-
65
59
  if (!query) {
66
60
  console.error('NextAuthPage got no query.');
67
61
  return null;
68
62
  }
69
-
70
63
  if (!providers || !providers.length) {
71
64
  console.error('You need to provide at least one provider.');
72
65
  return null;
73
66
  }
74
-
75
67
  const list = query[listKey];
76
68
  const protectIdentities = true;
77
69
  return NextAuth({
@@ -89,7 +81,6 @@ function NextAuthPage(props) {
89
81
  profile
90
82
  } = _ref;
91
83
  let identity;
92
-
93
84
  if (typeof user.id === 'string') {
94
85
  identity = user.id;
95
86
  } else if (typeof user.id === 'number') {
@@ -97,23 +88,20 @@ function NextAuthPage(props) {
97
88
  } else {
98
89
  identity = 0;
99
90
  }
100
-
101
91
  const userInput = resolver ? await resolver({
102
92
  user,
103
93
  account,
104
94
  profile
105
95
  }) : {};
106
- const result = await validateNextAuth(identityField, identity, protectIdentities, list); // ID
107
-
96
+ const result = await validateNextAuth(identityField, identity, protectIdentities, list);
97
+ // ID
108
98
  const data = _objectSpread({
109
99
  [identityField]: identity
110
100
  }, userInput);
111
-
112
101
  if (!result.success) {
113
102
  if (!autoCreate) {
114
103
  return false;
115
104
  }
116
-
117
105
  const createUser = await list.createOne({
118
106
  data
119
107
  }).then(returned => {
@@ -127,7 +115,6 @@ function NextAuthPage(props) {
127
115
  });
128
116
  return createUser.success;
129
117
  }
130
-
131
118
  const updateUser = await list.updateOne({
132
119
  where: {
133
120
  id: result.item.id
@@ -144,25 +131,20 @@ function NextAuthPage(props) {
144
131
  });
145
132
  return updateUser.success;
146
133
  },
147
-
148
134
  async redirect(_ref2) {
149
135
  let {
150
136
  url
151
137
  } = _ref2;
152
138
  return url;
153
139
  },
154
-
155
140
  async session(_ref3) {
156
141
  let {
157
142
  session,
158
143
  token
159
144
  } = _ref3;
160
145
  let returnSession = session;
161
-
162
146
  if (!token.itemId) {
163
- return {
164
- expires: '0'
165
- };
147
+ return session;
166
148
  } else {
167
149
  returnSession = _objectSpread(_objectSpread({}, session), {}, {
168
150
  data: token.data,
@@ -171,19 +153,19 @@ function NextAuthPage(props) {
171
153
  itemId: token.itemId
172
154
  });
173
155
  }
174
-
175
156
  return returnSession;
176
157
  },
177
-
178
158
  async jwt(_ref4) {
179
159
  let {
180
160
  token
181
161
  } = _ref4;
182
162
  const identity = token.sub;
163
+ if (!identity) {
164
+ return token;
165
+ }
183
166
  const result = await validateNextAuth(identityField, identity, protectIdentities, list);
184
-
185
167
  if (!result.success) {
186
- token.itemId = null;
168
+ token.itemId = undefined;
187
169
  } else {
188
170
  token.itemId = result.item.id;
189
171
  const data = await query[listKey].findOne({
@@ -194,15 +176,12 @@ function NextAuthPage(props) {
194
176
  });
195
177
  token.data = data;
196
178
  }
197
-
198
179
  const returnToken = _objectSpread(_objectSpread({}, token), {}, {
199
180
  subject: token.sub,
200
181
  listKey
201
182
  });
202
-
203
183
  return returnToken;
204
184
  }
205
-
206
185
  }
207
186
  });
208
187
  }
package/src/index.ts CHANGED
@@ -3,18 +3,20 @@ import {
3
3
  AdminFileToWrite,
4
4
  BaseListTypeInfo,
5
5
  KeystoneConfig,
6
- KeystoneContext,
7
6
  AdminUIConfig,
8
7
  BaseKeystoneTypeInfo,
9
8
  SessionStrategy,
9
+ KeystoneContext,
10
10
  } from '@keystone-6/core/types';
11
+ import type { NextApiRequest } from 'next';
12
+
11
13
  import { getSession } from 'next-auth/react';
12
14
  import { getToken } from 'next-auth/jwt';
13
15
  import { Provider } from 'next-auth/providers';
14
16
 
15
17
  import * as cookie from 'cookie';
16
-
17
- import { JWT, Session } from 'next-auth';
18
+ import { JWT } from 'next-auth/jwt';
19
+ import { Session } from 'next-auth';
18
20
  import { nextConfigTemplate } from './templates/next-config';
19
21
  // import * as Path from 'path';
20
22
 
@@ -57,34 +59,19 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
57
59
  * - to the init page when initFirstItem is configured, and there are no user in the database
58
60
  * - to the signin page when no valid session is present
59
61
  */
60
- const pageMiddleware: AdminUIConfig<BaseKeystoneTypeInfo>['pageMiddleware'] = async ({
62
+ const authMiddleware: AdminUIConfig<BaseKeystoneTypeInfo>['pageMiddleware'] = async ({
61
63
  context,
62
- isValidSession,
64
+ wasAccessAllowed,
63
65
  }) => {
64
66
  const { req, session } = context;
65
67
  const pathname = url.parse(req?.url!).pathname!;
66
68
 
67
- if (isValidSession) {
68
- if (
69
- pathname === `${customPath}/api/auth/signin` ||
70
- (pages?.signIn && pathname.includes(pages?.signIn))
71
- ) {
72
- return { kind: 'redirect', to: `${customPath}` };
73
- }
69
+ if (wasAccessAllowed) {
74
70
  if (customPath !== '' && pathname === '/') {
75
71
  return { kind: 'redirect', to: `${customPath}` };
76
72
  }
77
73
  return;
78
74
  }
79
- if (
80
- pathname.includes('/_next/') ||
81
- pathname.includes('/api/auth/') ||
82
- (pages?.signIn && pathname.includes(pages?.signIn)) ||
83
- (pages?.error && pathname.includes(pages?.error)) ||
84
- (pages?.signOut && pathname.includes(pages?.signOut))
85
- ) {
86
- return;
87
- }
88
75
  if (!session && !pathname.includes(`${customPath}/api/auth/`)) {
89
76
  return {
90
77
  kind: 'redirect',
@@ -94,14 +81,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
94
81
  };
95
82
 
96
83
  /**
97
- * getAdditionalFiles
84
+ * authGetAdditionalFiles
98
85
  *
99
86
  * This function adds files to be generated into the Admin UI build. Must be added to the
100
87
  * ui.getAdditionalFiles config.
101
88
  *
102
89
  * The signin page is always included, and the init page is included when initFirstItem is set
103
90
  */
104
- const getAdditionalFiles = () => {
91
+ const authGetAdditionalFiles = () => {
105
92
  const filesToWrite: AdminFileToWrite[] = [
106
93
  {
107
94
  mode: 'write',
@@ -128,8 +115,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
128
115
  *
129
116
  * Must be added to the ui.publicPages config
130
117
  */
131
- const publicPages = [
132
- `${customPath}/api/__keystone_api_build`,
118
+ const authPublicPages = [
133
119
  `${customPath}/api/auth/csrf`,
134
120
  `${customPath}/api/auth/signin`,
135
121
  `${customPath}/api/auth/callback`,
@@ -142,8 +128,8 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
142
128
  // @ts-ignore
143
129
  function addPages(provider: Provider) {
144
130
  const name = provider.id;
145
- publicPages.push(`${customPath}/api/auth/signin/${name}`);
146
- publicPages.push(`${customPath}/api/auth/callback/${name}`);
131
+ authPublicPages.push(`${customPath}/api/auth/signin/${name}`);
132
+ authPublicPages.push(`${customPath}/api/auth/callback/${name}`);
147
133
  }
148
134
  providers.map(addPages);
149
135
 
@@ -197,17 +183,19 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
197
183
  const { get, end, ...sessionStrategy } = _sessionStrategy;
198
184
  return {
199
185
  ...sessionStrategy,
200
- get: async ({ req, createContext }) => {
186
+ get: async ({ context }) => {
187
+ const { req } = context;
201
188
  const pathname = url.parse(req?.url!).pathname!;
202
189
  let nextSession: Session | JWT | null;
190
+ if (!req) return;
203
191
  if (pathname.includes('/api/auth')) {
204
192
  return;
205
193
  }
206
- const sudoContext = createContext({ sudo: true });
194
+ const sudoContext = context.sudo();
207
195
 
208
196
  if (req.headers?.authorization?.split(' ')[0] === 'Bearer') {
209
197
  nextSession = await getToken({
210
- req,
198
+ req: req as NextApiRequest,
211
199
  secret: sessionSecret,
212
200
  });
213
201
  } else {
@@ -231,7 +219,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
231
219
  data: nextSession.data,
232
220
  };
233
221
 
234
- const userSession = await get({ req: reqWithUser, createContext });
222
+ const userSession = await get({ context });
235
223
 
236
224
  return {
237
225
  ...userSession,
@@ -241,12 +229,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
241
229
  itemId: nextSession.itemId,
242
230
  };
243
231
  },
244
- end: async ({ res, req, createContext }) => {
245
- await end({ res, req, createContext });
232
+ end: async ({ context }) => {
233
+ await end({ context });
246
234
  const TOKEN_NAME =
247
235
  process.env.NODE_ENV === 'production'
248
236
  ? '__Secure-next-auth.session-token'
249
237
  : 'next-auth.session-token';
238
+ const { req, res } = context;
239
+ if (!req || !res) return;
250
240
  res.setHeader(
251
241
  'Set-Cookie',
252
242
  cookie.serialize(TOKEN_NAME, '', {
@@ -264,6 +254,9 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
264
254
  };
265
255
  };
266
256
 
257
+ function defaultIsAccessAllowed({ session }: KeystoneContext) {
258
+ return session !== undefined;
259
+ }
267
260
  /**
268
261
  * withAuth
269
262
  *
@@ -277,35 +270,35 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
277
270
  const withAuth = (keystoneConfig: KeystoneConfig): KeystoneOAuthConfig => {
278
271
  validateConfig(keystoneConfig);
279
272
  let { ui } = keystoneConfig;
280
- if (keystoneConfig.ui) {
273
+ if (!ui?.isDisabled) {
274
+ const {
275
+ getAdditionalFiles = [],
276
+ isAccessAllowed = defaultIsAccessAllowed,
277
+ pageMiddleware,
278
+ publicPages = [],
279
+ } = ui || {};
281
280
  ui = {
282
- ...keystoneConfig.ui,
283
- publicPages: [...(keystoneConfig.ui.publicPages || []), ...publicPages],
284
- getAdditionalFiles: [...(keystoneConfig.ui?.getAdditionalFiles || []), getAdditionalFiles],
285
- pageMiddleware: async args =>
286
- (await pageMiddleware(args)) ?? keystoneConfig?.ui?.pageMiddleware?.(args),
281
+ ...ui,
282
+ publicPages: [...publicPages, ...authPublicPages],
287
283
  isAccessAllowed: async (context: KeystoneContext) => {
288
- const { req } = context;
289
- const pathname = url.parse(req?.url!).pathname!;
290
-
291
- // Allow nextjs scripts and static files to be accessed without auth
292
- if (pathname.includes('/_next/')) {
293
- return true;
294
- }
295
-
296
- // Allow keystone to access /api/__keystone_api_build for hot reloading
284
+ const pathname = url.parse(context.req?.url!).pathname!;
297
285
  if (
298
- process.env.NODE_ENV !== 'production' &&
299
- context.req?.url !== undefined &&
300
- new URL(context.req.url, 'http://example.com').pathname ===
301
- `${customPath}/api/__keystone_api_build`
286
+ pathname.startsWith(`${customPath}/_next`) ||
287
+ pathname.startsWith(`${customPath}/__next`) ||
288
+ pathname.startsWith(`${customPath}/api/auth/`) ||
289
+ (pages?.signIn && pathname.includes(pages?.signIn)) ||
290
+ (pages?.error && pathname.includes(pages?.error)) ||
291
+ (pages?.signOut && pathname.includes(pages?.signOut))
302
292
  ) {
303
293
  return true;
304
294
  }
305
-
306
- return keystoneConfig.ui?.isAccessAllowed
307
- ? keystoneConfig.ui.isAccessAllowed(context)
308
- : context.session !== undefined;
295
+ return await isAccessAllowed(context);
296
+ },
297
+ getAdditionalFiles: [...getAdditionalFiles, authGetAdditionalFiles],
298
+ pageMiddleware: async args => {
299
+ const shouldRedirect = await authMiddleware(args);
300
+ if (shouldRedirect) return shouldRedirect;
301
+ return pageMiddleware?.(args);
309
302
  },
310
303
  };
311
304
  }
@@ -116,25 +116,28 @@ export default function NextAuthPage(props: NextAuthPageProps) {
116
116
  async session({ session, token }) {
117
117
  let returnSession = session;
118
118
  if (!token.itemId) {
119
- return { expires: '0' };
119
+ return session;
120
120
  } else {
121
121
  returnSession = {
122
122
  ...session,
123
123
  data: token.data,
124
124
  subject: token.sub,
125
- listKey: token.listKey as string,
126
- itemId: token.itemId as string,
125
+ listKey: token.listKey,
126
+ itemId: token.itemId,
127
127
  };
128
128
  }
129
129
 
130
130
  return returnSession;
131
131
  },
132
132
  async jwt({ token }) {
133
- const identity = token.sub as number | string;
133
+ const identity = token.sub;
134
+ if (!identity) {
135
+ return token;
136
+ }
134
137
  const result = await validateNextAuth(identityField, identity, protectIdentities, list);
135
138
 
136
139
  if (!result.success) {
137
- token.itemId = null;
140
+ token.itemId = undefined;
138
141
  } else {
139
142
  token.itemId = result.item.id;
140
143
  const data = await query[listKey].findOne({
@@ -1,61 +1,12 @@
1
1
  import ejs from 'ejs';
2
2
 
3
3
  const template = `
4
- const Path = require('path');
5
- // @ts-ignore
6
- const withPreconstruct = require('@preconstruct/next');
4
+ const keystoneConfig = require('@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/next-config').config;
7
5
 
8
- module.exports = withPreconstruct({
9
- typescript: {
10
- ignoreBuildErrors: true,
11
- },
12
- env: {
13
- NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:<%= process.env.PORT || 3000 %><%= keystonePath || '' %>/api/auth',
14
- },
15
- eslint: {
16
- ignoreDuringBuilds: true,
17
- },
18
- webpack(config, { isServer }) {
19
- config.resolve.alias = {
20
- ...config.resolve.alias,
21
- react: Path.dirname(require.resolve('react/package.json')),
22
- 'react-dom': Path.dirname(require.resolve('react-dom/package.json')),
23
- '@keystone-6/core': Path.dirname(
24
- require.resolve('@keystone-6/core/package.json')
25
- ),
26
- };
27
- if (isServer) {
28
- config.externals = [
29
- ...config.externals,
30
- /@keystone-6\\/core(?!\\/___internal-do-not-use-will-break-in-patch\\/admin-ui\\/id-field-view|\\/fields\\/types\\/[^\\/]+\\/views)/,
31
- /.prisma\\/client/
32
- ];
33
- // we need to set these to true so that when __dirname/__filename is used
34
- // to resolve the location of field views, we will get a path that we can use
35
- // rather than just the __dirname/__filename of the generated file.
36
- // https://webpack.js.org/configuration/node/#node__filename
37
- (_config$node = config.node) !== null && _config$node !== void 0 ? _config$node : config.node = {};
38
- config.node.__dirname = true;
39
- config.node.__filename = true;
40
- }
41
- return config;
42
- },
43
- <% if (keystonePath) { %>
44
- <% if (process.env.NODE_ENV != 'production') { %>
45
- async rewrites() {
46
- return [
47
- {
48
- source: '/api/__keystone_api_build',
49
- destination: 'http://localhost:<%= process.env.PORT || 3000 %><%= keystonePath || '' %>/api/__keystone_api_build',
50
- basePath: false
51
- }
52
- ];
53
- },
54
- <% }%>
55
- basePath: '<%= keystonePath || '' %>'
56
- <% } %>
57
- });
58
- `;
6
+ module.exports = {
7
+ ...keystoneConfig,
8
+ basePath: '<%= keystonePath || '' %>'
9
+ };`;
59
10
  export const nextConfigTemplate = ({ keystonePath }: { keystonePath: string }) => {
60
11
  const nextConfigOut = ejs.render(template, { keystonePath });
61
12
 
@@ -1,26 +1,11 @@
1
- import type { ServerResponse, IncomingMessage } from 'http';
2
- import type { NextRequest } from 'next/server';
3
1
  import { Provider } from 'next-auth/providers';
4
2
  import { CookiesOptions, PagesOptions } from 'next-auth';
5
- import { BaseListTypeInfo, KeystoneConfig, CreateContext } from '@keystone-6/core/types';
6
-
7
- type NextAuthResponse = IncomingMessage & NextRequest;
3
+ import { BaseListTypeInfo, KeystoneConfig, KeystoneContext } from '@keystone-6/core/types';
8
4
 
9
5
  export declare type AuthSessionStrategy<StoredSessionData> = {
10
- start: (args: {
11
- res: ServerResponse;
12
- data: any;
13
- createContext: CreateContext;
14
- }) => Promise<string>;
15
- end: (args: {
16
- req: IncomingMessage;
17
- res: ServerResponse;
18
- createContext: CreateContext;
19
- }) => Promise<void>;
20
- get: (args: {
21
- req: NextAuthResponse;
22
- createContext: CreateContext;
23
- }) => Promise<StoredSessionData | undefined>;
6
+ start: (args: { data: any; context: KeystoneContext }) => Promise<unknown>;
7
+ end: (args: { context: KeystoneContext }) => Promise<unknown>;
8
+ get: (args: { context: KeystoneContext }) => Promise<StoredSessionData | undefined>;
24
9
  };
25
10
 
26
11
  export type NextAuthProviders = Provider[];