147 lines
3.9 KiB
TypeScript
147 lines
3.9 KiB
TypeScript
import type { NextAuthOptions } from 'next-auth'
|
||
import type { JWT } from 'next-auth/jwt'
|
||
import CredentialsProvider from 'next-auth/providers/credentials'
|
||
import GitHubProvider from 'next-auth/providers/github'
|
||
import GoogleProvider from 'next-auth/providers/google'
|
||
import {
|
||
fetchRefreshedBackendJwt,
|
||
getJwtExpMs,
|
||
shouldRefreshBackendToken,
|
||
} from '@/lib/backend-jwt-refresh'
|
||
|
||
const API_BASE = process.env.API_BASE_URL ?? 'http://localhost:8080'
|
||
|
||
type AuthUser = {
|
||
id: string
|
||
email: string
|
||
username: string
|
||
accessToken: string
|
||
refreshToken: string
|
||
accessTokenExpires: number
|
||
}
|
||
|
||
async function refreshAccessToken(token: JWT): Promise<JWT> {
|
||
const next = await fetchRefreshedBackendJwt(token)
|
||
if (!next) {
|
||
return {
|
||
...token,
|
||
error: 'RefreshAccessTokenError',
|
||
}
|
||
}
|
||
return next
|
||
}
|
||
|
||
export const authOptions: NextAuthOptions = {
|
||
session: {
|
||
strategy: 'jwt',
|
||
/** NextAuth oturum çerezi ömrü (saniye) — backend refresh ile uyumlu uzun süre */
|
||
maxAge: 60 * 60 * 24 * 7,
|
||
},
|
||
secret: process.env.NEXTAUTH_SECRET ?? process.env.AUTH_SECRET,
|
||
providers: [
|
||
...(process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
||
? [
|
||
GoogleProvider({
|
||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||
}),
|
||
]
|
||
: []),
|
||
...(process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET
|
||
? [
|
||
GitHubProvider({
|
||
clientId: process.env.GITHUB_CLIENT_ID,
|
||
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
||
}),
|
||
]
|
||
: []),
|
||
CredentialsProvider({
|
||
name: 'Credentials',
|
||
credentials: {
|
||
email: { label: 'Email', type: 'email' },
|
||
password: { label: 'Password', type: 'password' },
|
||
},
|
||
async authorize(credentials) {
|
||
const email = credentials?.email
|
||
const password = credentials?.password
|
||
if (!email || !password) return null
|
||
|
||
const res = await fetch(`${API_BASE}/api/v1/auth/login`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ email, password }),
|
||
})
|
||
|
||
if (!res.ok) return null
|
||
|
||
const data = (await res.json()) as { access: string; refresh: string }
|
||
const accessToken = data.access
|
||
const refreshToken = data.refresh
|
||
|
||
return {
|
||
id: email,
|
||
email,
|
||
username: email,
|
||
accessToken,
|
||
refreshToken,
|
||
accessTokenExpires: getJwtExpMs(accessToken),
|
||
} satisfies AuthUser
|
||
},
|
||
}),
|
||
],
|
||
callbacks: {
|
||
async jwt({ token, user }) {
|
||
if (user) {
|
||
const authUser = user as AuthUser
|
||
|
||
// OAuth login akışında backend access/refresh token'ı yoksa refresh denemeyelim.
|
||
if (!authUser.accessToken || !authUser.refreshToken) {
|
||
return {
|
||
...token,
|
||
user: {
|
||
id: authUser.id,
|
||
email: authUser.email,
|
||
username: authUser.username ?? authUser.email,
|
||
},
|
||
}
|
||
}
|
||
|
||
return {
|
||
...token,
|
||
accessToken: authUser.accessToken,
|
||
refreshToken: authUser.refreshToken,
|
||
accessTokenExpires: authUser.accessTokenExpires,
|
||
user: {
|
||
id: authUser.id,
|
||
email: authUser.email,
|
||
username: authUser.username,
|
||
},
|
||
}
|
||
}
|
||
|
||
if (token.error === 'RefreshAccessTokenError') {
|
||
return token
|
||
}
|
||
|
||
if (!shouldRefreshBackendToken(token)) {
|
||
return token
|
||
}
|
||
|
||
return refreshAccessToken(token)
|
||
},
|
||
async session({ session, token }) {
|
||
session.user = {
|
||
...(session.user ?? {}),
|
||
...(token.user ?? {}),
|
||
}
|
||
session.accessToken = token.accessToken
|
||
session.refreshToken = token.refreshToken
|
||
session.error = token.error
|
||
return session
|
||
},
|
||
},
|
||
pages: {
|
||
signIn: '/auth/login',
|
||
},
|
||
}
|