@opensaas/keystone-nextjs-auth 14.1.0 → 16.0.1

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.
@@ -9,22 +9,17 @@ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e };
9
9
 
10
10
  var NextAuth__default = /*#__PURE__*/_interopDefault(NextAuth);
11
11
 
12
- async function findMatchingIdentity(identityField, identity, itemAPI) {
13
- const items = await itemAPI.findMany({
12
+ async function findMatchingIdentity(identityField, identity, queryAPI) {
13
+ const item = await queryAPI.findOne({
14
14
  where: {
15
- [identityField]: {
16
- equals: identity
17
- }
18
- },
19
- resolveFields: false
15
+ [identityField]: identity
16
+ }
20
17
  }); // Identity failures with helpful errors
21
18
 
22
19
  let code;
23
20
 
24
- if (items.length === 0) {
21
+ if (!item) {
25
22
  code = 'IDENTITY_NOT_FOUND';
26
- } else if (items.length > 1) {
27
- code = 'MULTIPLE_IDENTITY_MATCHES';
28
23
  }
29
24
 
30
25
  if (code) {
@@ -36,7 +31,7 @@ async function findMatchingIdentity(identityField, identity, itemAPI) {
36
31
 
37
32
  return {
38
33
  success: true,
39
- item: items[0]
34
+ item
40
35
  };
41
36
  }
42
37
 
@@ -74,11 +69,18 @@ function NextAuthPage(props) {
74
69
  const list = query[listKey];
75
70
  const queryAPI = query[listKey];
76
71
  const protectIdentities = true;
77
- return NextAuth__default['default']({
72
+ return NextAuth__default["default"]({
78
73
  providers,
79
74
  callbacks: {
80
75
  async signIn(user, account, profile) {
81
- const identity = user.id;
76
+ let identity;
77
+
78
+ if (typeof user.id === 'string') {
79
+ identity = user.id;
80
+ } else if (typeof user.id === 'number') {
81
+ identity = user.id;
82
+ }
83
+
82
84
  const result = await validateNextAuth(list, identityField, identity, protectIdentities, queryAPI);
83
85
  const data = {}; // eslint-disable-next-line no-restricted-syntax
84
86
 
@@ -124,13 +126,11 @@ function NextAuthPage(props) {
124
126
  }
125
127
  },
126
128
 
127
- async redirect(url, baseUrl) {
129
+ async redirect(url) {
128
130
  return url;
129
131
  },
130
132
 
131
133
  async session(session, token) {
132
- console.log('session:', session);
133
-
134
134
  const returnSession = _objectSpread(_objectSpread({}, session), {}, {
135
135
  data: token.data,
136
136
  subject: token.sub,
@@ -141,8 +141,7 @@ function NextAuthPage(props) {
141
141
  return returnSession;
142
142
  },
143
143
 
144
- async jwt(token, user, account, profile, isNewUser) {
145
- console.log('account:', account);
144
+ async jwt(token) {
146
145
  const identity = token.sub;
147
146
 
148
147
  if (!token.itemId) {
@@ -177,5 +176,5 @@ function NextAuthPage(props) {
177
176
  }
178
177
  const getNextAuthPage = props => () => NextAuthPage(_objectSpread({}, props));
179
178
 
180
- exports['default'] = NextAuthPage;
179
+ exports["default"] = NextAuthPage;
181
180
  exports.getNextAuthPage = getNextAuthPage;
@@ -1,22 +1,17 @@
1
1
  import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';
2
2
  import NextAuth from 'next-auth';
3
3
 
4
- async function findMatchingIdentity(identityField, identity, itemAPI) {
5
- const items = await itemAPI.findMany({
4
+ async function findMatchingIdentity(identityField, identity, queryAPI) {
5
+ const item = await queryAPI.findOne({
6
6
  where: {
7
- [identityField]: {
8
- equals: identity
9
- }
10
- },
11
- resolveFields: false
7
+ [identityField]: identity
8
+ }
12
9
  }); // Identity failures with helpful errors
13
10
 
14
11
  let code;
15
12
 
16
- if (items.length === 0) {
13
+ if (!item) {
17
14
  code = 'IDENTITY_NOT_FOUND';
18
- } else if (items.length > 1) {
19
- code = 'MULTIPLE_IDENTITY_MATCHES';
20
15
  }
21
16
 
22
17
  if (code) {
@@ -28,7 +23,7 @@ async function findMatchingIdentity(identityField, identity, itemAPI) {
28
23
 
29
24
  return {
30
25
  success: true,
31
- item: items[0]
26
+ item
32
27
  };
33
28
  }
34
29
 
@@ -70,7 +65,14 @@ function NextAuthPage(props) {
70
65
  providers,
71
66
  callbacks: {
72
67
  async signIn(user, account, profile) {
73
- const identity = user.id;
68
+ let identity;
69
+
70
+ if (typeof user.id === 'string') {
71
+ identity = user.id;
72
+ } else if (typeof user.id === 'number') {
73
+ identity = user.id;
74
+ }
75
+
74
76
  const result = await validateNextAuth(list, identityField, identity, protectIdentities, queryAPI);
75
77
  const data = {}; // eslint-disable-next-line no-restricted-syntax
76
78
 
@@ -116,13 +118,11 @@ function NextAuthPage(props) {
116
118
  }
117
119
  },
118
120
 
119
- async redirect(url, baseUrl) {
121
+ async redirect(url) {
120
122
  return url;
121
123
  },
122
124
 
123
125
  async session(session, token) {
124
- console.log('session:', session);
125
-
126
126
  const returnSession = _objectSpread(_objectSpread({}, session), {}, {
127
127
  data: token.data,
128
128
  subject: token.sub,
@@ -133,8 +133,7 @@ function NextAuthPage(props) {
133
133
  return returnSession;
134
134
  },
135
135
 
136
- async jwt(token, user, account, profile, isNewUser) {
137
- console.log('account:', account);
136
+ async jwt(token) {
138
137
  const identity = token.sub;
139
138
 
140
139
  if (!token.itemId) {
@@ -1,57 +1,38 @@
1
- import type {
2
- GraphQLSchemaExtension,
3
- KeystoneContext,
4
- } from '@keystone-next/keystone/types';
1
+ import type { ItemRootValue } from '@keystone-next/keystone/types';
2
+ import { graphql } from '@keystone-next/keystone';
5
3
 
6
4
  import { AuthGqlNames } from '../types';
7
5
 
8
- export function getBaseAuthSchema<I extends string, S extends string>({
6
+ export function getBaseAuthSchema({
9
7
  listKey,
10
8
  gqlNames,
9
+ base,
11
10
  }: {
12
11
  listKey: string;
13
12
  gqlNames: AuthGqlNames;
14
- }): GraphQLSchemaExtension {
15
- return {
16
- typeDefs: `
17
- # Auth
18
- union AuthenticatedItem = ${listKey}
19
- type Query {
20
- authenticatedItem: AuthenticatedItem
21
- }
22
- `,
23
- resolvers: {
24
- Query: {
25
- async authenticatedItem(root, args, { session, lists }) {
13
+ base: graphql.BaseSchemaMeta;
14
+ }) {
15
+ const extension = {
16
+ query: {
17
+ authenticatedItem: graphql.field({
18
+ type: graphql.union({
19
+ name: 'AuthenticatedItem',
20
+ types: [base.object(listKey) as graphql.ObjectType<ItemRootValue>],
21
+ resolveType: (root, context) => context.session?.listKey,
22
+ }),
23
+ resolve(root, args, { session, db }) {
26
24
  if (
27
25
  typeof session?.itemId === 'string' &&
28
26
  typeof session.listKey === 'string'
29
27
  ) {
30
- try {
31
- return lists[session.listKey].findOne({
32
- where: { id: session.itemId },
33
- resolveFields: false,
34
- });
35
- } catch (e) {
36
- return null;
37
- }
28
+ return db[session.listKey].findOne({
29
+ where: { id: session.itemId },
30
+ });
38
31
  }
39
32
  return null;
40
33
  },
41
- },
42
- AuthenticatedItem: {
43
- __resolveType(rootVal: any, { session }: KeystoneContext) {
44
- return session?.listKey;
45
- },
46
- },
47
- // TODO: Is this the preferred approach for this?
48
- [gqlNames.ItemAuthenticationWithPasswordResult]: {
49
- __resolveType(rootVal: any) {
50
- return rootVal.sessionToken
51
- ? gqlNames.ItemAuthenticationWithPasswordSuccess
52
- : gqlNames.ItemAuthenticationWithPasswordFailure;
53
- },
54
- },
34
+ }),
55
35
  },
56
36
  };
37
+ return { extension };
57
38
  }
@@ -1,4 +1,7 @@
1
- import type { GraphQLSchemaExtension } from '@keystone-next/keystone/types';
1
+ import type {
2
+ GraphQLSchemaExtension,
3
+ KeystoneContext,
4
+ } from '@keystone-next/keystone/types';
2
5
  import {
3
6
  assertInputObjectType,
4
7
  GraphQLInputObjectType,
@@ -24,7 +27,7 @@ export function getInitFirstItemSchema({
24
27
  const createInputConfig = assertInputObjectType(
25
28
  graphQLSchema.getType(`${listKey}CreateInput`)
26
29
  ).toConfig();
27
- const fieldsSet = new Set(fields);
30
+ const fieldsSet = new Set<any>(fields);
28
31
  const initialCreateInput = printType(
29
32
  new GraphQLInputObjectType({
30
33
  ...createInputConfig,
@@ -48,13 +51,13 @@ export function getInitFirstItemSchema({
48
51
  async [gqlNames.createInitialItem](
49
52
  root: any,
50
53
  { data }: { data: Record<string, any> },
51
- context
54
+ context: KeystoneContext
52
55
  ) {
53
56
  if (!context.startSession) {
54
57
  throw new Error('No session implementation available on context');
55
58
  }
56
59
 
57
- const dbItemAPI = context.sudo().db.lists[listKey];
60
+ const dbItemAPI = context.sudo().db[listKey];
58
61
  const count = await dbItemAPI.count({});
59
62
  if (count !== 0) {
60
63
  throw new Error(
package/src/index.ts CHANGED
@@ -236,7 +236,7 @@ export function createAuth<GeneratedListTypes extends BaseGeneratedListTypes>({
236
236
  ...keystoneConfig.ui,
237
237
  publicPages: [...(keystoneConfig.ui.publicPages || []), ...publicPages],
238
238
  getAdditionalFiles: [
239
- ...(keystoneConfig.ui.getAdditionalFiles || []),
239
+ ...(keystoneConfig.ui?.getAdditionalFiles || []),
240
240
  getAdditionalFiles,
241
241
  ],
242
242
  pageMiddleware: async (args) =>
@@ -266,10 +266,11 @@ export function createAuth<GeneratedListTypes extends BaseGeneratedListTypes>({
266
266
  },
267
267
  };
268
268
  }
269
- let { session } = keystoneConfig;
270
- if (session && sessionData) {
271
- session = withItemData(session);
272
- }
269
+
270
+ if (!keystoneConfig.session)
271
+ throw new TypeError('Missing .session configuration');
272
+ const session = withItemData(keystoneConfig.session);
273
+
273
274
  const existingExtendGraphQLSchema = keystoneConfig.extendGraphqlSchema;
274
275
  return {
275
276
  ...keystoneConfig,
@@ -1,29 +1,23 @@
1
- import type { KeystoneListsAPI } from '@keystone-next/keystone/types';
2
-
3
1
  import { AuthTokenRequestErrorCode } from '../types';
4
2
 
5
3
  export async function findMatchingIdentity(
6
4
  identityField: string,
7
- identity: string,
8
- itemAPI: KeystoneListsAPI<any>[string]
5
+ identity: string | number,
6
+ queryAPI: any
9
7
  ): Promise<
10
8
  | { success: false; code: AuthTokenRequestErrorCode }
11
9
  | { success: true; item: any }
12
10
  > {
13
- const items = await itemAPI.findMany({
14
- where: { [identityField]: { equals: identity } },
15
- resolveFields: false,
11
+ const item = await queryAPI.findOne({
12
+ where: { [identityField]: identity },
16
13
  });
17
-
18
14
  // Identity failures with helpful errors
19
15
  let code: AuthTokenRequestErrorCode | undefined;
20
- if (items.length === 0) {
16
+ if (!item) {
21
17
  code = 'IDENTITY_NOT_FOUND';
22
- } else if (items.length > 1) {
23
- code = 'MULTIPLE_IDENTITY_MATCHES';
24
18
  }
25
19
  if (code) {
26
20
  return { success: false, code };
27
21
  }
28
- return { success: true, item: items[0] };
22
+ return { success: true, item };
29
23
  }
@@ -5,18 +5,18 @@ import { findMatchingIdentity } from './findMatchingIdentity';
5
5
  export async function validateNextAuth(
6
6
  list: any,
7
7
  identityField: string,
8
- identity: string,
8
+ identity: string | number,
9
9
  protectIdentities: boolean,
10
10
  itemAPI: KeystoneListsAPI<any>[string]
11
11
  ): Promise<
12
12
  | { success: false; code: NextAuthErrorCode }
13
- | { success: true; item: { id: any; [prop: string]: any } }
13
+ | { success: true; item: { id: any;[prop: string]: any } }
14
14
  > {
15
15
  const match = await findMatchingIdentity(identityField, identity, itemAPI);
16
16
 
17
17
  const { item } = match as {
18
18
  success: true;
19
- item: { id: any; [prop: string]: any };
19
+ item: { id: any;[prop: string]: any };
20
20
  };
21
21
 
22
22
  if (item) {
@@ -37,7 +37,12 @@ export default function NextAuthPage(props: NextAuthPageProps) {
37
37
  providers,
38
38
  callbacks: {
39
39
  async signIn(user, account, profile) {
40
- const identity = user.id as string;
40
+ let identity;
41
+ if (typeof user.id === 'string') {
42
+ identity = user.id;
43
+ } else if (typeof user.id === 'number') {
44
+ identity = user.id;
45
+ }
41
46
  const result = await validateNextAuth(
42
47
  list,
43
48
  identityField,
@@ -89,11 +94,10 @@ export default function NextAuthPage(props: NextAuthPageProps) {
89
94
  return result.success;
90
95
  }
91
96
  },
92
- async redirect(url, baseUrl) {
97
+ async redirect(url) {
93
98
  return url;
94
99
  },
95
100
  async session(session: any, token: any) {
96
- console.log('session:', session);
97
101
  const returnSession = {
98
102
  ...session,
99
103
  data: token.data,
@@ -103,9 +107,7 @@ export default function NextAuthPage(props: NextAuthPageProps) {
103
107
  };
104
108
  return returnSession;
105
109
  },
106
- async jwt(token, user, account, profile, isNewUser) {
107
- console.log('account:', account);
108
-
110
+ async jwt(token) {
109
111
  const identity = token.sub;
110
112
  if (!token.itemId) {
111
113
  const result = await validateNextAuth(
package/src/schema.ts CHANGED
@@ -1,28 +1,43 @@
1
- import { mergeSchemas } from '@graphql-tools/merge';
2
1
  import { ExtendGraphqlSchema } from '@keystone-next/keystone/types';
3
2
 
3
+ import { assertInputObjectType, GraphQLString, GraphQLID } from 'graphql';
4
+ import { graphql } from '@keystone-next/keystone';
4
5
  import { AuthGqlNames } from './types';
5
6
  import { getBaseAuthSchema } from './gql/getBaseAuthSchema';
6
7
 
7
- export const getSchemaExtension =
8
- ({
9
- identityField,
10
- listKey,
11
- gqlNames,
12
- }: {
13
- identityField: string;
14
- listKey: string;
15
- gqlNames: AuthGqlNames;
16
- }): ExtendGraphqlSchema =>
17
- (schema) =>
18
- [
19
- getBaseAuthSchema({
20
- listKey,
21
- gqlNames,
22
- }),
23
- ]
24
- .filter((x) => x)
25
- .reduce(
26
- (s, extension) => mergeSchemas({ schemas: [s], ...extension }),
27
- schema
8
+ export const getSchemaExtension = ({
9
+ identityField,
10
+ listKey,
11
+ gqlNames,
12
+ }: {
13
+ identityField: string;
14
+ listKey: string;
15
+ gqlNames: AuthGqlNames;
16
+ }): ExtendGraphqlSchema =>
17
+ 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}`
28
32
  );
33
+ }
34
+ const baseSchema = getBaseAuthSchema({
35
+ listKey,
36
+ gqlNames,
37
+ base,
38
+ });
39
+
40
+ return [baseSchema.extension].filter(
41
+ (x): x is Exclude<typeof x, undefined> => x !== undefined
42
+ );
43
+ });
@@ -3,13 +3,11 @@ import { AuthGqlNames } from '../types';
3
3
 
4
4
  const template = `
5
5
  import getNextAuthPage from '@opensaas/keystone-nextjs-auth/pages/NextAuthPage';
6
- import { nextAuthProviders as Providers } from '@opensaas/keystone-nextjs-auth';
7
6
  import { query } from '.keystone/api';
8
7
  import keystoneConfig from '../../../../../keystone';
9
8
 
10
9
  export default getNextAuthPage({
11
10
  identityField: '<%= identityField %>',
12
- mutationName: '<%= gqlNames.authenticateItemWithPassword %>',
13
11
  sessionData: '<%= sessionData %>',
14
12
  listKey: '<%= listKey %>',
15
13
  userMap: <%- JSON.stringify(userMap) %>,
@@ -27,10 +27,30 @@ module.exports = withPreconstruct({
27
27
  /@keystone-next\\/keystone(?!\\/___internal-do-not-use-will-break-in-patch\\/admin-ui\\/id-field-view|\\/fields\\/types\\/[^\\/]+\\/views)/,
28
28
  /.prisma\\/client/
29
29
  ];
30
+ // we need to set these to true so that when __dirname/__filename is used
31
+ // to resolve the location of field views, we will get a path that we can use
32
+ // rather than just the __dirname/__filename of the generated file.
33
+ // https://webpack.js.org/configuration/node/#node__filename
34
+ (_config$node = config.node) !== null && _config$node !== void 0 ? _config$node : config.node = {};
35
+ config.node.__dirname = true;
36
+ config.node.__filename = true;
30
37
  }
31
38
  return config;
32
39
  },
40
+ <% if (keystonePath) { %>
41
+ <% if (process.env.NODE_ENV != 'production') { %>
42
+ async rewrites() {
43
+ return [
44
+ {
45
+ source: '/api/__keystone_api_build',
46
+ destination: 'http://localhost:3000<%= keystonePath || '' %>/api/__keystone_api_build',
47
+ basePath: false
48
+ }
49
+ ];
50
+ },
51
+ <% }%>
33
52
  basePath: '<%= keystonePath || '' %>'
53
+ <% } %>
34
54
  });
35
55
  `;
36
56
  export const nextConfigTemplate = ({
package/src/types.ts CHANGED
@@ -44,17 +44,6 @@ export type AuthConfig<GeneratedListTypes extends BaseGeneratedListTypes> = {
44
44
  providers: NextAuthProviders;
45
45
  };
46
46
 
47
- export type InitFirstItemConfig<
48
- GeneratedListTypes extends BaseGeneratedListTypes
49
- > = {
50
- /** Array of fields to collect, e.g ['name', 'email', 'password'] */
51
- fields: GeneratedListTypes['fields'][];
52
- /** Suppresses the second screen where we ask people to subscribe and follow Keystone */
53
- skipKeystoneWelcome?: boolean;
54
- /** Extra input to add for the create mutation */
55
- itemData?: Partial<GeneratedListTypes['inputs']['create']>;
56
- };
57
-
58
47
  export type AuthTokenRequestErrorCode =
59
48
  | 'IDENTITY_NOT_FOUND'
60
49
  | 'MULTIPLE_IDENTITY_MATCHES';