188 lines
7.1 KiB
TypeScript
188 lines
7.1 KiB
TypeScript
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 };
|