@opensaas/keystone-nextjs-auth 25.0.0 → 26.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  }