@opensaas/keystone-nextjs-auth 25.0.0 → 26.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +17 -0
- package/README.md +15 -13
- package/dist/declarations/src/pages/NextAuthPage.d.ts +3 -3
- package/dist/declarations/src/types/index.d.ts +15 -23
- package/dist/opensaas-keystone-nextjs-auth.cjs.dev.js +57 -111
- package/dist/opensaas-keystone-nextjs-auth.cjs.prod.js +58 -106
- package/dist/opensaas-keystone-nextjs-auth.esm.js +56 -110
- package/package.json +4 -4
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.dev.js +7 -4
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.cjs.prod.js +7 -4
- package/pages/NextAuthPage/dist/opensaas-keystone-nextjs-auth-pages-NextAuthPage.esm.js +7 -4
- package/src/index.ts +47 -54
- package/src/pages/NextAuthPage.tsx +8 -5
- package/src/templates/next-config.ts +5 -54
- package/src/types/index.ts +4 -19
- package/src/types/next-auth.d.ts +13 -6
@@ -160,9 +160,7 @@ function NextAuthPage(props) {
|
|
160
160
|
let returnSession = session;
|
161
161
|
|
162
162
|
if (!token.itemId) {
|
163
|
-
return
|
164
|
-
expires: '0'
|
165
|
-
};
|
163
|
+
return session;
|
166
164
|
} else {
|
167
165
|
returnSession = _objectSpread(_objectSpread({}, session), {}, {
|
168
166
|
data: token.data,
|
@@ -180,10 +178,15 @@ function NextAuthPage(props) {
|
|
180
178
|
token
|
181
179
|
} = _ref4;
|
182
180
|
const identity = token.sub;
|
181
|
+
|
182
|
+
if (!identity) {
|
183
|
+
return token;
|
184
|
+
}
|
185
|
+
|
183
186
|
const result = await validateNextAuth(identityField, identity, protectIdentities, list);
|
184
187
|
|
185
188
|
if (!result.success) {
|
186
|
-
token.itemId =
|
189
|
+
token.itemId = undefined;
|
187
190
|
} else {
|
188
191
|
token.itemId = result.item.id;
|
189
192
|
const data = await query[listKey].findOne({
|
package/src/index.ts
CHANGED
@@ -3,18 +3,20 @@ import {
|
|
3
3
|
AdminFileToWrite,
|
4
4
|
BaseListTypeInfo,
|
5
5
|
KeystoneConfig,
|
6
|
-
KeystoneContext,
|
7
6
|
AdminUIConfig,
|
8
7
|
BaseKeystoneTypeInfo,
|
9
8
|
SessionStrategy,
|
9
|
+
KeystoneContext,
|
10
10
|
} from '@keystone-6/core/types';
|
11
|
+
import type { NextApiRequest } from 'next';
|
12
|
+
|
11
13
|
import { getSession } from 'next-auth/react';
|
12
14
|
import { getToken } from 'next-auth/jwt';
|
13
15
|
import { Provider } from 'next-auth/providers';
|
14
16
|
|
15
17
|
import * as cookie from 'cookie';
|
16
|
-
|
17
|
-
import {
|
18
|
+
import { JWT } from 'next-auth/jwt';
|
19
|
+
import { Session } from 'next-auth';
|
18
20
|
import { nextConfigTemplate } from './templates/next-config';
|
19
21
|
// import * as Path from 'path';
|
20
22
|
|
@@ -57,7 +59,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
57
59
|
* - to the init page when initFirstItem is configured, and there are no user in the database
|
58
60
|
* - to the signin page when no valid session is present
|
59
61
|
*/
|
60
|
-
const
|
62
|
+
const authMiddleware: AdminUIConfig<BaseKeystoneTypeInfo>['pageMiddleware'] = async ({
|
61
63
|
context,
|
62
64
|
isValidSession,
|
63
65
|
}) => {
|
@@ -65,26 +67,11 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
65
67
|
const pathname = url.parse(req?.url!).pathname!;
|
66
68
|
|
67
69
|
if (isValidSession) {
|
68
|
-
if (
|
69
|
-
pathname === `${customPath}/api/auth/signin` ||
|
70
|
-
(pages?.signIn && pathname.includes(pages?.signIn))
|
71
|
-
) {
|
72
|
-
return { kind: 'redirect', to: `${customPath}` };
|
73
|
-
}
|
74
70
|
if (customPath !== '' && pathname === '/') {
|
75
71
|
return { kind: 'redirect', to: `${customPath}` };
|
76
72
|
}
|
77
73
|
return;
|
78
74
|
}
|
79
|
-
if (
|
80
|
-
pathname.includes('/_next/') ||
|
81
|
-
pathname.includes('/api/auth/') ||
|
82
|
-
(pages?.signIn && pathname.includes(pages?.signIn)) ||
|
83
|
-
(pages?.error && pathname.includes(pages?.error)) ||
|
84
|
-
(pages?.signOut && pathname.includes(pages?.signOut))
|
85
|
-
) {
|
86
|
-
return;
|
87
|
-
}
|
88
75
|
if (!session && !pathname.includes(`${customPath}/api/auth/`)) {
|
89
76
|
return {
|
90
77
|
kind: 'redirect',
|
@@ -94,14 +81,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
94
81
|
};
|
95
82
|
|
96
83
|
/**
|
97
|
-
*
|
84
|
+
* authGetAdditionalFiles
|
98
85
|
*
|
99
86
|
* This function adds files to be generated into the Admin UI build. Must be added to the
|
100
87
|
* ui.getAdditionalFiles config.
|
101
88
|
*
|
102
89
|
* The signin page is always included, and the init page is included when initFirstItem is set
|
103
90
|
*/
|
104
|
-
const
|
91
|
+
const authGetAdditionalFiles = () => {
|
105
92
|
const filesToWrite: AdminFileToWrite[] = [
|
106
93
|
{
|
107
94
|
mode: 'write',
|
@@ -128,8 +115,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
128
115
|
*
|
129
116
|
* Must be added to the ui.publicPages config
|
130
117
|
*/
|
131
|
-
const
|
132
|
-
`${customPath}/api/__keystone_api_build`,
|
118
|
+
const authPublicPages = [
|
133
119
|
`${customPath}/api/auth/csrf`,
|
134
120
|
`${customPath}/api/auth/signin`,
|
135
121
|
`${customPath}/api/auth/callback`,
|
@@ -142,8 +128,8 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
142
128
|
// @ts-ignore
|
143
129
|
function addPages(provider: Provider) {
|
144
130
|
const name = provider.id;
|
145
|
-
|
146
|
-
|
131
|
+
authPublicPages.push(`${customPath}/api/auth/signin/${name}`);
|
132
|
+
authPublicPages.push(`${customPath}/api/auth/callback/${name}`);
|
147
133
|
}
|
148
134
|
providers.map(addPages);
|
149
135
|
|
@@ -197,17 +183,19 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
197
183
|
const { get, end, ...sessionStrategy } = _sessionStrategy;
|
198
184
|
return {
|
199
185
|
...sessionStrategy,
|
200
|
-
get: async ({
|
186
|
+
get: async ({ context }) => {
|
187
|
+
const { req } = context;
|
201
188
|
const pathname = url.parse(req?.url!).pathname!;
|
202
189
|
let nextSession: Session | JWT | null;
|
190
|
+
if (!req) return;
|
203
191
|
if (pathname.includes('/api/auth')) {
|
204
192
|
return;
|
205
193
|
}
|
206
|
-
const sudoContext =
|
194
|
+
const sudoContext = context.sudo();
|
207
195
|
|
208
196
|
if (req.headers?.authorization?.split(' ')[0] === 'Bearer') {
|
209
197
|
nextSession = await getToken({
|
210
|
-
req,
|
198
|
+
req: req as NextApiRequest,
|
211
199
|
secret: sessionSecret,
|
212
200
|
});
|
213
201
|
} else {
|
@@ -231,7 +219,7 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
231
219
|
data: nextSession.data,
|
232
220
|
};
|
233
221
|
|
234
|
-
const userSession = await get({
|
222
|
+
const userSession = await get({ context });
|
235
223
|
|
236
224
|
return {
|
237
225
|
...userSession,
|
@@ -241,12 +229,14 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
241
229
|
itemId: nextSession.itemId,
|
242
230
|
};
|
243
231
|
},
|
244
|
-
end: async ({
|
245
|
-
await end({
|
232
|
+
end: async ({ context }) => {
|
233
|
+
await end({ context });
|
246
234
|
const TOKEN_NAME =
|
247
235
|
process.env.NODE_ENV === 'production'
|
248
236
|
? '__Secure-next-auth.session-token'
|
249
237
|
: 'next-auth.session-token';
|
238
|
+
const { req, res } = context;
|
239
|
+
if (!req || !res) return;
|
250
240
|
res.setHeader(
|
251
241
|
'Set-Cookie',
|
252
242
|
cookie.serialize(TOKEN_NAME, '', {
|
@@ -264,6 +254,9 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
264
254
|
};
|
265
255
|
};
|
266
256
|
|
257
|
+
function defaultIsAccessAllowed({ session }: KeystoneContext) {
|
258
|
+
return session !== undefined;
|
259
|
+
}
|
267
260
|
/**
|
268
261
|
* withAuth
|
269
262
|
*
|
@@ -277,35 +270,35 @@ export function createAuth<GeneratedListTypes extends BaseListTypeInfo>({
|
|
277
270
|
const withAuth = (keystoneConfig: KeystoneConfig): KeystoneOAuthConfig => {
|
278
271
|
validateConfig(keystoneConfig);
|
279
272
|
let { ui } = keystoneConfig;
|
280
|
-
if (
|
273
|
+
if (!ui?.isDisabled) {
|
274
|
+
const {
|
275
|
+
getAdditionalFiles = [],
|
276
|
+
isAccessAllowed = defaultIsAccessAllowed,
|
277
|
+
pageMiddleware,
|
278
|
+
publicPages = [],
|
279
|
+
} = ui || {};
|
281
280
|
ui = {
|
282
|
-
...
|
283
|
-
publicPages: [...
|
284
|
-
getAdditionalFiles: [...(keystoneConfig.ui?.getAdditionalFiles || []), getAdditionalFiles],
|
285
|
-
pageMiddleware: async args =>
|
286
|
-
(await pageMiddleware(args)) ?? keystoneConfig?.ui?.pageMiddleware?.(args),
|
281
|
+
...ui,
|
282
|
+
publicPages: [...publicPages, ...authPublicPages],
|
287
283
|
isAccessAllowed: async (context: KeystoneContext) => {
|
288
|
-
const
|
289
|
-
const pathname = url.parse(req?.url!).pathname!;
|
290
|
-
|
291
|
-
// Allow nextjs scripts and static files to be accessed without auth
|
292
|
-
if (pathname.includes('/_next/')) {
|
293
|
-
return true;
|
294
|
-
}
|
295
|
-
|
296
|
-
// Allow keystone to access /api/__keystone_api_build for hot reloading
|
284
|
+
const pathname = url.parse(context.req?.url!).pathname!;
|
297
285
|
if (
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
286
|
+
pathname.startsWith(`${customPath}/_next`) ||
|
287
|
+
pathname.startsWith(`${customPath}/__next`) ||
|
288
|
+
pathname.startsWith(`${customPath}/api/auth/`) ||
|
289
|
+
(pages?.signIn && pathname.includes(pages?.signIn)) ||
|
290
|
+
(pages?.error && pathname.includes(pages?.error)) ||
|
291
|
+
(pages?.signOut && pathname.includes(pages?.signOut))
|
302
292
|
) {
|
303
293
|
return true;
|
304
294
|
}
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
295
|
+
return await isAccessAllowed(context);
|
296
|
+
},
|
297
|
+
getAdditionalFiles: [...getAdditionalFiles, authGetAdditionalFiles],
|
298
|
+
pageMiddleware: async args => {
|
299
|
+
const shouldRedirect = await authMiddleware(args);
|
300
|
+
if (shouldRedirect) return shouldRedirect;
|
301
|
+
return pageMiddleware?.(args);
|
309
302
|
},
|
310
303
|
};
|
311
304
|
}
|
@@ -116,25 +116,28 @@ export default function NextAuthPage(props: NextAuthPageProps) {
|
|
116
116
|
async session({ session, token }) {
|
117
117
|
let returnSession = session;
|
118
118
|
if (!token.itemId) {
|
119
|
-
return
|
119
|
+
return session;
|
120
120
|
} else {
|
121
121
|
returnSession = {
|
122
122
|
...session,
|
123
123
|
data: token.data,
|
124
124
|
subject: token.sub,
|
125
|
-
listKey: token.listKey
|
126
|
-
itemId: token.itemId
|
125
|
+
listKey: token.listKey,
|
126
|
+
itemId: token.itemId,
|
127
127
|
};
|
128
128
|
}
|
129
129
|
|
130
130
|
return returnSession;
|
131
131
|
},
|
132
132
|
async jwt({ token }) {
|
133
|
-
const identity = token.sub
|
133
|
+
const identity = token.sub;
|
134
|
+
if (!identity) {
|
135
|
+
return token;
|
136
|
+
}
|
134
137
|
const result = await validateNextAuth(identityField, identity, protectIdentities, list);
|
135
138
|
|
136
139
|
if (!result.success) {
|
137
|
-
token.itemId =
|
140
|
+
token.itemId = undefined;
|
138
141
|
} else {
|
139
142
|
token.itemId = result.item.id;
|
140
143
|
const data = await query[listKey].findOne({
|
@@ -1,61 +1,12 @@
|
|
1
1
|
import ejs from 'ejs';
|
2
2
|
|
3
3
|
const template = `
|
4
|
-
const
|
5
|
-
// @ts-ignore
|
6
|
-
const withPreconstruct = require('@preconstruct/next');
|
4
|
+
const keystoneConfig = require('@keystone-6/core/___internal-do-not-use-will-break-in-patch/admin-ui/next-config').config;
|
7
5
|
|
8
|
-
module.exports =
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
env: {
|
13
|
-
NEXTAUTH_URL: process.env.NEXTAUTH_URL || 'http://localhost:<%= process.env.PORT || 3000 %><%= keystonePath || '' %>/api/auth',
|
14
|
-
},
|
15
|
-
eslint: {
|
16
|
-
ignoreDuringBuilds: true,
|
17
|
-
},
|
18
|
-
webpack(config, { isServer }) {
|
19
|
-
config.resolve.alias = {
|
20
|
-
...config.resolve.alias,
|
21
|
-
react: Path.dirname(require.resolve('react/package.json')),
|
22
|
-
'react-dom': Path.dirname(require.resolve('react-dom/package.json')),
|
23
|
-
'@keystone-6/core': Path.dirname(
|
24
|
-
require.resolve('@keystone-6/core/package.json')
|
25
|
-
),
|
26
|
-
};
|
27
|
-
if (isServer) {
|
28
|
-
config.externals = [
|
29
|
-
...config.externals,
|
30
|
-
/@keystone-6\\/core(?!\\/___internal-do-not-use-will-break-in-patch\\/admin-ui\\/id-field-view|\\/fields\\/types\\/[^\\/]+\\/views)/,
|
31
|
-
/.prisma\\/client/
|
32
|
-
];
|
33
|
-
// we need to set these to true so that when __dirname/__filename is used
|
34
|
-
// to resolve the location of field views, we will get a path that we can use
|
35
|
-
// rather than just the __dirname/__filename of the generated file.
|
36
|
-
// https://webpack.js.org/configuration/node/#node__filename
|
37
|
-
(_config$node = config.node) !== null && _config$node !== void 0 ? _config$node : config.node = {};
|
38
|
-
config.node.__dirname = true;
|
39
|
-
config.node.__filename = true;
|
40
|
-
}
|
41
|
-
return config;
|
42
|
-
},
|
43
|
-
<% if (keystonePath) { %>
|
44
|
-
<% if (process.env.NODE_ENV != 'production') { %>
|
45
|
-
async rewrites() {
|
46
|
-
return [
|
47
|
-
{
|
48
|
-
source: '/api/__keystone_api_build',
|
49
|
-
destination: 'http://localhost:<%= process.env.PORT || 3000 %><%= keystonePath || '' %>/api/__keystone_api_build',
|
50
|
-
basePath: false
|
51
|
-
}
|
52
|
-
];
|
53
|
-
},
|
54
|
-
<% }%>
|
55
|
-
basePath: '<%= keystonePath || '' %>'
|
56
|
-
<% } %>
|
57
|
-
});
|
58
|
-
`;
|
6
|
+
module.exports = {
|
7
|
+
...keystoneConfig,
|
8
|
+
basePath: '<%= keystonePath || '' %>'
|
9
|
+
};`;
|
59
10
|
export const nextConfigTemplate = ({ keystonePath }: { keystonePath: string }) => {
|
60
11
|
const nextConfigOut = ejs.render(template, { keystonePath });
|
61
12
|
|
package/src/types/index.ts
CHANGED
@@ -1,26 +1,11 @@
|
|
1
|
-
import type { ServerResponse, IncomingMessage } from 'http';
|
2
|
-
import type { NextRequest } from 'next/server';
|
3
1
|
import { Provider } from 'next-auth/providers';
|
4
2
|
import { CookiesOptions, PagesOptions } from 'next-auth';
|
5
|
-
import { BaseListTypeInfo, KeystoneConfig,
|
6
|
-
|
7
|
-
type NextAuthResponse = IncomingMessage & NextRequest;
|
3
|
+
import { BaseListTypeInfo, KeystoneConfig, KeystoneContext } from '@keystone-6/core/types';
|
8
4
|
|
9
5
|
export declare type AuthSessionStrategy<StoredSessionData> = {
|
10
|
-
start: (args: {
|
11
|
-
|
12
|
-
|
13
|
-
createContext: CreateContext;
|
14
|
-
}) => Promise<string>;
|
15
|
-
end: (args: {
|
16
|
-
req: IncomingMessage;
|
17
|
-
res: ServerResponse;
|
18
|
-
createContext: CreateContext;
|
19
|
-
}) => Promise<void>;
|
20
|
-
get: (args: {
|
21
|
-
req: NextAuthResponse;
|
22
|
-
createContext: CreateContext;
|
23
|
-
}) => Promise<StoredSessionData | undefined>;
|
6
|
+
start: (args: { data: any; context: KeystoneContext }) => Promise<unknown>;
|
7
|
+
end: (args: { context: KeystoneContext }) => Promise<unknown>;
|
8
|
+
get: (args: { context: KeystoneContext }) => Promise<StoredSessionData | undefined>;
|
24
9
|
};
|
25
10
|
|
26
11
|
export type NextAuthProviders = Provider[];
|
package/src/types/next-auth.d.ts
CHANGED
@@ -2,18 +2,25 @@ import NextAuth from 'next-auth';
|
|
2
2
|
import { JWT } from 'next-auth/jwt';
|
3
3
|
|
4
4
|
declare module 'next-auth' {
|
5
|
+
interface Session extends JWT {
|
6
|
+
user?: any;
|
7
|
+
listKey?: string;
|
8
|
+
itemId?: string;
|
9
|
+
subject?: string | number | null | undefined;
|
10
|
+
expires?: string;
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
declare module 'next-auth/jwt' {
|
5
15
|
interface JWT {
|
6
16
|
data?: any | undefined;
|
7
|
-
subject?: string | undefined;
|
17
|
+
subject?: string | number | null | undefined;
|
8
18
|
listKey?: string;
|
9
|
-
itemId?: string
|
19
|
+
itemId?: string;
|
10
20
|
name?: string | null | undefined;
|
11
21
|
email?: string | null | undefined;
|
12
22
|
picture?: string | null | undefined;
|
13
|
-
sub?: string | null | undefined;
|
23
|
+
sub?: string | number | null | undefined;
|
14
24
|
expires?: string | null | undefined;
|
15
25
|
}
|
16
|
-
interface Session extends JWT {
|
17
|
-
user?: any;
|
18
|
-
}
|
19
26
|
}
|