@opensaas/keystone-nextjs-auth 25.0.0 → 26.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 +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
|
}
|