@opensaas/keystone-nextjs-auth 20.5.0 → 21.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.
@@ -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
  },