@opensaas/keystone-nextjs-auth 20.3.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.
package/src/index.ts CHANGED
@@ -9,16 +9,15 @@ import {
9
9
  BaseKeystoneTypeInfo,
10
10
  } from '@keystone-6/core/types';
11
11
  import { getSession } from 'next-auth/react';
12
+ import { getToken } from 'next-auth/jwt';
13
+ import { Provider } from 'next-auth/providers';
14
+
12
15
  import * as cookie from 'cookie';
16
+
13
17
  import { nextConfigTemplate } from './templates/next-config';
14
18
  // import * as Path from 'path';
15
19
 
16
- import {
17
- AuthConfig,
18
- AuthGqlNames,
19
- KeystoneAuthConfig,
20
- NextAuthSession,
21
- } from './types';
20
+ import { AuthConfig, KeystoneOAuthConfig, NextAuthSession } from './types';
22
21
  import { getSchemaExtension } from './schema';
23
22
  import { authTemplate } from './templates/auth';
24
23
 
@@ -28,33 +27,23 @@ import { authTemplate } from './templates/auth';
28
27
  * Generates config for Keystone to implement standard auth features.
29
28
  */
30
29
 
31
- export type { NextAuthProviders, KeystoneAuthConfig } from './types';
30
+ export type { NextAuthProviders, KeystoneOAuthConfig } from './types';
32
31
  export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
33
- listKey,
34
- identityField,
35
- sessionData,
36
32
  autoCreate,
37
- userMap,
38
- accountMap,
39
- profileMap,
33
+ cookies,
34
+ identityField,
35
+ listKey,
40
36
  keystonePath,
37
+ pages,
38
+ resolver,
41
39
  providers,
40
+ sessionData,
42
41
  sessionSecret,
