From 51c7a79f323c6a4938a855ba0b46ab9e5f700bef Mon Sep 17 00:00:00 2001 From: LaeekAhmed Date: Sun, 8 Mar 2026 11:05:26 -0400 Subject: [PATCH 1/2] Revert "Updated to nextauth v5, switched to microsoft entra (#47)" This reverts commit 47739262bd35b2643af96efe4d30c6c3e1af86c3. --- package.json | 2 +- pnpm-lock.yaml | 142 +++++++++++++------- sample.env | 10 +- src/app/api/auth/[...nextauth]/route.ts | 8 +- src/app/auth/signin/page.tsx | 25 ++-- src/app/layout.tsx | 14 +- src/app/providers.tsx | 6 + src/auth.ts | 88 ------------ src/components/ui/custom/macfast-header.tsx | 4 +- src/hooks/useFetchWithAuth.ts | 2 +- src/lib/auth.ts | 76 +++++++++++ src/proxy.ts | 43 +++--- src/types/next-auth.d.ts | 26 ++-- 13 files changed, 242 insertions(+), 204 deletions(-) create mode 100644 src/app/providers.tsx delete mode 100644 src/auth.ts create mode 100644 src/lib/auth.ts diff --git a/package.json b/package.json index cb0c663..d681897 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "lodash": "^4.17.23", "lucide-react": "^0.553.0", "next": "^16.0.8", - "next-auth": "5.0.0-beta.30", + "next-auth": "^4.24.13", "radix-ui": "^1.4.3", "react": "19.2.0", "react-dom": "19.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d42bfd9..9c2fe6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,8 +105,8 @@ importers: specifier: ^16.0.8 version: 16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) next-auth: - specifier: 5.0.0-beta.30 - version: 5.0.0-beta.30(next@16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) + specifier: ^4.24.13 + version: 4.24.13(next@16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) radix-ui: specifier: ^1.4.3 version: 1.4.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -157,20 +157,6 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@auth/core@0.41.0': - resolution: {integrity: sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==} - peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - nodemailer: ^6.8.0 - peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': - optional: true - nodemailer: - optional: true - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -226,6 +212,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -1872,6 +1862,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -2402,8 +2396,8 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - jose@6.1.3: - resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2552,6 +2546,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lucide-react@0.553.0: resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} peerDependencies: @@ -2598,18 +2596,16 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next-auth@5.0.0-beta.30: - resolution: {integrity: sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==} + next-auth@4.24.13: + resolution: {integrity: sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==} peerDependencies: - '@simplewebauthn/browser': ^9.0.1 - '@simplewebauthn/server': ^9.0.2 - next: ^14.0.0-0 || ^15.0.0 || ^16.0.0 + '@auth/core': 0.34.3 + next: ^12.2.5 || ^13 || ^14 || ^15 || ^16 nodemailer: ^7.0.7 - react: ^18.2.0 || ^19.0.0 + react: ^17.0.2 || ^18 || ^19 + react-dom: ^17.0.2 || ^18 || ^19 peerDependenciesMeta: - '@simplewebauthn/browser': - optional: true - '@simplewebauthn/server': + '@auth/core': optional: true nodemailer: optional: true @@ -2638,13 +2634,17 @@ packages: node-releases@2.0.26: resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==} - oauth4webapi@3.8.5: - resolution: {integrity: sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==} + oauth@0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2673,6 +2673,13 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + oidc-token-hash@5.2.0: + resolution: {integrity: sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==} + engines: {node: ^10.13.0 || >=12.0.0} + + openid-client@5.7.1: + resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2727,18 +2734,21 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - preact-render-to-string@6.5.11: - resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} + preact-render-to-string@5.2.6: + resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} peerDependencies: preact: '>=10' - preact@10.24.3: - resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} + preact@10.28.0: + resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -3088,6 +3098,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3116,6 +3130,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yjs@13.6.29: resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -3137,14 +3154,6 @@ snapshots: '@alloc/quick-lru@5.2.0': {} - '@auth/core@0.41.0': - dependencies: - '@panva/hkdf': 1.2.1 - jose: 6.1.3 - oauth4webapi: 3.8.5 - preact: 10.24.3 - preact-render-to-string: 6.5.11(preact@10.24.3) - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -3222,6 +3231,8 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/runtime@7.28.4': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -4928,6 +4939,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@0.7.2: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -5613,7 +5626,7 @@ snapshots: jiti@2.6.1: {} - jose@6.1.3: {} + jose@4.15.9: {} js-tokens@4.0.0: {} @@ -5728,6 +5741,10 @@ snapshots: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lucide-react@0.553.0(react@19.2.0): dependencies: react: 19.2.0 @@ -5763,11 +5780,20 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.30(next@16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0): + next-auth@4.24.13(next@16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@auth/core': 0.41.0 + '@babel/runtime': 7.28.4 + '@panva/hkdf': 1.2.1 + cookie: 0.7.2 + jose: 4.15.9 next: 16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + oauth: 0.9.15 + openid-client: 5.7.1 + preact: 10.28.0 + preact-render-to-string: 5.2.6(preact@10.28.0) react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + uuid: 8.3.2 next@16.0.8(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: @@ -5794,10 +5820,12 @@ snapshots: node-releases@2.0.26: {} - oauth4webapi@3.8.5: {} + oauth@0.9.15: {} object-assign@4.1.1: {} + object-hash@2.2.0: {} + object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -5838,6 +5866,15 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + oidc-token-hash@5.2.0: {} + + openid-client@5.7.1: + dependencies: + jose: 4.15.9 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.2.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5891,14 +5928,17 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - preact-render-to-string@6.5.11(preact@10.24.3): + preact-render-to-string@5.2.6(preact@10.28.0): dependencies: - preact: 10.24.3 + preact: 10.28.0 + pretty-format: 3.8.0 - preact@10.24.3: {} + preact@10.28.0: {} prelude-ls@1.2.1: {} + pretty-format@3.8.0: {} + prismjs@1.30.0: {} prop-types@15.8.1: @@ -6395,6 +6435,8 @@ snapshots: dependencies: react: 19.2.0 + uuid@8.3.2: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -6444,9 +6486,7 @@ snapshots: yallist@3.1.1: {} - yjs@13.6.29: - dependencies: - lib0: 0.2.117 + yallist@4.0.0: {} yocto-queue@0.1.0: {} diff --git a/sample.env b/sample.env index d3f145f..78b1b57 100644 --- a/sample.env +++ b/sample.env @@ -2,9 +2,9 @@ NEXT_PUBLIC_API_URL = http://localhost:8000 NEXTAUTH_URL=http://localhost:3000 # NEXTAUTH_SECRET is required by the Auth.js library to handle sessions, to generate a secret you can run this command (openssl rand -base64 32) or just set to a random string like "mysecret" -AUTH_SECRET= +NEXTAUTH_SECRET= -# These are all values that can be found in the teams chat provided by UTS -AUTH_MICROSOFT_ENTRA_ID_ID= -AUTH_MICROSOFT_ENTRA_ID_SECRET= -AUTH_MICROSOFT_ENTRA_ID_TENANT_ID= \ No newline at end of file +# These are all values that can be found in the Auth0 client, in the future will be provided by UTS +AUTH_CLIENT_SECRET= +AUTH_CLIENT_ID= +AUTH_ISSUER= \ No newline at end of file diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index 7c62e2d..7b38c1b 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,6 @@ -import { handlers } from "@/auth"; -export const { GET, POST } = handlers; +import NextAuth from "next-auth"; +import { authOptions } from "@/lib/auth"; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index a2bd5a5..5508a1b 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -1,34 +1,25 @@ "use client"; -import { useEffect, Suspense } from "react"; +import { useEffect } from "react"; import { signIn } from "next-auth/react"; import { useSearchParams } from "next/navigation"; -function SignInContent() { +export default function SignInPage() { const searchParams = useSearchParams(); - - const redirectTo = searchParams.get("callbackUrl") || "/"; + // If there is no callbackUrl (like if the user went directly to /auth/signin), redirect to home "/" + const callbackUrl = searchParams.get("callbackUrl") || "/"; useEffect(() => { - signIn("microsoft-entra-id", { redirectTo }); - }, [redirectTo]); + // "auth0" is the provider ID. This might be changed in the future when we swap to Azure Active Directory / Entra (whatever Microsoft calls it these days) + signIn("auth0", { callbackUrl }); + }, [callbackUrl]); return (

