Skip to content

Commit d99eea4

Browse files
committed
Account for pages directory in the function implementation
1 parent e278f9f commit d99eea4

File tree

5 files changed

+173
-19
lines changed

5 files changed

+173
-19
lines changed

lib/ts/framework/index.ts

Whitespace-only changes.

lib/ts/framework/nextjs.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import SuperTokensReact from "../";
2+
import type { SuperTokensConfig } from "../types";
3+
4+
const NextjsRouter = {
5+
pathName: "",
6+
};
7+
8+
type SuperTokensNextjsConfig = {
9+
routing: "app-directory" | "pages-directory";
10+
} & SuperTokensConfig;
11+
12+
export function init(config: SuperTokensConfig) {
13+
if (typeof window !== "undefined") {
14+
SuperTokensReact.init({
15+
...config,
16+
windowHandler: (original) => ({
17+
...original,
18+
location: {
19+
...original.location,
20+
getPathName: () => NextjsRouter.pathName,
21+
},
22+
}),
23+
});
24+
}
25+
}
26+
27+
export function SuperTokensProvider(props) {}

lib/ts/recipe/session/framework/index.ts

Whitespace-only changes.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import jose from "jose";
2+
3+
import SuperTokens from "../../../superTokens";
4+
5+
import type { AccessTokenPayload, LoadedSessionContext } from "../types";
6+
7+
const COOKIE_ACCESS_TOKEN_NAME = "sAccessToken";
8+
const HEADER_ACCESS_TOKEN_NAME = "st-access-token";
9+
const FRONT_TOKEN_NAME = "sFrontToken";
10+
11+
type CookiesStore = {
12+
get: (name: string) => { value: string };
13+
};
14+
15+
function isCookiesStore(obj: unknown): obj is CookiesStore {
16+
return typeof obj === "object" && obj !== null && "get" in obj && typeof (obj as CookiesStore).get === "function";
17+
}
18+
19+
type CookiesObject = Record<string, string>;
20+
21+
type GetServerSidePropsRedirect = {
22+
redirect: { destination: string; permanent: boolean };
23+
};
24+
type GetServerSidePropsReturnValue =
25+
| {
26+
props: { session: LoadedSessionContext };
27+
}
28+
| GetServerSidePropsRedirect;
29+
30+
type SSRSessionState =
31+
| "front-token-not-found"
32+
| "front-token-expired"
33+
| "access-token-not-found"
34+
| "tokens-do-not-match"
35+
| "tokens-match";
36+
37+
export async function getSSRSession(
38+
cookies: CookiesStore,
39+
redirect: (url: string) => never
40+
): Promise<LoadedSessionContext>;
41+
export async function getSSRSession(cookies: CookiesObject): Promise<GetServerSidePropsReturnValue>;
42+
export async function getSSRSession(
43+
cookies: CookiesObject | CookiesStore,
44+
redirect?: (url: string) => never
45+
): Promise<LoadedSessionContext | GetServerSidePropsReturnValue> {
46+
if (isCookiesStore(cookies)) {
47+
if (!redirect) {
48+
throw new Error("Undefined redirect function");
49+
}
50+
}
51+
52+
const { state, session } = await getSSRSessionState(cookies);
53+
switch (state) {
54+
case "front-token-not-found":
55+
if (!redirect) {
56+
return { redirect: { destination: getWebsiteBasePath(), permanent: false } };
57+
} else {
58+
return redirect(getWebsiteBasePath());
59+
}
60+
case "front-token-expired":
61+
case "access-token-not-found":
62+
case "tokens-do-not-match":
63+
if (!redirect) {
64+
return { redirect: { destination: `${getApiBasePath()}/refresh`, permanent: false } };
65+
} else {
66+
return redirect(`${getApiBasePath()}/refresh`);
67+
}
68+
case "tokens-match":
69+
if (!redirect) {
70+
return { props: { session: session as LoadedSessionContext } };
71+
}
72+
return session as LoadedSessionContext;
73+
default:
74+
// This is here just to prevent typescript from complaining
75+
// about the function not returning a value
76+
throw new Error(`Unknown state: ${state}`);
77+
}
78+
}
79+
80+
function getCookieValue(cookieStore: CookiesStore | CookiesObject, name: string): string | undefined {
81+
if (isCookiesStore(cookieStore)) {
82+
return cookieStore.get(name)?.value;
83+
}
84+
return cookieStore[name];
85+
}
86+
87+
async function getSSRSessionState(
88+
cookies: CookiesObject | CookiesStore
89+
): Promise<{ state: SSRSessionState; session?: LoadedSessionContext }> {
90+
const frontToken = getCookieValue(cookies, FRONT_TOKEN_NAME);
91+
if (!frontToken) {
92+
return { state: "front-token-not-found" };
93+
}
94+
95+
const parsedFrontToken = parseFrontToken(frontToken);
96+
if (parsedFrontToken.up?.exp && parsedFrontToken.up.exp < Date.now()) {
97+
return { state: "front-token-expired" };
98+
}
99+
100+
const accessToken =
101+
getCookieValue(cookies, COOKIE_ACCESS_TOKEN_NAME) || getCookieValue(cookies, HEADER_ACCESS_TOKEN_NAME);
102+
if (!accessToken) {
103+
return { state: "access-token-not-found" };
104+
}
105+
106+
const parsedAccessToken = await parseAccessToken(accessToken);
107+
if (!comparePayloads(parsedFrontToken, parsedAccessToken)) {
108+
return { state: "tokens-do-not-match" };
109+
}
110+
111+
return {
112+
state: "tokens-match",
113+
session: {
114+
userId: parsedAccessToken.up.sub,
115+
accessTokenPayload: parsedAccessToken,
116+
doesSessionExist: true,
117+
loading: false,
118+
invalidClaims: [],
119+
accessDeniedValidatorError: undefined,
120+
},
121+
};
122+
}
123+
124+
const getApiBasePath = () => {
125+
return SuperTokens.getInstanceOrThrow().appInfo.apiBasePath.getAsStringDangerous();
126+
};
127+
128+
const getWebsiteBasePath = () => {
129+
return SuperTokens.getInstanceOrThrow().appInfo.websiteBasePath.getAsStringDangerous();
130+
};
131+
132+
function parseFrontToken(frontToken: string): AccessTokenPayload {
133+
return JSON.parse(decodeURIComponent(escape(atob(frontToken))));
134+
}
135+
136+
async function parseAccessToken(token: string): Promise<AccessTokenPayload> {
137+
const JWKS = jose.createRemoteJWKSet(new URL(`${getApiBasePath()}/authjwt/jwks.json`));
138+
const { payload } = await jose.jwtVerify(token, JWKS);
139+
return payload;
140+
}
141+
142+
function comparePayloads(payload1: AccessTokenPayload, payload2: AccessTokenPayload): boolean {
143+
return JSON.stringify(payload1) === JSON.stringify(payload2);
144+
}

