diff --git a/auth.ts b/auth.ts index 9090603..c379cd2 100644 --- a/auth.ts +++ b/auth.ts @@ -3,8 +3,7 @@ import SpotifyProvider from 'next-auth/providers/spotify' import { db } from '@/lib/db' import { authURL } from '@/lib/auth/configs' -import { refreshAccessToken } from '@/lib/auth/refresh-access-token' -import type { ExtendedUser } from '@/types' +// import { refreshAccessToken } from '@/lib/auth/refresh-access-token' export const { handlers: { GET, POST }, @@ -69,20 +68,32 @@ export const { ...token, access_token: account.access_token, token_type: account.token_type, - expires_at: account.expires_at - ? account.expires_at * 1000 - : Date.now(), + expires_at: Date.now() + 30000, + // expires_at: account.expires_at + // ? account.expires_at * 1000 + // : Date.now(), expires_in: account.expires_in, refresh_token: account.refresh_token, } } - if (Date.now() < (token.expires_at as number)) { - return token - } + console.log('auth token ', { + refresh: token.refresh_token, + access: token.access_token, + }) + console.log({ + expires: new Date(token.expires_at as number).toLocaleTimeString( + 'sv-SE', + ), + }) + return token - console.log('token expired') - return await refreshAccessToken(token) + // if (Date.now() < (token.expires_at as number)) { + // return token + // } + + // console.log('token expired') + // return await refreshAccessToken(token) }, async session({ session, token }: { session: any; token: any }) { const user = { @@ -92,7 +103,7 @@ export const { expires_at: token.expires_at, expires_in: token.expires_in, refresh_token: token.refresh_token, - } satisfies ExtendedUser + } session.user = user session.error = token.error diff --git a/lib/auth/refresh-access-token.ts b/lib/auth/refresh-access-token.ts index 1795105..04908d4 100644 --- a/lib/auth/refresh-access-token.ts +++ b/lib/auth/refresh-access-token.ts @@ -1,6 +1,6 @@ import { JWT } from 'next-auth/jwt' -const refreshAccessToken = async (token: JWT) => { +export const refreshAccessToken = async (token: JWT) => { try { const params = new URLSearchParams() params.append('grant_type', 'refresh_token') @@ -45,5 +45,3 @@ const refreshAccessToken = async (token: JWT) => { } } } - -export { refreshAccessToken } diff --git a/lib/current-profile.ts b/lib/current-profile.ts index d12d04b..618258e 100644 --- a/lib/current-profile.ts +++ b/lib/current-profile.ts @@ -1,10 +1,9 @@ import { auth } from '@/auth' -import { ExtendedUser } from '@/types' import { db } from '@/lib/db' -const currentProfile = async () => { +export const currentProfile = async () => { const session = await auth() - const user = session?.user as ExtendedUser + const user = session?.user if (!user || !user.email) return null @@ -16,5 +15,3 @@ const currentProfile = async () => { return profile } - -export { currentProfile } diff --git a/middleware.ts b/middleware.ts index 21de42f..1d58994 100644 --- a/middleware.ts +++ b/middleware.ts @@ -1,37 +1,111 @@ -import { auth, signIn } from '@/auth' +import { NextResponse, type NextRequest } from 'next/server' +import { + ResponseCookies, + RequestCookies, +} from 'next/dist/server/web/spec-extension/cookies' +import { getToken, encode } from 'next-auth/jwt' + +import { auth } from '@/auth' +import { refreshAccessToken } from '@/lib/auth/refresh-access-token' import { apiAuthPrefix, authRoutes, defaultLoginRedirect, publicRoutes, } from '@/lib/auth/routes' -import type { ExtendedSession } from '@/types' -export default auth(req => { - const { nextUrl } = req - const isLoggedIn = !!req.auth +const sessionCookieName = + process.env.NODE_ENV === 'development' + ? 'authjs.session-token' + : '__Secure-authjs.session-token' - if ((req.auth as ExtendedSession)?.error === 'RefreshAccessTokenError') { - signIn('spotify') - } +export default auth(async request => { + const { nextUrl, auth } = request + const secret = process.env.NEXTAUTH_SECRET || '' + + const token = await getToken({ + req: request, + secret, + salt: sessionCookieName, + }) + + console.log('middleware token', { + refresh: token?.refresh_token, + access: token?.access_token, + }) const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix) const isAuthRoute = authRoutes.includes(nextUrl.pathname) const isPublicRoute = publicRoutes.includes(nextUrl.pathname) if (isApiAuthRoute) return - if (isAuthRoute) { - if (isLoggedIn) + if (auth?.user) { return Response.redirect(new URL(defaultLoginRedirect, nextUrl)) - return + } else { + return + } } - if (!isLoggedIn && !isPublicRoute) { - return Response.redirect(new URL('/auth/login', nextUrl)) + if (!isPublicRoute) { + if (!auth?.user) { + return Response.redirect(new URL('/auth/login', nextUrl)) + } + if (Date.now() < auth.user.expires_at) return + + console.log('token expired') + + const token = await getToken({ + req: request, + secret, + salt: sessionCookieName, + }) + if (!token) return Response.redirect(new URL('/auth/login', nextUrl)) + + const refreshedToken = await refreshAccessToken(token) + const encodedToken = await encode({ + ...refreshedToken, + secret, + salt: sessionCookieName, + }) + + console.log({ + refreshedToken, + }) + + const response = NextResponse.next() + response.cookies.set(sessionCookieName, encodedToken) + applySetCookie(request, response) + + return response } }) +/** + * Copy cookies from the Set-Cookie header of the response to the Cookie header of the request, + * so that it will appear to SSR/RSC as if the user already has the new cookies. + * https://github.com/vercel/next.js/issues/49442#issuecomment-1679807704 + */ +const applySetCookie = (req: NextRequest, res: NextResponse) => { + // parse the outgoing Set-Cookie header + const setCookies = new ResponseCookies(res.headers) + // Build a new Cookie header for the request by adding the setCookies + const newReqHeaders = new Headers(req.headers) + const newReqCookies = new RequestCookies(newReqHeaders) + setCookies.getAll().forEach(cookie => newReqCookies.set(cookie)) + // set “request header overrides” on the outgoing response + NextResponse.next({ + request: { headers: newReqHeaders }, + }).headers.forEach((value, key) => { + if ( + key === 'x-middleware-override-headers' || + key.startsWith('x-middleware-request-') + ) { + res.headers.set(key, value) + } + }) +} + export const config = { matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)'], } diff --git a/package-lock.json b/package-lock.json index 6287bd3..d1c8765 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "cmdk": "1.0.0", "framer-motion": "11.0.24", "next": "14.1.4", - "next-auth": "5.0.0-beta.16", + "next-auth": "5.0.0-beta.17", "next-safe-action": "6.2.0", "prettier": "3.2.5", "prettier-plugin-tailwindcss": "0.5.13", @@ -67,9 +67,9 @@ } }, "node_modules/@auth/core": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.28.1.tgz", - "integrity": "sha512-gvp74mypYZADpTlfGRp6HE0G3pIHWvtJpy+KZ+8FvY0cmlIpHog+jdMOdd29dQtLtN25kF2YbfHsesCFuGUQbg==", + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.30.0.tgz", + "integrity": "sha512-8AE4m/nk+4EIiVCJwxZAsJeAQuzpEC8M8768mmKVn60CGDdupKQkVhxbRlm5Qh7eNRCoFFME+0DvtaX2aXrYaA==", "dependencies": { "@panva/hkdf": "^1.1.1", "@types/cookie": "0.6.0", @@ -3468,9 +3468,9 @@ } }, "node_modules/jose": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.2.tgz", - "integrity": "sha512-/WByRr4jDcsKlvMd1dRJnPfS1GVO3WuKyaurJ/vvXcOaUQO8rnNObCQMlv/5uCceVQIq5Q4WLF44ohsdiTohdg==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.2.4.tgz", + "integrity": "sha512-6ScbIk2WWCeXkmzF6bRPmEuaqy1m8SbsRFMa/FLrSCkGIhj8OLVG/IH+XHVmNMx/KUo8cVWEE6oKR4dJ+S0Rkg==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -3807,11 +3807,11 @@ } }, "node_modules/next-auth": { - "version": "5.0.0-beta.16", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.16.tgz", - "integrity": "sha512-dX2snB+ezN23tFzSes3n3uosT9iBf0eILPYWH/R2fd9n3ZzdMQlRzq7JIOPeS1aLc84IuRlyuyXyx9XmmZB6og==", + "version": "5.0.0-beta.17", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.17.tgz", + "integrity": "sha512-XA/7JtAjOgDfAeotJPFUsFZGGItZwzZrxLt9Gc9fE7EchLk6zydZfuZ22Vvwixs3IilkN644D5IoD5tEOAFGCQ==", "dependencies": { - "@auth/core": "0.28.1" + "@auth/core": "0.30.0" }, "peerDependencies": { "@simplewebauthn/browser": "^9.0.1", @@ -4000,9 +4000,9 @@ } }, "node_modules/oauth4webapi": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.10.3.tgz", - "integrity": "sha512-9FkXEXfzVKzH63GUOZz1zMr3wBaICSzk6DLXx+CGdrQ10ItNk2ePWzYYc1fdmKq1ayGFb2aX97sRCoZ2s0mkDw==", + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.10.4.tgz", + "integrity": "sha512-DSoj8QoChzOCQlJkRmYxAJCIpnXFW32R0Uq7avyghIeB6iJq0XAblOD7pcq3mx4WEBDwMuKr0Y1qveCBleG2Xw==", "funding": { "url": "https://github.com/sponsors/panva" } diff --git a/types.ts b/types.ts index 271cf83..6b61c84 100644 --- a/types.ts +++ b/types.ts @@ -4,29 +4,16 @@ import type { Playlist, Artist, } from '@spotify/web-api-ts-sdk' -import { Session, User } from 'next-auth' -export type ExtendedUser = User & { - access_token: string - token_type: string - expires_at: number - expires_in: number - refresh_token: string -} - -export type ExtendedSession = Session & { error: string | undefined } +export type gamePlaylist = Artist | Playlist +export type gameTracks = (Track | PlaylistedTrack)[] -type gamePlaylist = Artist | Playlist -type gameTracks = (Track | PlaylistedTrack)[] - -type gameData = { +export type gameData = { playlist: gamePlaylist tracks: gameTracks } -type statistic = { +export type statistic = { metric: string value: string | number } - -export type { gamePlaylist, gameTracks, gameData, statistic } diff --git a/types/next-auth.d.ts b/types/next-auth.d.ts new file mode 100644 index 0000000..b92fdbe --- /dev/null +++ b/types/next-auth.d.ts @@ -0,0 +1,14 @@ +import type { Session, User } from 'next-auth' + +declare module 'next-auth' { + interface Session extends Session { + error: string | undefined + } + interface User extends User { + access_token: string + token_type: string + expires_at: number + expires_in: number + refresh_token: string + } +}