@opensaas/keystone-nextjs-auth 20.3.0 → 21.0.0

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