|
1 | 1 | import prisma from "@/lib/prisma/prisma";
|
2 | 2 | import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
3 |
| -import NextAuth, { SessionStrategy } from "next-auth"; |
| 3 | +import NextAuth, { Session, TokenSet, User } from "next-auth"; |
4 | 4 | import GoogleProvider from "next-auth/providers/google";
|
5 | 5 |
|
6 | 6 | declare module "next-auth" {
|
@@ -31,15 +31,82 @@ export const getAuthOptions = () => {
|
31 | 31 | pages: {
|
32 | 32 | signIn: "/auth/signin",
|
33 | 33 | },
|
34 |
| - session: { |
35 |
| - strategy: "jwt" as SessionStrategy, |
36 |
| - }, |
37 |
| - jwt: { |
38 |
| - // The maximum age of the NextAuth.js issued JWT in seconds. |
39 |
| - // Defaults to `session.maxAge`. |
40 |
| - maxAge: 60 * 60 * 24 * 30, |
41 |
| - }, |
42 | 34 | adapter: PrismaAdapter(prisma),
|
| 35 | + callbacks: { |
| 36 | + async session({ session, user }: { session: Session; user: User }) { |
| 37 | + const [google] = await prisma.account.findMany({ |
| 38 | + where: { userId: user.id, provider: "google" }, |
| 39 | + }); |
| 40 | + if ( |
| 41 | + google.expires_at !== null && |
| 42 | + google.refresh_token !== null && |
| 43 | + google.expires_at * 1000 < Date.now() |
| 44 | + ) { |
| 45 | + // If the access token has expired, try to refresh it |
| 46 | + try { |
| 47 | + const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET; |
| 48 | + const googleClientId = process.env.GOOGLE_CLIENT_ID; |
| 49 | + if ( |
| 50 | + googleClientSecret === undefined || |
| 51 | + googleClientId === undefined |
| 52 | + ) { |
| 53 | + throw new Error("Missing Google client secret or client id"); |
| 54 | + } |
| 55 | + // https://accounts.google.com/.well-known/openid-configuration |
| 56 | + // We need the `token_endpoint`. |
| 57 | + const response = await fetch( |
| 58 | + "https://oauth2.googleapis.com/token", |
| 59 | + { |
| 60 | + headers: { |
| 61 | + "Content-Type": "application/x-www-form-urlencoded", |
| 62 | + }, |
| 63 | + body: new URLSearchParams({ |
| 64 | + client_id: googleClientId, |
| 65 | + client_secret: googleClientSecret, |
| 66 | + grant_type: "refresh_token", |
| 67 | + refresh_token: google.refresh_token, |
| 68 | + }), |
| 69 | + method: "POST", |
| 70 | + } |
| 71 | + ); |
| 72 | + |
| 73 | + const tokens: TokenSet = (await response.json()) as TokenSet; |
| 74 | + |
| 75 | + if (!response.ok) throw tokens; |
| 76 | + |
| 77 | + if ( |
| 78 | + tokens.expires_in === undefined || |
| 79 | + tokens.expires_in === null || |
| 80 | + typeof tokens.expires_in !== "number" |
| 81 | + ) { |
| 82 | + throw new Error("Missing expires_in"); |
| 83 | + } |
| 84 | + const newExpiresAt = Math.floor( |
| 85 | + Date.now() / 1000 + tokens.expires_in |
| 86 | + ); |
| 87 | + |
| 88 | + await prisma.account.update({ |
| 89 | + data: { |
| 90 | + access_token: tokens.access_token, |
| 91 | + expires_at: newExpiresAt, |
| 92 | + refresh_token: tokens.refresh_token ?? google.refresh_token, |
| 93 | + }, |
| 94 | + where: { |
| 95 | + provider_providerAccountId: { |
| 96 | + provider: "google", |
| 97 | + providerAccountId: google.providerAccountId, |
| 98 | + }, |
| 99 | + }, |
| 100 | + }); |
| 101 | + } catch (error) { |
| 102 | + console.error("Error refreshing access token", error); |
| 103 | + // The error property will be used client-side to handle the refresh token error |
| 104 | + session.error = "RefreshAccessTokenError"; |
| 105 | + } |
| 106 | + } |
| 107 | + return session; |
| 108 | + }, |
| 109 | + }, |
43 | 110 | };
|
44 | 111 | return options;
|
45 | 112 | };
|
|
0 commit comments