-
Notifications
You must be signed in to change notification settings - Fork 0
feat(auth): handle refresh token #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
+1,497
−505
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
48e1b28
feat: handle refresh token
peppescg a6125db
refactor: move to constants file
peppescg 15b6005
refactor: env var
peppescg ec0e948
refactor: use NEXT_PUBLIC_OIDC_PROVIDER_ID as unique env var
peppescg cddb515
refactor: update doc for env vars
peppescg f87d93e
lint and format
peppescg 50f154f
feat: configure hey-api client passing the bearer token to api services
peppescg a57c8a5
feat(auth): handle refresh token
peppescg c89da94
refactor: refresh token diagram flow
peppescg c3a228a
fix: remove leftover catalogue page
peppescg 04656fc
fix: api-client new instance
peppescg b253e31
fix: raise console error in case of missing token or userId
peppescg 19231fe
fix: redirect to sign in if refresh token is expired
peppescg b46fdd6
feat: implement OIDC authentication with automatic token refresh
peppescg 1daf0f3
refactor: move to util reusable account auth info
peppescg 477661e
fix: update env var
peppescg 07434cf
fix: remove throw error on better auth secret env var
peppescg 4eb6f4a
fix: remove throw error on api base url env var
peppescg e33fca5
refactor: comment
peppescg 2f6ee5d
fix: types
peppescg 1e04eac
leftover
peppescg afd590c
feat(card): add server card component
peppescg 4e3deba
fix: client api on server actions
peppescg 99ce1ec
chore: pnpm script for run real oidc + msw
peppescg 2085ddf
fix: redirect issue
peppescg 7396a89
format
peppescg b1100e3
test: update use cases
peppescg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| import { cookies } from "next/headers"; | ||
| import type { NextRequest } from "next/server"; | ||
| import { NextResponse } from "next/server"; | ||
| import { refreshAccessToken } from "@/lib/auth/auth"; | ||
| import { BETTER_AUTH_SECRET, COOKIE_NAME } from "@/lib/auth/constants"; | ||
| import type { OidcTokenData } from "@/lib/auth/types"; | ||
| import { decrypt } from "@/lib/auth/utils"; | ||
|
|
||
| /** | ||
| * API Route Handler to refresh OIDC access token. | ||
| * | ||
| * This Route Handler can modify cookies (unlike Server Actions during render). | ||
| */ | ||
| export async function POST(request: NextRequest) { | ||
| try { | ||
| const body = await request.json(); | ||
| const { userId } = body; | ||
|
|
||
| if (!userId) { | ||
| return NextResponse.json({ error: "Missing userId" }, { status: 400 }); | ||
| } | ||
|
|
||
| const cookieStore = await cookies(); | ||
| const encryptedCookie = cookieStore.get(COOKIE_NAME); | ||
|
|
||
| if (!encryptedCookie?.value) { | ||
| return NextResponse.json({ error: "No token found" }, { status: 401 }); | ||
| } | ||
peppescg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| let tokenData: OidcTokenData; | ||
| try { | ||
| tokenData = await decrypt(encryptedCookie.value, BETTER_AUTH_SECRET); | ||
| } catch (error) { | ||
| console.error("[Refresh API] Token decryption failed:", error); | ||
| cookieStore.delete(COOKIE_NAME); | ||
| return NextResponse.json({ error: "Invalid token" }, { status: 401 }); | ||
| } | ||
|
|
||
| if (tokenData.userId !== userId) { | ||
| console.error("[Refresh API] Token userId mismatch"); | ||
| cookieStore.delete(COOKIE_NAME); | ||
| return NextResponse.json({ error: "Invalid token" }, { status: 401 }); | ||
| } | ||
|
|
||
| if (!tokenData.refreshToken) { | ||
| console.error("[Refresh API] No refresh token available"); | ||
| cookieStore.delete(COOKIE_NAME); | ||
| return NextResponse.json({ error: "No refresh token" }, { status: 401 }); | ||
| } | ||
|
|
||
| // Call refreshAccessToken which will save the new token in the cookie | ||
| const refreshedData = await refreshAccessToken( | ||
| tokenData.refreshToken, | ||
| userId, | ||
| tokenData.refreshTokenExpiresAt, | ||
| ); | ||
|
|
||
| if (!refreshedData) { | ||
| console.error("[Refresh API] Token refresh failed"); | ||
| cookieStore.delete(COOKIE_NAME); | ||
| return NextResponse.json( | ||
| { error: "[Refresh API] Refresh failed" }, | ||
| { status: 401 }, | ||
| ); | ||
| } | ||
|
|
||
| return NextResponse.json({ | ||
| success: true, | ||
| accessToken: refreshedData.accessToken, | ||
peppescg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); | ||
| } catch (error) { | ||
| console.error("[Refresh API] Error during token refresh:", error); | ||
| return NextResponse.json( | ||
| { error: "Internal server error" }, | ||
| { status: 500 }, | ||
| ); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,54 @@ | ||
| "use server"; | ||
|
|
||
| import { getRegistryV01Servers } from "@/generated/sdk.gen"; | ||
| import type { V0ServerJson } from "@/generated/types.gen"; | ||
| import { getAuthenticatedClient } from "@/lib/api-client"; | ||
|
|
||
| export async function getServers(): Promise<V0ServerJson[]> { | ||
| const api = await getAuthenticatedClient(); | ||
| const resp = await api.getRegistryV01Servers({ | ||
| client: api.client, | ||
| }); | ||
|
|
||
| if (resp.error) { | ||
| console.error("[catalog] Failed to fetch servers:", resp.error); | ||
| return []; | ||
| } | ||
|
|
||
| if (!resp.data) { | ||
| return []; | ||
| } | ||
|
|
||
| const data = resp.data; | ||
| const items = Array.isArray(data?.servers) ? data.servers : []; | ||
|
|
||
| // Extract the server objects from the response | ||
| return items | ||
| .map((item) => item?.server) | ||
| .filter((server): server is V0ServerJson => server != null); | ||
| } | ||
|
|
||
| export async function getServersSummary() { | ||
| try { | ||
| const resp = await getRegistryV01Servers(); | ||
| const data = resp.data; | ||
| const items = Array.isArray(data?.servers) ? data.servers : []; | ||
|
|
||
| const titles = items | ||
| .map((it) => it?.server?.title ?? it?.server?.name) | ||
| .filter((t): t is string => typeof t === "string") | ||
| .slice(0, 5); | ||
|
|
||
| const sample = items.slice(0, 5).map((it) => ({ | ||
| title: it?.server?.title ?? it?.server?.name ?? "Unknown", | ||
| name: it?.server?.name ?? "unknown", | ||
| version: it?.server?.version, | ||
| })); | ||
|
|
||
| return { count: items.length, titles, sample }; | ||
| } catch (error) { | ||
| // Log the error for debugging | ||
| console.error("[catalog] Failed to fetch servers:", error); | ||
| const api = await getAuthenticatedClient(); | ||
| const resp = await api.getRegistryV01Servers({ client: api.client }); | ||
|
|
||
| if (resp.error) { | ||
| console.error("[catalog] Failed to fetch servers:", resp.error); | ||
| return { count: 0, titles: [], sample: [] }; | ||
| } | ||
|
|
||
| if (!resp.data) { | ||
| return { count: 0, titles: [], sample: [] }; | ||
| } | ||
|
|
||
| const items = Array.isArray(resp.data?.servers) ? resp.data.servers : []; | ||
|
|
||
| const sample = items.slice(0, 5).map((it) => ({ | ||
| title: it?.server?.title ?? it?.server?.name ?? "Unknown", | ||
| name: it?.server?.name ?? "unknown", | ||
| version: it?.server?.version, | ||
| })); | ||
|
|
||
| const titles = sample.map((s) => s.title); | ||
|
|
||
| return { count: items.length, titles, sample }; | ||
| } |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.