Redirecting to Login...

-

- Please wait while we connect to McMaster SSO -

+

Please wait while we send you to SSO

); } - -export default function SignInPage() { - return ( - Loading...}> - - - ); -} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2b9c64d..4ab0d58 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,10 +1,10 @@ import type { Metadata } from "next"; import { Inter, Poppins } from "next/font/google"; -import "@/styles/globals.css"; -import "@/styles/globals.css"; +import "../styles/globals.css"; -import { SessionProvider } from "next-auth/react"; -import { auth } from "@/auth"; +import { authOptions } from "@/lib/auth"; +import { getServerSession } from "next-auth"; +import { Providers } from "./providers"; const inter = Inter({ variable: "--font-inter", @@ -27,17 +27,17 @@ export default async function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const session = await auth(); + const session = await getServerSession(authOptions); return ( - + {children} - + ); } diff --git a/src/app/providers.tsx b/src/app/providers.tsx new file mode 100644 index 0000000..d29f39f --- /dev/null +++ b/src/app/providers.tsx @@ -0,0 +1,6 @@ +"use client"; +import { SessionProvider } from "next-auth/react"; + +export function Providers({ children, session }: any) { + return {children}; +} diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index c2acdf1..0000000 --- a/src/auth.ts +++ /dev/null @@ -1,88 +0,0 @@ -import NextAuth from "next-auth"; -import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id"; - -// followed example from https://authjs.dev/guides/refresh-token-rotation and https://authjs.dev/reference/core/providers/microsoft-entra-id - -export const { handlers, auth } = NextAuth({ - providers: [ - MicrosoftEntraID({ - clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID, - clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET, - issuer: `https://login.microsoftonline.com/${process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID}/v2.0`, - authorization: { - params: { - scope: "openid profile email offline_access", - }, - }, - }), - ], - callbacks: { - async jwt({ token, account }) { - if (account) { - console.log("new sign-in, saving tokens"); - return { - ...token, - access_token: account.access_token, - id_token: account.id_token, - expires_at: account.expires_at, - refresh_token: account.refresh_token, - }; - } - - const shouldRefresh = - !token.expires_at || Date.now() >= (token.expires_at - 60) * 1000; - - if (!shouldRefresh) { - const secondsLeft = token.expires_at - ? Math.floor(token.expires_at - Date.now() / 1000) - : "unknown"; - console.log(`token still valid, ${secondsLeft}s until refresh`); - return token; - } - - console.log("token expired, attempting refresh..."); - - if (!token.refresh_token) { - console.error("no refresh token available"); - return { ...token, error: "RefreshTokenError" }; - } - - try { - const response = await fetch( - `https://login.microsoftonline.com/${process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID}/oauth2/v2.0/token`, - { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - client_id: process.env.AUTH_MICROSOFT_ENTRA_ID_ID!, - client_secret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET!, - grant_type: "refresh_token", - refresh_token: token.refresh_token as string, - }), - }, - ); - - const tokensOrError = await response.json(); - if (!response.ok) throw tokensOrError; - - return { - ...token, - access_token: tokensOrError.access_token, - id_token: tokensOrError.id_token ?? token.id_token, - expires_at: Math.floor(Date.now() / 1000) + tokensOrError.expires_in, - refresh_token: tokensOrError.refresh_token ?? token.refresh_token, - error: undefined, - }; - } catch (error) { - console.error("token refresh failed:", error); - return { ...token, error: "RefreshTokenError" }; - } - }, - - async session({ session, token }) { - session.id_token = token.id_token; - session.error = token.error; - return session; - }, - }, -}); diff --git a/src/components/ui/custom/macfast-header.tsx b/src/components/ui/custom/macfast-header.tsx index 39049f1..cbab4c0 100644 --- a/src/components/ui/custom/macfast-header.tsx +++ b/src/components/ui/custom/macfast-header.tsx @@ -139,7 +139,7 @@ export function MacFastHeader() { signOut({ redirectTo: "/" })} + onClick={() => signOut({ callbackUrl: "/" })} > Log out @@ -148,7 +148,7 @@ export function MacFastHeader() { ) : ( - + {onResume ? ( + + ) : ( + + )} ); diff --git a/src/lib/api.ts b/src/lib/api.ts index b1d44e4..dfb732c 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,5 @@ -import { auth } from "@/auth"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/lib/auth"; import { useAuthFetch } from "@/hooks/useFetchWithAuth"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; @@ -21,8 +22,8 @@ export async function fetchWithAuth( endpoint: string, options: RequestInit = {}, ) { - const session = await auth(); - const token = session?.id_token; + const session = await getServerSession(authOptions); + const token = session?.accessToken; console.log(token); diff --git a/src/lib/resume-api.ts b/src/lib/resume-api.ts new file mode 100644 index 0000000..e5e72de --- /dev/null +++ b/src/lib/resume-api.ts @@ -0,0 +1,37 @@ +import type { useAuthFetch } from "@/hooks/useFetchWithAuth"; + + +export type ResumeTarget = { + course_code: string; + unit_name: string; + subtopic_name: string; +}; + + + +/** + * + * For now returns dummy data; to be replaced with real API call. + * + * @param courseCode + * @param authFetch + * @returns + */ +export async function getResumeTarget( + courseCode: string, + authFetch: ReturnType, +): Promise { + // TODO(ticket #66): Replace with real API call when backend resume endpoint is ready. + // Example (uncomment and use getJson from @/lib/api when ready): + // const response = await authFetch(`/api/core/resume/?course_code=${encodeURIComponent(courseCode)}`); + // if (!response.ok) throw new Error("Failed to get resume target"); + // return getJson(response) as Promise; + + // Dummy data + const dummy: ResumeTarget = { + course_code: courseCode, + unit_name: "Unit 1", + subtopic_name: "Introduction", + }; + return Promise.resolve(dummy); +}