import NextAuth, { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import GitHubProvider from "next-auth/providers/github"; import GoogleProvider from "next-auth/providers/google"; import { JWT } from "next-auth/jwt"; // Helper to get API URL consistently const API_URL = process.env.NEXT_PUBLIC_API_URL || process.env.BASE_API_URL || "http://localhost:8080/api"; /** * Refresh token ile yeni access token al */ async function refreshAccessToken(token: JWT) { try { const response = await fetch(`${API_URL}/api/v1/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ refresh_token: token.refreshToken, }), }); if (!response.ok) { throw new Error("Failed to refresh token"); } const refreshedTokens = await response.json(); return { ...token, accessToken: refreshedTokens.access_token, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, accessTokenExpires: Date.now() + 15 * 60 * 1000, // 15 dakika (Time should ideally come from backend) }; } catch (error) { console.error("Error refreshing access token:", error); return { ...token, error: "RefreshAccessTokenError", }; } } const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, // Optional: used if redirecting from a separate auth flow accessToken: { label: "Access Token", type: "text" }, refreshToken: { label: "Refresh Token", type: "text" }, }, async authorize(credentials) { // 1. External Token Flow (if tokens are passed directly, e.g. from OAuth on backend) if (credentials?.accessToken && credentials?.refreshToken) { try { // Validate token and get user info const meResponse = await fetch(`${API_URL}/api/v1/auth/me`, { headers: { "Authorization": `Bearer ${credentials.accessToken}`, }, }); if (!meResponse.ok) return null; const userData = await meResponse.json(); return { id: userData.id?.toString(), email: userData.email, name: userData.username, username: userData.username, // Added to satisfy User interface is_admin: userData.is_admin, // Capture is_admin accessToken: credentials.accessToken, refreshToken: credentials.refreshToken, accessTokenExpires: Date.now() + 15 * 60 * 1000, }; } catch { return null; } } // 2. Standard Email/Password Flow if (!credentials?.email || !credentials?.password) { return null; } try { const response = await fetch(`${API_URL}/api/v1/auth/login`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ email: credentials.email, password: credentials.password, }), }); if (!response.ok) { return null; } const data = await response.json(); // Structure matches user's provided JSON example return { id: data.user.id.toString(), email: data.user.email, name: data.user.username, username: data.user.username, // Added to satisfy User interface is_admin: data.user.is_admin, // Capture is_admin accessToken: data.access_token, refreshToken: data.refresh_token, accessTokenExpires: Date.now() + 15 * 60 * 1000, }; } catch (error) { console.error("Login error:", error); return null; } }, }), // Keep existing providers if they are configured GitHubProvider({ clientId: process.env.GITHUB_CLIENT_ID || "", clientSecret: process.env.GITHUB_CLIENT_SECRET || "", }), GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID || "", clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", }), ], pages: { signIn: "/auth/login", signOut: "/auth/login", error: "/auth/login", }, callbacks: { async jwt({ token, user }) { // Initial sign in if (user) { token.id = user.id; token.email = user.email; token.name = user.name || undefined; token.username = user.username; token.accessToken = user.accessToken; token.refreshToken = user.refreshToken; token.roles = user.roles; token.is_admin = user.is_admin; token.accessTokenExpires = user.accessTokenExpires; } // Return previous token if the access token has not expired yet if (Date.now() < (token.accessTokenExpires as number)) { return token; } // Access token has expired, try to update it return refreshAccessToken(token); }, async session({ session, token }) { if (token) { session.user.id = token.id as string; session.user.email = token.email as string; session.user.name = token.name as string; session.user.username = token.username as string; session.user.is_admin = token.is_admin as boolean; // Expose is_admin to session session.accessToken = token.accessToken as string; session.user.accessToken = token.accessToken as string; session.error = token.error as string; } return session; }, }, session: { strategy: "jwt", }, secret: process.env.NEXTAUTH_SECRET, // Ensure this matches .env }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };