|
1 |
| -import jwksClient from "jwks-rsa"; |
2 |
| -import JsonWebToken from "jsonwebtoken"; |
3 |
| -import type { JwtHeader, JwtPayload, SigningKeyCallback } from "jsonwebtoken"; |
4 |
| - |
| 1 | +import jose from "jose"; |
5 | 2 | import { cookies, headers } from "next/headers";
|
6 | 3 | import { redirect } from "next/navigation";
|
7 | 4 |
|
| 5 | +import SuperTokens from "../../../superTokens"; |
| 6 | + |
| 7 | +import type { AccessTokenPayload, LoadedSessionContext } from "../types"; |
| 8 | + |
8 | 9 | // const ACCESS_TOKEN_NAME = "st-access-token";
|
9 | 10 | const ACCESS_TOKEN_NAME = "sAccessToken";
|
10 | 11 | const FRONT_TOKEN_NAME = "sFrontToken";
|
11 | 12 |
|
12 |
| -const REFRESH_TOKEN_PATH = "/api/refresh"; |
13 |
| -const AUTH_PATH = "/auth"; |
14 |
| - |
15 | 13 | // TODO:
|
16 |
| -// - Update the api paths based on the SDK config |
17 | 14 | // - Add error handling
|
18 | 15 | // - Figure out the correct token name. Is it sAccessToken or st-access-token?
|
19 |
| -export async function getSessionOrRedirect() { |
| 16 | +export async function getSessionOrRedirect(): Promise<LoadedSessionContext> { |
20 | 17 | const headersList = await headers();
|
21 | 18 | const cookieStore = await cookies();
|
22 |
| - |
23 | 19 | const frontToken = cookieStore.get(FRONT_TOKEN_NAME)?.value;
|
24 |
| - |
| 20 | + const authPagePage = getWebsiteBasePath(); |
25 | 21 | if (!frontToken) {
|
26 |
| - redirect(AUTH_PATH); |
| 22 | + redirect(authPagePage); |
27 | 23 | }
|
28 | 24 |
|
29 |
| - // const lastAccessTokenUpdate = cookieStore.get(LAST_ACCESS_TOKEN_UPDATE)?.value; |
30 |
| - // if (!lastAccessTokenUpdate) { |
31 |
| - // redirect(REFRESH_TOKEN_PATH); |
32 |
| - // } |
33 |
| - // const parsedLastAccessTokenUpdate = parseInt(lastAccessTokenUpdate); |
34 |
| - |
| 25 | + const refreshTokenPath = `${getApiBasePath()}/refresh`; |
35 | 26 | const parsedFrontToken = parseFrontToken(frontToken);
|
36 | 27 | if (parsedFrontToken.up?.exp && parsedFrontToken.up.exp < Date.now()) {
|
37 |
| - redirect(REFRESH_TOKEN_PATH); |
| 28 | + redirect(refreshTokenPath); |
38 | 29 | }
|
39 | 30 |
|
40 | 31 | const accessToken = cookieStore.get(ACCESS_TOKEN_NAME)?.value || headersList.get(ACCESS_TOKEN_NAME);
|
41 | 32 | if (!accessToken) {
|
42 | 33 | // TODO: Should redirect to auth page?
|
43 |
| - redirect(REFRESH_TOKEN_PATH); |
| 34 | + redirect(refreshTokenPath); |
44 | 35 | }
|
45 | 36 |
|
46 |
| - const parsedAccessToken = await verifyToken(accessToken); |
47 |
| - if (!areTokenPayloadsEqual(parsedFrontToken, parsedAccessToken)) { |
48 |
| - redirect(REFRESH_TOKEN_PATH); |
| 37 | + const parsedAccessToken = await parseAccessToken(accessToken); |
| 38 | + if (!comparePayloads(parsedFrontToken, parsedAccessToken)) { |
| 39 | + redirect(refreshTokenPath); |
49 | 40 | }
|
50 | 41 |
|
51 |
| - // TODO: Return the actual session object |
52 |
| - return {}; |
| 42 | + return { |
| 43 | + userId: parsedAccessToken.up.sub, |
| 44 | + accessTokenPayload: parsedAccessToken, |
| 45 | + doesSessionExist: true, |
| 46 | + loading: false, |
| 47 | + invalidClaims: [], |
| 48 | + accessDeniedValidatorError: undefined, |
| 49 | + }; |
53 | 50 | }
|
54 | 51 |
|
55 |
| -function parseFrontToken(frontToken: string): TokenPayload { |
56 |
| - return JSON.parse(decodeURIComponent(escape(atob(frontToken)))); |
57 |
| -} |
| 52 | +const getApiBasePath = () => { |
| 53 | + return SuperTokens.getInstanceOrThrow().appInfo.apiBasePath.getAsStringDangerous(); |
| 54 | +}; |
58 | 55 |
|
59 |
| -type TokenPayload = { |
60 |
| - uid: string; |
61 |
| - ate: number; |
62 |
| - up: { |
63 |
| - iat: number; |
64 |
| - exp: number; |
65 |
| - sub: string; |
66 |
| - tId: string; |
67 |
| - rsub: string; |
68 |
| - sessionHandle: string; |
69 |
| - refrehTokenHash1: string; |
70 |
| - parentRefereshTokenHash1: string | null; |
71 |
| - antiCsrfToken: string | null; |
72 |
| - iss: string; |
73 |
| - "st-role": { |
74 |
| - v: string; |
75 |
| - t: number[]; |
76 |
| - }; |
77 |
| - "st-perm": { |
78 |
| - v: string[]; |
79 |
| - t: number; |
80 |
| - }; |
81 |
| - }; |
| 56 | +const getWebsiteBasePath = () => { |
| 57 | + return SuperTokens.getInstanceOrThrow().appInfo.websiteBasePath.getAsStringDangerous(); |
82 | 58 | };
|
83 | 59 |
|
84 |
| -const client = jwksClient({ |
85 |
| - jwksUri: `<api-domain>/<api-base-path>/jwt/jwks.json`, |
86 |
| -}); |
| 60 | +function parseFrontToken(frontToken: string): AccessTokenPayload { |
| 61 | + return JSON.parse(decodeURIComponent(escape(atob(frontToken)))); |
| 62 | +} |
87 | 63 |
|
88 |
| -async function verifyToken(token: string): Promise<JwtPayload> { |
89 |
| - const getPublicKey = (header: JwtHeader, callback: SigningKeyCallback) => { |
90 |
| - client.getSigningKey(header.kid, (err, key) => { |
91 |
| - if (err) { |
92 |
| - callback(err); |
93 |
| - } else { |
94 |
| - const signingKey = key?.getPublicKey(); |
95 |
| - callback(null, signingKey); |
96 |
| - } |
97 |
| - }); |
98 |
| - }; |
| 64 | +async function parseAccessToken(token: string): Promise<AccessTokenPayload> { |
| 65 | + const JWKS = jose.createRemoteJWKSet(new URL(`${getApiBasePath()}/authjwt/jwks.json`)); |
| 66 | + const { payload } = await jose.jwtVerify(token, JWKS); |
| 67 | + return payload; |
| 68 | +} |
99 | 69 |
|
100 |
| - return new Promise((resolve, reject) => { |
101 |
| - JsonWebToken.verify(token, getPublicKey, {}, (err, decoded) => { |
102 |
| - if (err) { |
103 |
| - reject(err); |
104 |
| - } else { |
105 |
| - resolve(decoded as JwtPayload); |
106 |
| - } |
107 |
| - }); |
108 |
| - }); |
| 70 | +function comparePayloads(payload1: AccessTokenPayload, payload2: AccessTokenPayload): boolean { |
| 71 | + return ( |
| 72 | + payload1.uid === payload2.uid && |
| 73 | + payload1.ate === payload2.ate && |
| 74 | + payload1.up.sub === payload2.up.sub && |
| 75 | + payload1.up.tId === payload2.up.tId && |
| 76 | + payload1.up.sessionHandle === payload2.up.sessionHandle && |
| 77 | + payload1.up.refrehTokenHash1 === payload2.up.refrehTokenHash1 && |
| 78 | + payload1.up.parentRefereshTokenHash1 === payload2.up.parentRefereshTokenHash1 && |
| 79 | + payload1.up.antiCsrfToken === payload2.up.antiCsrfToken && |
| 80 | + payload1.up.iss === payload2.up.iss && |
| 81 | + payload1.up["st-role"].v === payload2.up["st-role"].v && |
| 82 | + payload1.up["st-role"].t.toString() === payload2.up["st-role"].t.toString() && |
| 83 | + payload1.up["st-perm"].v.toString() === payload2.up["st-perm"].v.toString() && |
| 84 | + payload1.up["st-perm"].t === payload2.up["st-perm"].t |
| 85 | + ); |
109 | 86 | }
|
0 commit comments