@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.
- package/CHANGELOG.md +7 -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 +66 -75
- package/dist/opensaas-keystone-nextjs-auth.cjs.prod.js +64 -75
- package/dist/opensaas-keystone-nextjs-auth.esm.js +64 -75
- package/package.json +1 -1
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.dev.js +37 -32
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.prod.js +37 -32
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.esm.js +37 -32
- package/src/gql/getBaseAuthSchema.ts +0 -4
- package/src/index.ts +61 -64
- package/src/pages/NextAuthPage.tsx +59 -38
- 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
@@ -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) {
|
@@ -125,7 +146,7 @@ export default function NextAuthPage(props: NextAuthPageProps) {
|
|
125
146
|
);
|
126
147
|
|
127
148
|
if (!result.success) {
|
128
|
-
return
|
149
|
+
return token;
|
129
150
|
}
|
130
151
|
token.itemId = result.item.id;
|
131
152
|
}
|
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
|
-
}
|