lib/ts/recipe/session/nextjs/getSessionOrRedirect.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import jose from "jose";
2-
import { cookies, headers } from "next/headers";
2+
import { cookies } from "next/headers";
33
import { redirect } from "next/navigation";
44

55
import SuperTokens from "../../../superTokens";
66

77
import type { AccessTokenPayload, LoadedSessionContext } from "../types";
88

9-
// const ACCESS_TOKEN_NAME = "st-access-token";
109
const COOKIE_ACCESS_TOKEN_NAME = "sAccessToken";
1110
const HEADER_ACCESS_TOKEN_NAME = "st-access-token";
1211
const FRONT_TOKEN_NAME = "sFrontToken";
1312

1413
// TODO:
1514
// - Add error handling
1615
export async function getSessionOrRedirect(): Promise<LoadedSessionContext> {
17-
// const headersList = await headers();
1816
const cookieStore = await cookies();
1917
const frontToken = cookieStore.get(FRONT_TOKEN_NAME)?.value;
2018
const authPagePage = getWebsiteBasePath();
@@ -28,7 +26,6 @@ export async function getSessionOrRedirect(): Promise<LoadedSessionContext> {
2826
redirect(refreshTokenPath);
2927
}
3028

31-
// Apparently both end up in cookies.
3229
const accessToken =
3330
cookieStore.get(COOKIE_ACCESS_TOKEN_NAME)?.value || cookieStore.get(HEADER_ACCESS_TOKEN_NAME)?.value;
3431
if (!accessToken) {
@@ -70,19 +67,5 @@ async function parseAccessToken(token: string): Promise<AccessTokenPayload> {
7067
}
7168

7269
function comparePayloads(payload1: AccessTokenPayload, payload2: AccessTokenPayload): boolean {
73-
return (
74-
payload1.uid === payload2.uid &&
75-
payload1.ate === payload2.ate &&
76-
payload1.up.sub === payload2.up.sub &&
77-
payload1.up.tId === payload2.up.tId &&
78-
payload1.up.sessionHandle === payload2.up.sessionHandle &&
79-
payload1.up.refrehTokenHash1 === payload2.up.refrehTokenHash1 &&
80-
payload1.up.parentRefereshTokenHash1 === payload2.up.parentRefereshTokenHash1 &&
81-
payload1.up.antiCsrfToken === payload2.up.antiCsrfToken &&
82-
payload1.up.iss === payload2.up.iss &&
83-
payload1.up["st-role"].v === payload2.up["st-role"].v &&
84-
payload1.up["st-role"].t.toString() === payload2.up["st-role"].t.toString() &&
85-
payload1.up["st-perm"].v.toString() === payload2.up["st-perm"].v.toString() &&
86-
payload1.up["st-perm"].t === payload2.up["st-perm"].t
87-
);
70+
return JSON.stringify(payload1) === JSON.stringify(payload2);
8871
}

0 commit comments

Comments
 (0)