43
42
  }: AuthConfig<GeneratedListTypes>) {
44
43
  // The protectIdentities flag is currently under review to see whether it should be
45
44
  // part of the createAuth API (in which case its use cases need to be documented and tested)
46
45
  // or whether always being true is what we want, in which case we can refactor our code
47
46
  // to match this. -TL
48
- const gqlNames: AuthGqlNames = {
49
- // Core
50
- authenticateItemWithPassword: `authenticate${listKey}WithPassword`,
51
- ItemAuthenticationWithPasswordResult: `${listKey}AuthenticationWithPasswordResult`,
52
- ItemAuthenticationWithPasswordSuccess: `${listKey}AuthenticationWithPasswordSuccess`,
53
- ItemAuthenticationWithPasswordFailure: `${listKey}AuthenticationWithPasswordFailure`,
54
- // Initial data
55
- CreateInitialInput: `CreateInitial${listKey}Input`,
56
- createInitialItem: `createInitial${listKey}`,
57
- };
58
47
 
59
48
  const customPath = !keystonePath || keystonePath === '/' ? '' : keystonePath;
60
49
  /**
@@ -71,9 +60,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
71
60
  async ({ context, isValidSession }) => {
72
61
  const { req, session } = context;
73
62
  const pathname = url.parse(req?.url!).pathname!;
74
- if (pathname === `${customPath}/api/__keystone_api_build`) {
75
- return;
76
- }
63
+
77
64
  if (isValidSession) {
78
65
  if (pathname === `${customPath}/api/auth/signin`) {
79
66
  return { kind: 'redirect', to: `${customPath}` };
@@ -83,8 +70,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
83
70
  }
84
71
  return;
85
72
  }
86
-
87
- 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
+ ) {
88
81
  return { kind: 'redirect', to: `${customPath}/api/auth/signin` };
89
82
  }
90
83
  };
@@ -103,14 +96,10 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
103
96
  mode: 'write',
104
97
  outputPath: 'pages/api/auth/[...nextauth].js',
105
98
  src: authTemplate({
106
- gqlNames,
99
+ autoCreate,
107
100
  identityField,
108
- sessionData,
109
101
  listKey,
110
- autoCreate,
111
- userMap,
112
- accountMap,
113
- profileMap,
102
+ sessionData,
114
103
  sessionSecret,
115
104
  }),
116
105
  },
@@ -129,14 +118,18 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
129
118
  * Must be added to the ui.publicPages config
130
119
  */
131
120
  const publicPages = [
121
+ `${customPath}/api/__keystone_api_build`,
132
122
  `${customPath}/api/auth/csrf`,
133
123
  `${customPath}/api/auth/signin`,
134
124
  `${customPath}/api/auth/callback`,
135
125
  `${customPath}/api/auth/session`,
136
126
  `${customPath}/api/auth/providers`,
137
127
  `${customPath}/api/auth/signout`,
128
+ `${customPath}/api/auth/error`,
138
129
  ];
139
- function addPages(provider) {
130
+ // TODO: Add Provider Types
131
+ // @ts-ignore
132
+ function addPages(provider: Provider) {
140
133
  const name = provider.id;
141
134
  publicPages.push(`${customPath}/api/auth/signin/${name}`);
142
135
  publicPages.push(`${customPath}/api/auth/callback/${name}`);
@@ -151,7 +144,6 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
151
144
  const extendGraphqlSchema = getSchemaExtension({
152
145
  identityField,
153
146
  listKey,
154
- gqlNames,
155
147
  });
156
148
 
157
149
  /**
@@ -166,13 +158,16 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
166
158
  throw new Error(msg);
167
159
  }
168
160
 
161
+ // TODO: Check if providers
162
+ // TODO: Check other required commands/data
163
+
169
164
  // TODO: Check for String-like typing for identityField? How?
170
165
  // TODO: Validate that the identifyField is unique.
171
166
  // TODO: If this field isn't required, what happens if I try to log in as `null`?
172
167
  const identityFieldConfig = listConfig.fields[identityField];
173
168
  if (identityFieldConfig === undefined) {
174
- const i = JSON.stringify(identityField);
175
- 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.`;
176
171
  throw new Error(msg);
177
172
  }
178
173
  };
@@ -188,15 +183,32 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
188
183
  const withItemData = (
189
184
  _sessionStrategy: SessionStrategy<Record<string, any>>
190
185
  ): SessionStrategy<NextAuthSession | undefined> => {
191
- const { get, ...sessionStrategy } = _sessionStrategy;
186
+ const { get, start, ...sessionStrategy } = _sessionStrategy;
192
187
  return {
193
188
  ...sessionStrategy,
189
+ start: async ({ res }) => {
190
+ console.log('start');
191
+
192
+ const session = await start({ res });
193
+ return session;
194
+ },
194
195
  get: async ({ req }) => {
195
196
  const pathname = url.parse(req?.url!).pathname!;
196
197
  if (pathname.includes('/api/auth')) {
197
198
  return;
198
199
  }
200
+ if (req.headers.authorization?.split(' ')[0] === 'Bearer') {
201
+ const token = (await getToken({
202
+ req,
203
+ secret: sessionSecret,
204
+ })) as NextAuthSession;
205
+
206
+ if (token?.data?.id) {
207
+ return token;
208
+ }
209
+ }
199
210
  const nextSession: unknown = await getSession({ req });
211
+
200
212
  if (nextSession) {
201
213
  return nextSession as NextAuthSession;
202
214
  }
@@ -215,7 +227,8 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
215
227
  secure: process.env.NODE_ENV === 'production',
216
228
  path: '/',
217
229
  sameSite: 'lax',
218
- domain: url.parse(req.url).hostname,
230
+ // TODO: Update parse to URL
231
+ domain: url.parse(req.url as string).hostname as string,
219
232
  })
220
233
  );
221
234
  },
@@ -232,7 +245,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
232
245
  * It validates the auth config against the provided keystone config, and preserves existing
233
246
  * config by composing existing extendGraphqlSchema functions and ui config.
234
247
  */
235
- const withAuth = (keystoneConfig: KeystoneConfig): KeystoneAuthConfig => {
248
+ const withAuth = (keystoneConfig: KeystoneConfig): KeystoneOAuthConfig => {
236
249
  validateConfig(keystoneConfig);
237
250
  let { ui } = keystoneConfig;
238
251
  if (keystoneConfig.ui) {
@@ -248,6 +261,15 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
248
261
  keystoneConfig?.ui?.pageMiddleware?.(args),
249
262
  enableSessionItem: true,
250
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
251
273
  if (
252
274
  process.env.NODE_ENV !== 'production' &&
253
275
  context.req?.url !== undefined &&
@@ -256,25 +278,10 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
256
278
  ) {
257
279
  return true;
258
280
  }
259
- // Allow access to the adminMeta data from the /init path to correctly render that page
260
- // even if the user isn't logged in (which should always be the case if they're seeing /init)
261
- const headers = context.req?.headers;
262
- const host = headers
263
- ? headers['x-forwarded-host'] || headers.host
264
- : null;
265
- const thisUrl = headers?.referer
266
- ? new URL(headers.referer)
267
- : undefined;
268
- const accessingInitPage =
269
- thisUrl?.pathname === '/init' &&
270
- thisUrl?.host === host &&
271
- (await context.sudo().query[listKey].count({})) === 0;
272
- return (
273
- accessingInitPage ||
274
- (keystoneConfig.ui?.isAccessAllowed
275
- ? keystoneConfig.ui.isAccessAllowed(context)
276
- : context.session !== undefined)
277
- );
281
+
282
+ return keystoneConfig.ui?.isAccessAllowed
283
+ ? keystoneConfig.ui.isAccessAllowed(context)
284
+ : context.session !== undefined;
278
285
  },
279
286
  };
280
287
  }
@@ -287,8 +294,11 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
287
294
  return {
288
295
  ...keystoneConfig,
289
296
  ui,
290
- session,
297
+ cookies,
291
298
  providers,
299
+ pages,
300
+ resolver,
301
+ session,
292
302
  lists: {
293
303
  ...keystoneConfig.lists,
294
304
  },
@@ -1,43 +1,74 @@
1
- import NextAuth from 'next-auth';
1
+ import NextAuth, {
2
+ CookiesOptions,
3
+ EventCallbacks,
4
+ PagesOptions,
5
+ } from 'next-auth';
2
6
  import type { KeystoneListsAPI } from '@keystone-6/core/types';
3
7
  import { Provider } from 'next-auth/providers';
8
+ import { JWTOptions } from 'next-auth/jwt';
4
9
  import { validateNextAuth } from '../lib/validateNextAuth';
5
10
 
6
- // Need to bring in correct props
7
- type NextAuthPageProps = {
11
+ // TODO: See if possible to merge with `type AuthConfig`
12
+ type CoreNextAuthPageProps = {
13
+ autoCreate: boolean;
14
+ cookies?: Partial<CookiesOptions>;
15
+ events?: Partial<EventCallbacks>;
8
16
  identityField: string;
9
- mutationName: string;
10
- providers: Provider[];
11
- query: KeystoneListsAPI<any>;
12
- sessionData: string;
17
+ jwt?: Partial<JWTOptions>;
13
18
  listKey: string;
14
- autoCreate: boolean;
15
- userMap: any;
16
- accountMap: any;
17
- profileMap: any;
19
+ pages?: Partial<PagesOptions>;
20
+ providers?: Provider[];
21
+ resolver?: Function | undefined;
22
+ sessionData: string | undefined;
18
23
  sessionSecret: string;
19
24
  };
20
25
 
26
+ type NextAuthGglProps = {
27
+ mutationName?: string;
28
+ query?: KeystoneListsAPI<any>;
29
+ };
30
+
31
+ export type NextAuthPageProps = CoreNextAuthPageProps & NextAuthGglProps;
32
+
21
33
  export default function NextAuthPage(props: NextAuthPageProps) {
22
34
  const {
35
+ autoCreate,
36
+ cookies,
37
+ events,
38
+ identityField,
39
+ jwt,
40
+ listKey,
41
+ pages,
23
42
  providers,
24
43
  query,
25
- identityField,
44
+ resolver,
26
45
  sessionData,
27
- listKey,
28
- autoCreate,
29
- userMap,
30
- accountMap,
31
- profileMap,
32
46
  sessionSecret,
33
47
  } = props;
48
+ // TODO: (v1.1). https://github.com/ijsto/keystone-6-oauth/projects/1#card-78602004
49
+ console.log('NextAuthPages... ', pages);
50
+
51
+ if (!query) {
52
+ console.error('NextAuthPage got no query.');
53
+ return null;
54
+ }
55
+
56
+ if (!providers || !providers.length) {
57
+ console.error('You need to provide at least one provider.');
58
+ return null;
59
+ }
60
+
34
61
  const list = query[listKey];
35
62
  const queryAPI = query[listKey];
36
63
  const protectIdentities = true;
37
64
 
38
65
  return NextAuth({
39
- secret: sessionSecret,
66
+ cookies,
40
67
  providers,
68
+ pages: pages || {},
69
+ events: events || {},
70
+ jwt: jwt || {},
71
+ secret: sessionSecret,
41
72
  callbacks: {
42
73
  async signIn({ user, account, profile }) {
43
74
  let identity;
@@ -48,31 +79,21 @@ export default function NextAuthPage(props: NextAuthPageProps) {
48
79
  } else {
49
80
  identity = 0;
50
81
  }
82
+ const userInput = resolver
83
+ ? await resolver({ user, account, profile })
84
+ : {};
85
+
51
86
  const result = await validateNextAuth(
52
87
  identityField,
53
88
  identity,
54
89
  protectIdentities,
55
90
  queryAPI
56
91
  );
57
- const data: any = {};
58
- // eslint-disable-next-line no-restricted-syntax
59
- for (const key in userMap) {
60
- if (Object.prototype.hasOwnProperty.call(userMap, key)) {
61
- data[key] = user[userMap[key]];
62
- }
63
- }
64
- // eslint-disable-next-line no-restricted-syntax
65
- for (const key in accountMap) {
66
- if (Object.prototype.hasOwnProperty.call(accountMap, key)) {
67
- data[key] = account[accountMap[key]];
68
- }
69
- }
70
- // eslint-disable-next-line no-restricted-syntax
71
- for (const key in profileMap) {
72
- if (Object.prototype.hasOwnProperty.call(profileMap, key)) {
73
- data[key] = profile[profileMap[key]];
74
- }
75
- }
92
+ // ID
93
+ const data: any = {
94
+ [identityField]: identity,
95
+ ...userInput,
96
+ };
76
97
 
77
98
  if (!result.success) {
78
99
  if (!autoCreate) {
package/src/schema.ts CHANGED
@@ -1,39 +1,17 @@
1
1
  import { ExtendGraphqlSchema } from '@keystone-6/core/types';
2
2
 
3
- import { assertInputObjectType, GraphQLString, GraphQLID } from 'graphql';
4
3
  import { graphql } from '@keystone-6/core';
5
- import { AuthGqlNames } from './types';
6
4
  import { getBaseAuthSchema } from './gql/getBaseAuthSchema';
7
5
 
8
6
  export const getSchemaExtension = ({
9
- identityField,
10
7
  listKey,
11
- gqlNames,
12
8
  }: {
13
9
  identityField: string;
14
10
  listKey: string;
15
- gqlNames: AuthGqlNames;
16
11
  }): ExtendGraphqlSchema =>
17
12
  graphql.extend((base) => {
18
- const uniqueWhereInputType = assertInputObjectType(
19
- base.schema.getType(`${listKey}WhereUniqueInput`)
20
- );
21
- const identityFieldOnUniqueWhere =
22
- uniqueWhereInputType.getFields()[identityField];
23
- if (
24
- identityFieldOnUniqueWhere?.type !== GraphQLString &&
25
- identityFieldOnUniqueWhere?.type !== GraphQLID
26
- ) {
27
- throw new Error(
28
- `createAuth was called with an identityField of ${identityField} on the list ${listKey} ` +
29
- `but that field doesn't allow being searched uniquely with a String or ID. ` +
30
- `You should likely add \`isIndexed: 'unique'\` ` +
31
- `to the field at ${listKey}.${identityField}`
32
- );
33
- }
34
13
  const baseSchema = getBaseAuthSchema({
35
14
  listKey,
36
- gqlNames,
37
15
  base,
38
16
  });
39
17
 
@@ -1,5 +1,5 @@
1
1
  import ejs from 'ejs';
2
- import { AuthGqlNames } from '../types';
2
+ import { NextAuthPageProps } from '../pages/NextAuthPage';
3
3
 
4
4
  const template = `
5
5
  import getNextAuthPage from '@opensaas/keystone-nextjs-auth/pages/NextAuthPage';
@@ -7,49 +7,32 @@ import { query } from '.keystone/api';
7
7
  import keystoneConfig from '../../../../../keystone';
8
8
 
9
9
  export default getNextAuthPage({
10
+ autoCreate: <%= autoCreate %>,
10
11
  identityField: '<%= identityField %>',
11
- sessionData: '<%= sessionData %>',
12
12
  listKey: '<%= listKey %>',
13
- userMap: <%- JSON.stringify(userMap) %>,
14
- accountMap: <%- JSON.stringify(accountMap) %>,
15
- profileMap: <%- JSON.stringify(profileMap) %>,
16
- autoCreate: <%= autoCreate %>,
17
- sessionSecret: '<%= sessionSecret %>',
13
+ pages: keystoneConfig.pages,
18
14
  providers: keystoneConfig.providers,
19
15
  query,
16
+ resolver: keystoneConfig.resolver,
17
+ sessionData: '<%= sessionData %>',
18
+ sessionSecret: '<%= sessionSecret %>',
20
19
  });
21
20
  `;
22
21
 
22
+ type AuthTemplateOptions = NextAuthPageProps;
23
+
23
24
  export const authTemplate = ({
24
- gqlNames,
25
+ autoCreate,
25
26
  identityField,
26
- sessionData,
27
27
  listKey,
28
- autoCreate,
29
- userMap,
30
- accountMap,
31
- profileMap,
28
+ sessionData,
32
29
  sessionSecret,
33
- }: {
34
- gqlNames: AuthGqlNames;
35
- identityField: string;
36
- sessionData: any;
37
- listKey: string;
38
- autoCreate: boolean;
39
- userMap: any;
40
- accountMap: any;
41
- profileMap: any;
42
- sessionSecret: string;
43
- }) => {
30
+ }: AuthTemplateOptions) => {
44
31
  const authOut = ejs.render(template, {
45
- gqlNames,
46
32
  identityField,
47
33
  sessionData,
48
34
  listKey,
49
35
  autoCreate,
50
- userMap,
51
- accountMap,
52
- profileMap,
53
36
  sessionSecret,
54
37
  });
55
38
  return authOut;
@@ -9,6 +9,9 @@ module.exports = withPreconstruct({
9
9
  typescript: {
10
10
  ignoreBuildErrors: true,
11
11
  },
12
+ env: {
13
+ NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:<%= process.env.PORT || 3000 %><%= keystonePath || '' %>/api/auth',
14
+ },
12
15
  eslint: {
13
16
  ignoreDuringBuilds: true,
14
17
  },
package/src/types.ts CHANGED
@@ -1,44 +1,43 @@
1
1
  import { BaseListTypeInfo, KeystoneConfig } from '@keystone-6/core/types';
2
+ import { CookiesOptions, PagesOptions } from 'next-auth';
2
3
  import { Provider } from 'next-auth/providers';
3
4
 
4
- export type AuthGqlNames = {
5
- CreateInitialInput: string;
6
- createInitialItem: string;
7
- authenticateItemWithPassword: string;
8
- ItemAuthenticationWithPasswordResult: string;
9
- ItemAuthenticationWithPasswordSuccess: string;
10
- ItemAuthenticationWithPasswordFailure: string;
11
- };
12
-
13
5
  export type NextAuthSession = { listKey: string; itemId: string; data: any };
14
6
 
15
7
  export type NextAuthProviders = Provider[];
16
8
 
17
- type KeytoneAuthProviders = {
9
+ type KeytoneOAuthOptions = {
18
10
  providers: NextAuthProviders;
11
+ pages?: Partial<PagesOptions>;
12
+ };
13
+ type NextAuthOptions = {
14
+ cookies?: Partial<CookiesOptions>;
15
+ resolver: any;
19
16
  };
20
17
 
21
- export type KeystoneAuthConfig = KeystoneConfig & KeytoneAuthProviders;
18
+ export type KeystoneOAuthConfig = KeystoneConfig &
19
+ KeytoneOAuthOptions &
20
+ NextAuthOptions;
22
21
 
23
22
  export type AuthConfig<GeneratedListTypes extends BaseListTypeInfo> = {
23
+ /** Auth Create users in Keystone DB from Auth Provider */
24
+ autoCreate: boolean;
25
+ /** Adds ability to customize cookie options, for example, to facilitate cross-subdomain functionality */
26
+ cookies?: Partial<CookiesOptions>;
24
27
  /** The key of the list to authenticate users with */
25
28
  listKey: GeneratedListTypes['key'];
26
29
  /** The path of the field the identity is stored in; must be text-ish */
27
30
  identityField: GeneratedListTypes['fields'];
28
- /** Session data population */
29
- sessionData?: string;
30
- /** Auth Create users in Keystone DB from Auth Provider */
31
- autoCreate: boolean;
32
- /** Map User in next-auth to item */
33
- userMap: any;
34
- /** Map Account in next-auth to item */
35
- accountMap: any;
36
- /** Map Profile in next-auth to item */
37
- profileMap: any;
38
31
  /** Path for Keystone interface */
39
32
  keystonePath?: string;
33
+ // Custom pages for different NextAuth events
34
+ pages?: any; // TODO: Fix types
40
35
  /** Providers for Next Auth */
41
36
  providers: NextAuthProviders;
37
+ /** Resolver for user to define their profile */
38
+ resolver?: Function | undefined;
39
+ /** Session data population */
40
+ sessionData?: string | undefined;
42
41
  /** Next-Auth Session Secret */
43
42
  sessionSecret: string;
44
43
  };
@@ -1,81 +0,0 @@
1
- import type {
2
- GraphQLSchemaExtension,
3
- KeystoneContext,
4
- } from '@keystone-6/core/types';
5
- import {
6
- assertInputObjectType,
7
- GraphQLInputObjectType,
8
- GraphQLSchema,
9
- printType,
10
- } from 'graphql';
11
-
12
- import { AuthGqlNames, InitFirstItemConfig } from '../types';
13
-
14
- export function getInitFirstItemSchema({
15
- listKey,
16
- fields,
17
- itemData,
18
- gqlNames,
19
- graphQLSchema,
20
- }: {
21
- listKey: string;
22
- fields: InitFirstItemConfig<any>['fields'];
23
- itemData: InitFirstItemConfig<any>['itemData'];
24
- gqlNames: AuthGqlNames;
25
- graphQLSchema: GraphQLSchema;
26
- }): GraphQLSchemaExtension {
27
- const createInputConfig = assertInputObjectType(
28
- graphQLSchema.getType(`${listKey}CreateInput`)
29
- ).toConfig();
30
- const fieldsSet = new Set<any>(fields);
31
- const initialCreateInput = printType(
32
- new GraphQLInputObjectType({
33
- ...createInputConfig,
34
- fields: Object.fromEntries(
35
- Object.entries(createInputConfig.fields).filter(([fieldKey]) =>
36
- fieldsSet.has(fieldKey)
37
- )
38
- ),
39
- name: gqlNames.CreateInitialInput,
40
- })
41
- );
42
- return {
43
- typeDefs: `
44
- ${initialCreateInput}
45
- type Mutation {
46
- ${gqlNames.createInitialItem}(data: ${gqlNames.CreateInitialInput}!): ${gqlNames.ItemAuthenticationWithPasswordSuccess}!
47
- }
48
- `,
49
- resolvers: {
50
- Mutation: {
51
- async [gqlNames.createInitialItem](
52
- root: any,
53
- { data }: { data: Record<string, any> },
54
- context: KeystoneContext
55
- ) {
56
- if (!context.startSession) {
57
- throw new Error('No session implementation available on context');
58
- }
59
-
60
- const dbItemAPI = context.sudo().db[listKey];
61
- const count = await dbItemAPI.count({});
62
- if (count !== 0) {
63
- throw new Error(
64
- 'Initial items can only be created when no items exist in that list'
65
- );
66
- }
67
-
68
- // Update system state
69
- const item = await dbItemAPI.createOne({
70
- data: { ...data, ...itemData },
71
- });
72
- const sessionToken = await context.startSession({
73
- listKey,
74
- itemId: item.id,
75
- });
76
- return { item, sessionToken };
77
- },
78
- },
79
- },
80
- };
81
- }