diff --git a/auth.ts b/auth.ts index e7b929e..f569573 100644 --- a/auth.ts +++ b/auth.ts @@ -1,45 +1,79 @@ -import NextAuth,{ type DefaultSession } from "next-auth"; +import NextAuth, { type DefaultSession } from "next-auth"; import Authentik from "next-auth/providers/authentik"; import { PrismaAdapter } from "@auth/prisma-adapter" import { PrismaClient } from "@prisma/client" +import { JWT } from "next-auth/jwt" const prisma = new PrismaClient() declare module "next-auth" { - interface Session { - access_token: string | undefined; - } + interface Session { + access_token: string | undefined; + error?: "RefreshAccessTokenError" + } } - + +declare module "next-auth/jwt" { + interface JWT { + access_token: string + expires_at: number + refresh_token: string + error?: "RefreshAccessTokenError" + } +} + export const { handlers, auth, signIn, signOut } = NextAuth({ - adapter: PrismaAdapter(prisma), - providers: [Authentik({ - clientId: process.env.AUTH_OIDC_CLIENT_ID, - clientSecret: process.env.AUTH_OIDC_CLIENT_SECRET, - issuer: process.env.AUTH_OIDC_ISSUER, - authorization: { params: { scope: 'openid profile email offline_access' } }, - })], - callbacks: { - async jwt({token, account}) { - if (account) { - token = Object.assign({}, token, { access_token: account.access_token }); - } - return token - }, - async session({session, user}) { - const getToken = await prisma.account.findFirst({ - where: { - userId: user.id, - }, - }); + adapter: PrismaAdapter(prisma), + providers: [Authentik({ + clientId: process.env.AUTH_OIDC_CLIENT_ID, + clientSecret: process.env.AUTH_OIDC_CLIENT_SECRET, + issuer: process.env.AUTH_OIDC_ISSUER, + authorization: { params: { scope: 'openid profile email offline_access' } }, + })], + callbacks: { + async session({ session, user }) { + const [authentikAccount] = await prisma.account.findMany({ + where: { userId: user.id, provider: "authentik" }, + }) - let accessToken: string | undefined = undefined; - if (getToken) { - accessToken = getToken.access_token!; - } + if (authentikAccount.expires_at * 1000 < Date.now()) { + console.info("refreshing token"); + try { + const response = await fetch(process.env.AUTH_OIDC_TOKEN_URL!, { + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + client_id: process.env.AUTH_OIDC_CLIENT_ID!, + client_secret: process.env.AUTH_OIDC_CLIENT_SECRET!, + grant_type: "refresh_token", + refresh_token: authentikAccount.refresh_token, + }), + method: "POST", + }) - session.access_token = accessToken; - return session; - } - } + const tokens = await response.json() + + if (!response.ok) throw tokens + await prisma.account.update({ + data: { + access_token: tokens.access_token, + expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in), + refresh_token: + tokens.refresh_token ?? authentikAccount.refresh_token, + }, + where: { + provider_providerAccountId: { + provider: "authentik", + providerAccountId: authentikAccount.providerAccountId, + }, + }, + }) + } catch (error) { + console.error("Error refreshing access token", error) + // The error property will be used client-side to handle the refresh token error + session.error = "RefreshAccessTokenError" + } + } + return session + } + } });