Skip to content

Commit 8267233

Browse files
authored
Merge pull request #334 from kinde-oss/feat/setup-queue
2 parents 9e971f4 + 4ba0909 commit 8267233

File tree

5 files changed

+216
-136
lines changed

5 files changed

+216
-136
lines changed

src/authMiddleware/authMiddleware.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { getSplitCookies } from "../utils/cookies/getSplitSerializedCookies";
1010
import { getIdToken } from "../utils/getIdToken";
1111
import { OAuth2CodeExchangeResponse } from "@kinde-oss/kinde-typescript-sdk";
1212
import { copyCookiesToRequest } from "../utils/copyCookiesToRequest";
13+
import { getStandardCookieOptions } from "../utils/cookies/getStandardCookieOptions";
1314

1415
const handleMiddleware = async (req, options, onSuccess) => {
1516
const { pathname } = req.nextUrl;
@@ -19,11 +20,13 @@ const handleMiddleware = async (req, options, onSuccess) => {
1920
const loginPage = options?.loginPage || `${config.apiPath}/${routes.login}`;
2021
const callbackPage = `${config.apiPath}/kinde_callback`;
2122
const registerPage = `${config.apiPath}/${routes.register}`;
23+
const setupPage = `${config.apiPath}/${routes.setup}`;
2224

2325
if (
2426
loginPage == pathname ||
2527
callbackPage == pathname ||
26-
registerPage == pathname
28+
registerPage == pathname ||
29+
setupPage == pathname
2730
) {
2831
return NextResponse.next();
2932
}
@@ -85,11 +88,36 @@ const handleMiddleware = async (req, options, onSuccess) => {
8588
console.log("authMiddleware: access token expired, refreshing");
8689
}
8790

91+
const sendResult = (debugMessage: string) => {
92+
if (config.isDebugMode) {
93+
console.error(debugMessage);
94+
}
95+
if (!isPublicPath) {
96+
return NextResponse.redirect(
97+
new URL(
98+
loginRedirectUrl,
99+
options?.redirectURLBase || config.redirectURL,
100+
),
101+
);
102+
}
103+
};
104+
88105
try {
89106
refreshResponse = await kindeClient.refreshTokens(session, false);
90107
kindeAccessToken = refreshResponse.access_token;
91108
kindeIdToken = refreshResponse.id_token;
109+
if (config.isDebugMode) {
110+
console.log(
111+
"authMiddleware: tokens refreshed",
112+
!!refreshResponse.access_token,
113+
!!refreshResponse.id_token,
114+
);
115+
}
116+
} catch (error) {
117+
return sendResult("authMiddleware: error refreshing tokens");
118+
}
92119

120+
try {
93121
// if we want layouts/pages to get immediate access to the new token,
94122
// we need to set the cookie on the response here
95123
const splitAccessTokenCookies = getSplitCookies(
@@ -108,7 +136,11 @@ const handleMiddleware = async (req, options, onSuccess) => {
108136
resp.cookies.set(cookie.name, cookie.value, cookie.options);
109137
});
110138

111-
resp.cookies.set("refresh_token", refreshResponse.refresh_token);
139+
resp.cookies.set(
140+
"refresh_token",
141+
refreshResponse.refresh_token,
142+
getStandardCookieOptions(),
143+
);
112144

113145
// copy the cookies from the response to the request
114146
// in Next versions prior to 14.2.8, the cookies function
@@ -117,24 +149,10 @@ const handleMiddleware = async (req, options, onSuccess) => {
117149
copyCookiesToRequest(req, resp);
118150

119151
if (config.isDebugMode) {
120-
console.log("authMiddleware: tokens refreshed");
152+
console.log("authMiddleware: tokens refreshed and cookies updated");
121153
}
122154
} catch (error) {
123-
// token is expired and refresh failed, redirect to login
124-
if (config.isDebugMode) {
125-
console.error(
126-
"authMiddleware: token refresh failed, redirecting to login",
127-
);
128-
}
129-
130-
if (!isPublicPath) {
131-
return NextResponse.redirect(
132-
new URL(
133-
loginRedirectUrl,
134-
options?.redirectURLBase || config.redirectURL,
135-
),
136-
);
137-
}
155+
sendResult("authMiddleware: error settings new token in cookie");
138156
}
139157
}
140158

src/handlers/setup.ts

Lines changed: 112 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,149 +2,155 @@ import { jwtDecoder } from "@kinde/jwt-decoder";
22
import { KindeAccessToken, KindeIdToken } from "../types";
33
import { config } from "../config/index";
44
import { generateUserObject } from "../utils/generateUserObject";
5-
import { validateToken } from "@kinde/jwt-validator";
6-
import { refreshTokens } from "../utils/refreshTokens";
75
import { getAccessToken } from "../utils/getAccessToken";
86
import RouterClient from "../routerClients/RouterClient";
97
import { getIdToken } from "../utils/getIdToken";
108
import { isTokenExpired } from "../utils/jwt/validation";
119
import { sessionManager } from "../session/sessionManager";
1210
import { kindeClient } from "../session/kindeServerClient";
11+
import { RequestQueueManager } from "../utils/workQueue";
1312

1413
/**
1514
*
1615
* @param {RouterClient} routerClient
1716
* @returns
1817
*/
1918
export const setup = async (routerClient: RouterClient) => {
20-
try {
21-
let accessTokenEncoded = await getAccessToken(routerClient.req);
22-
let idTokenEncoded = await getIdToken(routerClient.req);
19+
const queueManager = RequestQueueManager.getInstance();
2320

24-
if (!accessTokenEncoded || !idTokenEncoded) {
25-
if (config.isDebugMode) {
26-
console.log("setup: no access or id token - returning NOT_LOGGED_IN");
21+
return queueManager.enqueue(async () => {
22+
try {
23+
let accessTokenEncoded = await getAccessToken(routerClient.req);
24+
let idTokenEncoded = await getIdToken(routerClient.req);
25+
26+
if (!accessTokenEncoded || !idTokenEncoded) {
27+
if (config.isDebugMode) {
28+
console.log("setup: no access or id token - returning NOT_LOGGED_IN");
29+
}
30+
return routerClient.json(
31+
{
32+
message: "NOT_LOGGED_IN",
33+
},
34+
{ status: 200 },
35+
);
2736
}
28-
return routerClient.json(
29-
{
30-
message: "NOT_LOGGED_IN",
31-
},
32-
{ status: 200 },
33-
);
34-
}
3537

36-
const session = await sessionManager(routerClient.req);
38+
const session = await sessionManager(routerClient.req);
3739

38-
if (isTokenExpired(accessTokenEncoded) || isTokenExpired(idTokenEncoded)) {
39-
if (config.isDebugMode) {
40-
console.log("setup: access or id token expired - attempting refresh");
40+
if (
41+
isTokenExpired(accessTokenEncoded) ||
42+
isTokenExpired(idTokenEncoded)
43+
) {
44+
if (config.isDebugMode) {
45+
console.log("setup: access or id token expired - attempting refresh");
46+
}
47+
try {
48+
const refreshResponse = await kindeClient.refreshTokens(session);
49+
accessTokenEncoded = refreshResponse.access_token;
50+
idTokenEncoded = refreshResponse.id_token;
51+
} catch (error) {
52+
if (config.isDebugMode) {
53+
console.error("setup: refresh tokens failed - returning error");
54+
}
55+
return routerClient.json(
56+
{ message: "REFRESH_FAILED", error },
57+
{ status: 500 },
58+
);
59+
}
4160
}
61+
62+
let accessToken: KindeAccessToken | null = null;
63+
let idToken: KindeIdToken | null = null;
64+
4265
try {
43-
const refreshResponse = await kindeClient.refreshTokens(session);
44-
accessTokenEncoded = refreshResponse.access_token;
45-
idTokenEncoded = refreshResponse.id_token;
66+
accessToken = jwtDecoder<KindeAccessToken>(accessTokenEncoded);
4667
} catch (error) {
4768
if (config.isDebugMode) {
48-
console.error("setup: refresh tokens failed - returning error");
69+
console.error(
70+
"setup: access token decode failed, redirecting to login",
71+
);
4972
}
5073
return routerClient.json(
51-
{ message: "REFRESH_FAILED", error },
74+
{ message: "ACCESS_TOKEN_DECODE_FAILED", error },
5275
{ status: 500 },
5376
);
5477
}
55-
}
5678

57-
let accessToken: KindeAccessToken | null = null;
58-
let idToken: KindeIdToken | null = null;
79+
try {
80+
idToken = jwtDecoder<KindeIdToken>(idTokenEncoded);
81+
} catch (error) {
82+
if (config.isDebugMode) {
83+
console.error("setup: id token decode failed, redirecting to login");
84+
}
85+
return routerClient.json(
86+
{ message: "ID_TOKEN_DECODE_FAILED", error },
87+
{ status: 500 },
88+
);
89+
}
5990

60-
try {
61-
accessToken = jwtDecoder<KindeAccessToken>(accessTokenEncoded);
62-
} catch (error) {
63-
if (config.isDebugMode) {
64-
console.error(
65-
"setup: access token decode failed, redirecting to login",
91+
if (!accessToken || !idToken) {
92+
return routerClient.json(
93+
{ message: "TOKENS_MISSING", error: "No access or id token" },
94+
{ status: 500 },
6695
);
6796
}
68-
return routerClient.json(
69-
{ message: "ACCESS_TOKEN_DECODE_FAILED", error },
70-
{ status: 500 },
71-
);
72-
}
7397

74-
try {
75-
idToken = jwtDecoder<KindeIdToken>(idTokenEncoded);
98+
const permissions = accessToken.permissions;
99+
100+
const organization = accessToken.org_code;
101+
const featureFlags = accessToken.feature_flags;
102+
const userOrganizations = idToken.org_codes;
103+
const orgName = accessToken.org_name;
104+
const orgProperties = accessToken.organization_properties;
105+
const orgNames = idToken.organizations;
106+
107+
return routerClient.json({
108+
accessToken,
109+
accessTokenEncoded,
110+
accessTokenRaw: accessTokenEncoded,
111+
idToken,
112+
idTokenRaw: idTokenEncoded,
113+
idTokenEncoded,
114+
user: generateUserObject(idToken, accessToken),
115+
permissions: {
116+
permissions,
117+
orgCode: organization,
118+
},
119+
needsRefresh: false,
120+
message: "OK",
121+
organization: {
122+
orgCode: organization,
123+
orgName,
124+
properties: {
125+
city: orgProperties?.kp_org_city?.v,
126+
industry: orgProperties?.kp_org_industry?.v,
127+
postcode: orgProperties?.kp_org_postcode?.v,
128+
state_region: orgProperties?.kp_org_state_region?.v,
129+
street_address: orgProperties?.kp_org_street_address?.v,
130+
street_address_2: orgProperties?.kp_org_street_address_2?.v,
131+
},
132+
},
133+
featureFlags,
134+
userOrganizations: {
135+
orgCodes: userOrganizations,
136+
orgs: orgNames?.map((org) => ({
137+
code: org?.id,
138+
name: org?.name,
139+
})),
140+
},
141+
});
76142
} catch (error) {
77143
if (config.isDebugMode) {
78-
console.error("setup: id token decode failed, redirecting to login");
144+
console.error("setup: failed, error: ", error);
79145
}
80-
return routerClient.json(
81-
{ message: "ID_TOKEN_DECODE_FAILED", error },
82-
{ status: 500 },
83-
);
84-
}
85146

86-
if (!accessToken || !idToken) {
87147
return routerClient.json(
88-
{ message: "TOKENS_MISSING", error: "No access or id token" },
148+
{
149+
message: "SETUP_FAILED",
150+
error,
151+
},
89152
{ status: 500 },
90153
);
91154
}
92-
93-
const permissions = accessToken.permissions;
94-
95-
const organization = accessToken.org_code;
96-
const featureFlags = accessToken.feature_flags;
97-
const userOrganizations = idToken.org_codes;
98-
const orgName = accessToken.org_name;
99-
const orgProperties = accessToken.organization_properties;
100-
const orgNames = idToken.organizations;
101-
102-
return routerClient.json({
103-
accessToken,
104-
accessTokenEncoded,
105-
accessTokenRaw: accessTokenEncoded,
106-
idToken,
107-
idTokenRaw: idTokenEncoded,
108-
idTokenEncoded,
109-
user: generateUserObject(idToken, accessToken),
110-
permissions: {
111-
permissions,
112-
orgCode: organization,
113-
},
114-
needsRefresh: false,
115-
message: "OK",
116-
organization: {
117-
orgCode: organization,
118-
orgName,
119-
properties: {
120-
city: orgProperties?.kp_org_city?.v,
121-
industry: orgProperties?.kp_org_industry?.v,
122-
postcode: orgProperties?.kp_org_postcode?.v,
123-
state_region: orgProperties?.kp_org_state_region?.v,
124-
street_address: orgProperties?.kp_org_street_address?.v,
125-
street_address_2: orgProperties?.kp_org_street_address_2?.v,
126-
},
127-
},
128-
featureFlags,
129-
userOrganizations: {
130-
orgCodes: userOrganizations,
131-
orgs: orgNames?.map((org) => ({
132-
code: org?.id,
133-
name: org?.name,
134-
})),
135-
},
136-
});
137-
} catch (error) {
138-
if (config.isDebugMode) {
139-
console.error("setup: failed, error: ", error);
140-
}
141-
142-
return routerClient.json(
143-
{
144-
message: "SETUP_FAILED",
145-
error,
146-
},
147-
{ status: 500 },
148-
);
149-
}
155+
});
150156
};
Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
1-
import {
2-
GLOBAL_COOKIE_OPTIONS,
3-
MAX_COOKIE_LENGTH,
4-
TWENTY_NINE_DAYS,
5-
} from "../constants";
1+
import { MAX_COOKIE_LENGTH } from "../constants";
62
import { splitString } from "../splitString";
7-
import { config } from "../../config";
8-
import { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies";
3+
import { getStandardCookieOptions } from "../../utils/cookies/getStandardCookieOptions";
94

105
export const getSplitCookies = (cookieName: string, cookieValue: string) => {
116
return splitString(cookieValue, MAX_COOKIE_LENGTH).map((value, index) => {
127
return {
138
name: cookieName + (index === 0 ? "" : index),
149
value: value,
15-
options: {
16-
maxAge: TWENTY_NINE_DAYS,
17-
domain: config.cookieDomain ? config.cookieDomain : undefined,
18-
...GLOBAL_COOKIE_OPTIONS,
19-
} as Partial<RequestCookie>,
10+
options: getStandardCookieOptions(),
2011
};
2112
});
2213
};

0 commit comments

Comments
 (0)