import NextAuth 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; 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 session({ session, user }) { const [authentikAccount] = await prisma.account.findMany({ where: { userId: user.id, provider: "authentik" }, }) let newAccessToken; if (!authentikAccount.expires_at || !authentikAccount.refresh_token) { throw "Expiry time or refresh token not set"; } 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", }) const tokens = await response.json() if (!response.ok) throw tokens newAccessToken = tokens.access_token; 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, access_token: newAccessToken ?? authentikAccount.access_token } } } });