@opensaas/keystone-nextjs-auth 20.5.0 → 21.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -58,23 +58,42 @@ async function validateNextAuth(identityField, identity, protectIdentities, item
58
58
 
59
59
  function NextAuthPage(props) {
60
60
  const {
61
+ autoCreate,
62
+ cookies,
63
+ events,
64
+ identityField,
65
+ jwt,
66
+ listKey,
67
+ pages,
61
68
  providers,
62
69
  query,
63
- identityField,
70
+ resolver,
64
71
  sessionData,
65
- listKey,
66
- autoCreate,
67
- userMap,
68
- accountMap,
69
- profileMap,
70
72
  sessionSecret
71
- } = props;
73
+ } = props; // TODO: (v1.1). https://github.com/ijsto/keystone-6-oauth/projects/1#card-78602004
74
+
75
+ console.log('NextAuthPages... ', pages);
76
+
77
+ if (!query) {
78
+ console.error('NextAuthPage got no query.');
79
+ return null;
80
+ }
81
+
82
+ if (!providers || !providers.length) {
83
+ console.error('You need to provide at least one provider.');
84
+ return null;
85
+ }
86
+
72
87
  const list = query[listKey];
73
88
  const queryAPI = query[listKey];
74
89
  const protectIdentities = true;
75
90
  return NextAuth__default["default"]({
76
- secret: sessionSecret,
91
+ cookies,
77
92
  providers,
93
+ pages: pages || {},
94
+ events: events || {},
95
+ jwt: jwt || {},
96
+ secret: sessionSecret,
78
97
  callbacks: {
79
98
  async signIn({
80
99
  user,
@@ -91,28 +110,16 @@ function NextAuthPage(props) {
91
110
  identity = 0;
92
111
  }
93
112
 
94
- const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
95
- const data = {}; // eslint-disable-next-line no-restricted-syntax
96
-
97
- for (const key in userMap) {
98
- if (Object.prototype.hasOwnProperty.call(userMap, key)) {
99
- data[key] = user[userMap[key]];
100
- }
101
- } // eslint-disable-next-line no-restricted-syntax
102
-
113
+ const userInput = resolver ? await resolver({
114
+ user,
115
+ account,
116
+ profile
117
+ }) : {};
118
+ const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI); // ID
103
119
 
104
- for (const key in accountMap) {
105
- if (Object.prototype.hasOwnProperty.call(accountMap, key)) {
106
- data[key] = account[accountMap[key]];
107
- }
108
- } // eslint-disable-next-line no-restricted-syntax
109
-
110
-
111
- for (const key in profileMap) {
112
- if (Object.prototype.hasOwnProperty.call(profileMap, key)) {
113
- data[key] = profile[profileMap[key]];
114
- }
115
- }
120
+ const data = _objectSpread({
121
+ [identityField]: identity
122
+ }, userInput);
116
123
 
117
124
  if (!result.success) {
118
125
  if (!autoCreate) {
@@ -167,9 +174,7 @@ function NextAuthPage(props) {
167
174
  const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
168
175
 
169
176
  if (!result.success) {
170
- return {
171
- result: false
172
- };
177
+ return token;
173
178
  }
174
179
 
175
180
  token.itemId = result.item.id;
@@ -58,23 +58,42 @@ async function validateNextAuth(identityField, identity, protectIdentities, item
58
58
 
59
59
  function NextAuthPage(props) {
60
60
  const {
61
+ autoCreate,
62
+ cookies,
63
+ events,
64
+ identityField,
65
+ jwt,
66
+ listKey,
67
+ pages,
61
68
  providers,
62
69
  query,
63
- identityField,
70
+ resolver,
64
71
  sessionData,
65
- listKey,
66
- autoCreate,
67
- userMap,
68
- accountMap,
69
- profileMap,
70
72
  sessionSecret
71
- } = props;
73
+ } = props; // TODO: (v1.1). https://github.com/ijsto/keystone-6-oauth/projects/1#card-78602004
74
+
75
+ console.log('NextAuthPages... ', pages);
76
+
77
+ if (!query) {
78
+ console.error('NextAuthPage got no query.');
79
+ return null;
80
+ }
81
+
82
+ if (!providers || !providers.length) {
83
+ console.error('You need to provide at least one provider.');
84
+ return null;
85
+ }
86
+
72
87
  const list = query[listKey];
73
88
  const queryAPI = query[listKey];
74
89
  const protectIdentities = true;
75
90
  return NextAuth__default["default"]({
76
- secret: sessionSecret,
91
+ cookies,
77
92
  providers,
93
+ pages: pages || {},
94
+ events: events || {},
95
+ jwt: jwt || {},
96
+ secret: sessionSecret,
78
97
  callbacks: {
79
98
  async signIn({
80
99
  user,
@@ -91,28 +110,16 @@ function NextAuthPage(props) {
91
110
  identity = 0;
92
111
  }
93
112
 
94
- const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
95
- const data = {}; // eslint-disable-next-line no-restricted-syntax
96
-
97
- for (const key in userMap) {
98
- if (Object.prototype.hasOwnProperty.call(userMap, key)) {
99
- data[key] = user[userMap[key]];
100
- }
101
- } // eslint-disable-next-line no-restricted-syntax
102
-
113
+ const userInput = resolver ? await resolver({
114
+ user,
115
+ account,
116
+ profile
117
+ }) : {};
118
+ const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI); // ID
103
119
 
104
- for (const key in accountMap) {
105
- if (Object.prototype.hasOwnProperty.call(accountMap, key)) {
106
- data[key] = account[accountMap[key]];
107
- }
108
- } // eslint-disable-next-line no-restricted-syntax
109
-
110
-
111
- for (const key in profileMap) {
112
- if (Object.prototype.hasOwnProperty.call(profileMap, key)) {
113
- data[key] = profile[profileMap[key]];
114
- }
115
- }
120
+ const data = _objectSpread({
121
+ [identityField]: identity
122
+ }, userInput);
116
123
 
117
124
  if (!result.success) {
118
125
  if (!autoCreate) {
@@ -167,9 +174,7 @@ function NextAuthPage(props) {
167
174
  const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
168
175
 
169
176
  if (!result.success) {
170
- return {
171
- result: false
172
- };
177
+ return token;
173
178
  }
174
179
 
175
180
  token.itemId = result.item.id;
@@ -49,23 +49,42 @@ async function validateNextAuth(identityField, identity, protectIdentities, item
49
49
 
50
50
  function NextAuthPage(props) {
51
51
  const {
52
+ autoCreate,
53
+ cookies,
54
+ events,
55
+ identityField,
56
+ jwt,
57
+ listKey,
58
+ pages,
52
59
  providers,
53
60
  query,
54
- identityField,
61
+ resolver,
55
62
  sessionData,
56
- listKey,
57
- autoCreate,
58
- userMap,
59
- accountMap,
60
- profileMap,
61
63
  sessionSecret
62
- } = props;
64
+ } = props; // TODO: (v1.1). https://github.com/ijsto/keystone-6-oauth/projects/1#card-78602004
65
+
66
+ console.log('NextAuthPages... ', pages);
67
+
68
+ if (!query) {
69
+ console.error('NextAuthPage got no query.');
70
+ return null;
71
+ }
72
+
73
+ if (!providers || !providers.length) {
74
+ console.error('You need to provide at least one provider.');
75
+ return null;
76
+ }
77
+
63
78
  const list = query[listKey];
64
79
  const queryAPI = query[listKey];
65
80
  const protectIdentities = true;
66
81
  return NextAuth({
67
- secret: sessionSecret,
82
+ cookies,
68
83
  providers,
84
+ pages: pages || {},
85
+ events: events || {},
86
+ jwt: jwt || {},
87
+ secret: sessionSecret,
69
88
  callbacks: {
70
89
  async signIn({
71
90
  user,
@@ -82,28 +101,16 @@ function NextAuthPage(props) {
82
101
  identity = 0;
83
102
  }
84
103
 
85
- const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
86
- const data = {}; // eslint-disable-next-line no-restricted-syntax
87
-
88
- for (const key in userMap) {
89
- if (Object.prototype.hasOwnProperty.call(userMap, key)) {
90
- data[key] = user[userMap[key]];
91
- }
92
- } // eslint-disable-next-line no-restricted-syntax
93
-
104
+ const userInput = resolver ? await resolver({
105
+ user,
106
+ account,
107
+ profile
108
+ }) : {};
109
+ const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI); // ID
94
110
 
95
- for (const key in accountMap) {
96
- if (Object.prototype.hasOwnProperty.call(accountMap, key)) {
97
- data[key] = account[accountMap[key]];
98
- }
99
- } // eslint-disable-next-line no-restricted-syntax
100
-
101
-
102
- for (const key in profileMap) {
103
- if (Object.prototype.hasOwnProperty.call(profileMap, key)) {
104
- data[key] = profile[profileMap[key]];
105
- }
106
- }
111
+ const data = _objectSpread({
112
+ [identityField]: identity
113
+ }, userInput);
107
114
 
108
115
  if (!result.success) {
109
116
  if (!autoCreate) {
@@ -158,9 +165,7 @@ function NextAuthPage(props) {
158
165
  const result = await validateNextAuth(identityField, identity, protectIdentities, queryAPI);
159
166
 
160
167
  if (!result.success) {
161
- return {
162
- result: false
163
- };
168
+ return token;
164
169
  }
165
170
 
166
171
  token.itemId = result.item.id;
@@ -1,15 +1,11 @@
1
1
  import type { BaseItem } from '@keystone-6/core/types';
2
2
  import { graphql } from '@keystone-6/core';
3
3
 
4
- import { AuthGqlNames } from '../types';
5
-
6
4
  export function getBaseAuthSchema({
7
5
  listKey,
8
- gqlNames,
9
6
  base,
10
7
  }: {
11
8
  listKey: string;
12
- gqlNames: AuthGqlNames;
13
9
  base: graphql.BaseSchemaMeta;
14
10
  }) {
15
11
  const extension = {
package/src/index.ts CHANGED
@@ -10,18 +10,14 @@ import {
10
10
  } from '@keystone-6/core/types';
11
11
  import { getSession } from 'next-auth/react';
12
12
  import { getToken } from 'next-auth/jwt';
13
- import * as cookie from 'cookie';
14
13
  import { Provider } from 'next-auth/providers';
15
- import { NextApiRequest } from 'next';
14
+
15
+ import * as cookie from 'cookie';
16
+
16
17
  import { nextConfigTemplate } from './templates/next-config';
17
18
  // import * as Path from 'path';
18
19
 
19
- import {
20
- AuthConfig,
21
- AuthGqlNames,
22
- KeystoneAuthConfig,
23
- NextAuthSession,
24
- } from './types';
20
+ import { AuthConfig, KeystoneOAuthConfig, NextAuthSession } from './types';
25
21
  import { getSchemaExtension } from './schema';
26
22
  import { authTemplate } from './templates/auth';
27
23
 
@@ -31,33 +27,23 @@ import { authTemplate } from './templates/auth';
31
27
  * Generates config for Keystone to implement standard auth features.
32
28
  */
33
29
 
34
- export type { NextAuthProviders, KeystoneAuthConfig } from './types';
30
+ export type { NextAuthProviders, KeystoneOAuthConfig } from './types';
35
31
  export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
36
- listKey,
37
- identityField,
38
- sessionData,
39
32
  autoCreate,
40
- userMap,
41
- accountMap,
42
- profileMap,
33
+ cookies,
34
+ identityField,
35
+ listKey,
43
36
  keystonePath,
37
+ pages,
38
+ resolver,
44
39
  providers,
40
+ sessionData,
45
41
  sessionSecret,
46
42
  }: AuthConfig<GeneratedListTypes>) {
47
43
  // The protectIdentities flag is currently under review to see whether it should be
48
44
  // part of the createAuth API (in which case its use cases need to be documented and tested)
49
45
  // or whether always being true is what we want, in which case we can refactor our code
50
46
  // to match this. -TL
51
- const gqlNames: AuthGqlNames = {
52
- // Core
53
- authenticateItemWithPassword: `authenticate${listKey}WithPassword`,
54
- ItemAuthenticationWithPasswordResult: `${listKey}AuthenticationWithPasswordResult`,
55
- ItemAuthenticationWithPasswordSuccess: `${listKey}AuthenticationWithPasswordSuccess`,
56
- ItemAuthenticationWithPasswordFailure: `${listKey}AuthenticationWithPasswordFailure`,
57
- // Initial data
58
- CreateInitialInput: `CreateInitial${listKey}Input`,
59
- createInitialItem: `createInitial${listKey}`,
60
- };
61
47
 
62
48
  const customPath = !keystonePath || keystonePath === '/' ? '' : keystonePath;
63
49
  /**
@@ -74,9 +60,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
74
60
  async ({ context, isValidSession }) => {
75
61
  const { req, session } = context;
76
62
  const pathname = url.parse(req?.url!).pathname!;
77
- if (pathname === `${customPath}/api/__keystone_api_build`) {
78
- return;
79
- }
63
+
80
64
  if (isValidSession) {
81
65
  if (pathname === `${customPath}/api/auth/signin`) {
82
66
  return { kind: 'redirect', to: `${customPath}` };
@@ -86,8 +70,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
86
70
  }
87
71
  return;
88
72
  }
89
-
90
- if (!session && !pathname.includes(`${customPath}/api/auth/`)) {
73
+ if (pathname.includes('/_next/') || pathname.includes('/api/auth/')) {
74
+ return;
75
+ }
76
+ if (
77
+ !session &&
78
+ !pathname.includes(`${customPath}/api/auth/`) &&
79
+ !(Object.values(pages).indexOf(pathname) > -1)
80
+ ) {
91
81
  return { kind: 'redirect', to: `${customPath}/api/auth/signin` };
92
82
  }
93
83
  };
@@ -106,14 +96,10 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
106
96
  mode: 'write',
107
97
  outputPath: 'pages/api/auth/[...nextauth].js',
108
98
  src: authTemplate({
109
- gqlNames,
99
+ autoCreate,
110
100
  identityField,
111
- sessionData,
112
101
  listKey,
113
- autoCreate,
114
- userMap,
115
- accountMap,
116
- profileMap,
102
+ sessionData,
117
103
  sessionSecret,
118
104
  }),
119
105
  },
@@ -132,13 +118,17 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
132
118
  * Must be added to the ui.publicPages config
133
119
  */
134
120
  const publicPages = [
121
+ `${customPath}/api/__keystone_api_build`,
135
122
  `${customPath}/api/auth/csrf`,
136
123
  `${customPath}/api/auth/signin`,
137
124
  `${customPath}/api/auth/callback`,
138
125
  `${customPath}/api/auth/session`,
139
126
  `${customPath}/api/auth/providers`,
140
127
  `${customPath}/api/auth/signout`,
128
+ `${customPath}/api/auth/error`,
141
129
  ];
130
+ // TODO: Add Provider Types
131
+ // @ts-ignore
142
132
  function addPages(provider: Provider) {
143
133
  const name = provider.id;
144
134
  publicPages.push(`${customPath}/api/auth/signin/${name}`);
@@ -154,7 +144,6 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
154
144
  const extendGraphqlSchema = getSchemaExtension({
155
145
  identityField,
156
146
  listKey,
157
- gqlNames,
158
147
  });
159
148
 
160
149
  /**
@@ -169,13 +158,16 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
169
158
  throw new Error(msg);
170
159
  }
171
160
 
161
+ // TODO: Check if providers
162
+ // TODO: Check other required commands/data
163
+
172
164
  // TODO: Check for String-like typing for identityField? How?
173
165
  // TODO: Validate that the identifyField is unique.
174
166
  // TODO: If this field isn't required, what happens if I try to log in as `null`?
175
167
  const identityFieldConfig = listConfig.fields[identityField];
176
168
  if (identityFieldConfig === undefined) {
177
- const i = JSON.stringify(identityField);
178
- const msg = `A createAuth() invocation for the "${listKey}" list specifies ${i} as its identityField but no field with that key exists on the list.`;
169
+ const identityFieldName = JSON.stringify(identityField);
170
+ const msg = `A createAuth() invocation for the "${listKey}" list specifies ${identityFieldName} as its identityField but no field with that key exists on the list.`;
179
171
  throw new Error(msg);
180
172
  }
181
173
  };
@@ -194,18 +186,25 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
194
186
  const { get, start, ...sessionStrategy } = _sessionStrategy;
195
187
  return {
196
188
  ...sessionStrategy,
197
- start,
189
+ start: async ({ res }) => {
190
+ console.log('start');
191
+
192
+ const session = await start({ res });
193
+ return session;
194
+ },
198
195
  get: async ({ req }) => {
199
196
  const pathname = url.parse(req?.url!).pathname!;
200
197
  if (pathname.includes('/api/auth')) {
201
198
  return;
202
199
  }
203
200
  if (req.headers.authorization?.split(' ')[0] === 'Bearer') {
204
- const request = req as NextApiRequest;
205
- const token = await getToken({ req: request, secret: sessionSecret });
201
+ const token = (await getToken({
202
+ req,
203
+ secret: sessionSecret,
204
+ })) as NextAuthSession;
206
205
 
207
206
  if (token?.data?.id) {
208
- return token as NextAuthSession;
207
+ return token;
209
208
  }
210
209
  }
211
210
  const nextSession: unknown = await getSession({ req });
@@ -228,6 +227,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
228
227
  secure: process.env.NODE_ENV === 'production',
229
228
  path: '/',
230
229
  sameSite: 'lax',
230
+ // TODO: Update parse to URL
231
231
  domain: url.parse(req.url as string).hostname as string,
232
232
  })
233
233
  );
@@ -245,7 +245,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
245
245
  * It validates the auth config against the provided keystone config, and preserves existing
246
246
  * config by composing existing extendGraphqlSchema functions and ui config.
247
247
  */
248
- const withAuth = (keystoneConfig: KeystoneConfig): KeystoneAuthConfig => {
248
+ const withAuth = (keystoneConfig: KeystoneConfig): KeystoneOAuthConfig => {
249
249
  validateConfig(keystoneConfig);
250
250
  let { ui } = keystoneConfig;
251
251
  if (keystoneConfig.ui) {
@@ -261,6 +261,15 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
261
261
  keystoneConfig?.ui?.pageMiddleware?.(args),
262
262
  enableSessionItem: true,
263
263
  isAccessAllowed: async (context: KeystoneContext) => {
264
+ const { req } = context;
265
+ const pathname = url.parse(req?.url!).pathname!;
266
+
267
+ // Allow nextjs scripts and static files to be accessed without auth
268
+ if (pathname.includes('/_next/')) {
269
+ return true;
270
+ }
271
+
272
+ // Allow keystone to access /api/__keystone_api_build for hot reloading
264
273
  if (
265
274
  process.env.NODE_ENV !== 'production' &&
266
275
  context.req?.url !== undefined &&
@@ -269,25 +278,10 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
269
278
  ) {
270
279
  return true;
271
280
  }
272
- // Allow access to the adminMeta data from the /init path to correctly render that page
273
- // even if the user isn't logged in (which should always be the case if they're seeing /init)
274
- const headers = context.req?.headers;
275
- const host = headers
276
- ? headers['x-forwarded-host'] || headers.host
277
- : null;
278
- const thisUrl = headers?.referer
279
- ? new URL(headers.referer)
280
- : undefined;
281
- const accessingInitPage =
282
- thisUrl?.pathname === '/init' &&
283
- thisUrl?.host === host &&
284
- (await context.sudo().query[listKey].count({})) === 0;
285
- return (
286
- accessingInitPage ||
287
- (keystoneConfig.ui?.isAccessAllowed
288
- ? keystoneConfig.ui.isAccessAllowed(context)
289
- : context.session !== undefined)
290
- );
281
+
282
+ return keystoneConfig.ui?.isAccessAllowed
283
+ ? keystoneConfig.ui.isAccessAllowed(context)
284
+ : context.session !== undefined;
291
285
  },
292
286
  };
293
287
  }
@@ -300,8 +294,11 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
300
294
  return {
301
295
  ...keystoneConfig,
302
296
  ui,
303
- session,
297
+ cookies,
304
298
  providers,
299
+ pages,
300
+ resolver,
301
+ session,
305
302
  lists: {
306
303
  ...keystoneConfig.lists,
307
304
  },