diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts index ae2dd87..dd25a2a 100644 --- a/app/api/auth/[...nextauth]/route.ts +++ b/app/api/auth/[...nextauth]/route.ts @@ -4,3 +4,16 @@ import { authOptions } from "@/lib/auth"; const handler = NextAuth(authOptions); export { handler as GET, handler as POST }; +interface JWT { + accessToken: string; + refreshToken: string; + accessTokenExpires: number; +} + +interface Profile { + sub?: string; + email?: string; + name?: string; + roles?: string[]; +} + diff --git a/app/api/calendars/route.ts b/app/api/calendars/route.ts index 3449365..fcbebfe 100644 --- a/app/api/calendars/route.ts +++ b/app/api/calendars/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; +import { authOptions } from "@/lib/auth"; import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { prisma } from "@/lib/prisma"; /** diff --git a/app/api/leantime/tasks/route.ts b/app/api/leantime/tasks/route.ts index 49667b5..2da74af 100644 --- a/app/api/leantime/tasks/route.ts +++ b/app/api/leantime/tasks/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { authOptions } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; // Cache for Leantime user IDs diff --git a/app/api/rocket-chat/messages/route.ts b/app/api/rocket-chat/messages/route.ts index f809835..94a3c85 100644 --- a/app/api/rocket-chat/messages/route.ts +++ b/app/api/rocket-chat/messages/route.ts @@ -1,5 +1,5 @@ import { getServerSession } from "next-auth/next"; -import { authOptions } from "@/app/api/auth/[...nextauth]/route"; +import { authOptions } from "@/lib/auth"; import { NextResponse } from "next/server"; // Helper function to get user token using admin credentials diff --git a/lib/auth.ts b/lib/auth.ts index a46fe0f..e2b7261 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -18,7 +18,7 @@ declare module 'next-auth' { } interface Profile { sub: string; - email?: string; + email: string; name?: string; roles?: string[]; } @@ -30,9 +30,9 @@ declare module 'next-auth/jwt' { email: string; name?: string; role: string[]; - accessToken?: string; - refreshToken?: string; - accessTokenExpires?: number; + accessToken: string; + refreshToken: string; + accessTokenExpires: number; error?: string; } } @@ -56,21 +56,31 @@ export const authOptions: NextAuthOptions = { callbacks: { async jwt({ token, account, profile }) { if (account && profile) { + if (!profile.sub) { + throw new Error('No user ID (sub) provided by Keycloak'); + } + if (!account.access_token || !account.refresh_token || !account.expires_at) { + throw new Error('Missing required token fields from Keycloak'); + } token.id = profile.sub; token.email = profile.email || ''; token.name = profile.name; token.role = profile.roles || ['user']; token.accessToken = account.access_token; token.refreshToken = account.refresh_token; - token.accessTokenExpires = account.expires_at! * 1000; + token.accessTokenExpires = account.expires_at * 1000; } // Return previous token if not expired - if (Date.now() < (token.accessTokenExpires as number)) { + if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) { return token; } // Token expired, try to refresh + if (!token.refreshToken) { + throw new Error('No refresh token available'); + } + try { const response = await fetch( `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`,