@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/CHANGELOG.md +19 -0
- package/README.md +5 -3
- package/dist/declarations/src/gql/getBaseAuthSchema.d.ts +1 -3
- package/dist/declarations/src/index.d.ts +4 -4
- package/dist/declarations/src/pages/NextAuthPage.d.ts +16 -9
- package/dist/declarations/src/schema.d.ts +1 -3
- package/dist/declarations/src/templates/auth.d.ts +4 -12
- package/dist/declarations/src/types.d.ts +17 -20
- package/dist/opensaas-keystone-nextjs-auth.cjs.dev.js +87 -74
- package/dist/opensaas-keystone-nextjs-auth.cjs.prod.js +85 -74
- package/dist/opensaas-keystone-nextjs-auth.esm.js +85 -74
- package/package.json +7 -7
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.dev.js +36 -29
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.prod.js +36 -29
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.esm.js +36 -29
- package/src/gql/getBaseAuthSchema.ts +0 -4
- package/src/index.ts +71 -61
- package/src/pages/NextAuthPage.tsx +58 -37
- package/src/schema.ts +0 -22
- package/src/templates/auth.ts +11 -28
- package/src/templates/next-config.ts +3 -0
- package/src/types.ts +20 -21
- package/src/gql/getInitFirstItemSchema.ts +0 -81
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,
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
99
|
+
autoCreate,
|
107
100
|
identityField,
|
108
|
-
sessionData,
|
109
101
|
listKey,
|
110
|
-
|
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
|
-
|
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
|
175
|
-
const msg = `A createAuth() invocation for the "${listKey}" list specifies ${
|
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
|
-
|
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):
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
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
|
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
|
-
//
|
7
|
-
type
|
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
|
-
|
10
|
-
providers: Provider[];
|
11
|
-
query: KeystoneListsAPI<any>;
|
12
|
-
sessionData: string;
|
17
|
+
jwt?: Partial<JWTOptions>;
|
13
18
|
listKey: string;
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
|
package/src/templates/auth.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import ejs from 'ejs';
|
2
|
-
import {
|
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
|
-
|
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
|
-
|
25
|
+
autoCreate,
|
25
26
|
identityField,
|
26
|
-
sessionData,
|
27
27
|
listKey,
|
28
|
-
|
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
|
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
|
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
|
-
}
|