@opensaas/keystone-nextjs-auth 25.0.0 → 26.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.
@@ -160,9 +160,7 @@ function NextAuthPage(props) {
160
160
  let returnSession = session;
161
161
 
162
162
  if (!token.itemId) {
163
- return {
164
- expires: '0'
165
- };
163
+ return session;
166
164
  } else {
167
165
  returnSession = _objectSpread(_objectSpread({}, session), {}, {
168
166
  data: token.data,
@@ -180,10 +178,15 @@ function NextAuthPage(props) {
180
178
  token
181
179
  } = _ref4;
182
180
  const identity = token.sub;
181
+
182
+ if (!identity) {
183
+ return token;
184
+ }
185
+
183
186
  const result = await validateNextAuth(identityField, identity, protectIdentities, list);
184
187
 
185
188
  if (!result.success) {
186
- token.itemId = null;
189
+ token.itemId = undefined;
187
190
  } else {
188
191
  token.itemId = result.item.id;
189
192
  const data = await query[listKey].findOne({
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,7 +59,7 @@ 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
64
  isValidSession,
63
65
  }) => {
@@ -65,26 +67,11 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
65
67
  const pathname = url.parse(req?.url!).pathname!;
66
68
 
67
69
  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
- }
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[];
@@ -2,18 +2,25 @@ import NextAuth from 'next-auth';
2
2
  import { JWT } from 'next-auth/jwt';
3
3
 
4
4
  declare module 'next-auth' {
5
+ interface Session extends JWT {
6
+ user?: any;
7
+ listKey?: string;
8
+ itemId?: string;
9
+ subject?: string | number | null | undefined;
10
+ expires?: string;
11
+ }
12
+ }
13
+
14
+ declare module 'next-auth/jwt' {
5
15
  interface JWT {
6
16
  data?: any | undefined;
7
- subject?: string | undefined;
17
+ subject?: string | number | null | undefined;
8
18
  listKey?: string;
9
- itemId?: string | undefined;
19
+ itemId?: string;
10
20
  name?: string | null | undefined;
11
21
  email?: string | null | undefined;
12
22
  picture?: string | null | undefined;
13
- sub?: string | null | undefined;
23
+ sub?: string | number | null | undefined;
14
24
  expires?: string | null | undefined;
15
25
  }
16
- interface Session extends JWT {
17
- user?: any;
18
- }
19
26
  }