import NextAuth, { NextAuthOptions } from 'next-auth' import CredentialsProvider from 'next-auth/providers/credentials' import { JWT } from 'next-auth/jwt' declare module 'next-auth' { interface Session { user: { name?: string | null surname?: string | null email?: string | null phone_number?: string | null image?: string | null userType?: string | null } accessToken?: string refreshToken?: string expiresAt?: number } interface User { id: string email: string name?: string accessToken?: string refreshToken?: string userType?: string } interface JWT { accessToken?: string refreshToken?: string expiresAt?: number error?: string } } interface GoogleToken extends JWT { accessToken?: string refreshToken?: string expiresAt?: number | undefined error?: string } export const authOptions: NextAuthOptions = { providers: [ //google login flow // GoogleProvider({ // clientId: process.env.GOOGLE_CLIENT_ID!, // clientSecret: process.env.GOOGLE_CLIENT_SECRET!, // }), //регистрация клиента CredentialsProvider({ id: 'register-credentials', name: 'Register', credentials: { name: { label: 'Name', type: 'text' }, surname: { label: 'Surname', type: 'text' }, email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, phone_number: { label: 'Phone Number', type: 'tel' }, privacy_accepted: { label: 'Privacy Accepted', type: 'boolean' }, }, async authorize(credentials) { try { if ( !credentials?.email || !credentials?.password || !credentials?.name || !credentials?.phone_number || !credentials?.privacy_accepted || !credentials?.surname ) { throw new Error('Все поля обязательны для заполнения') } const res = await fetch(`${process.env.BACKEND_URL}/register/clients/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: credentials.email, password: credentials.password, name: credentials.name, surname: credentials.surname, phone_number: credentials.phone_number, privacy_accepted: credentials.privacy_accepted === 'true', }), }) const data = await res.json() if (!res.ok) { console.error('Registration error response:', data) const errorMessage = typeof data === 'object' ? data.error || Object.values(data).flat().join(', ') : 'Registration failed' throw new Error(errorMessage) } return { id: data.user.id.toString(), email: data.user.email, name: data.user.name, surname: data.user.surname, accessToken: data.access, refreshToken: data.refresh, } } catch (error) { console.error('Registration error:', error) throw error } }, }), //логин CredentialsProvider({ name: 'Credentials', credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, }, async authorize(credentials) { try { const res = await fetch(`${process.env.BACKEND_URL}/auth/login/clients/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: credentials?.email, password: credentials?.password, }), }) const data = await res.json() if (!res.ok) { throw new Error(data.error || 'Authentication failed') } return { id: data.user.id.toString(), email: data.user.email, name: data.user.firstName, accessToken: data.access, refreshToken: data.refresh, } } catch (error) { console.error('Login error:', error) return null } }, }), ], callbacks: { async jwt({ token, user, account }) { // console.log('JWT Callback - User:', user?.userType) // console.log('JWT Callback - Account:', account?.type) if (user && account) { if (account.type === 'credentials') { // console.log('Adding userType to token:', user.userType) return { ...token, accessToken: user.accessToken, refreshToken: user.refreshToken, expiresAt: Math.floor(Date.now() / 1000) + 15 * 60, // 15 минут userType: user.userType, } } else { return { ...token, accessToken: account.access_token, refreshToken: account.refresh_token, expiresAt: account.expires_at, userType: user.userType, } } } // проверяем не истекает ли токен в ближайшие 5 минут const expiresAt = token.expiresAt as number | undefined if ( typeof expiresAt === 'number' && Date.now() < (expiresAt - 5 * 60) * 1000 // обновляем за 5 минут до истечения ) { return token } return refreshAccessToken(token as GoogleToken) }, async session({ session, token }) { // console.log('Session Callback - Token:', token) // console.log('Session Callback - userType:', token.userType) if (token) { session.user.userType = token.userType as string session.accessToken = token.accessToken as string session.refreshToken = token.refreshToken as string // console.log('Session Callback - Final session:', session) } return session }, async signIn({ account }) { if (account?.access_token && typeof account.access_token === 'string') { try { const res = await fetch(`${process.env.BACKEND_URL}/auth/google/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: account.id_token }), }) if (!res.ok) { console.error('Failed to authenticate with Django') return false } const djangoTokens = await res.json() // сторим токены бека, а не гугла account.access_token = djangoTokens.access account.refresh_token = djangoTokens.refresh account.expires_at = Math.floor(Date.now() / 1000) + 5 * 60 // 5 минут return true } catch (error) { console.error('Error sending token to backend:', error) return false } } return true }, }, } async function refreshAccessToken(token: GoogleToken): Promise { try { const response = await fetch(`${process.env.BACKEND_URL}/auth/refresh/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh: token.refreshToken, }), }) if (!response.ok) { const errorData = await response.json() throw errorData } const refreshedTokens = await response.json() return { ...token, accessToken: refreshedTokens.access, refreshToken: refreshedTokens.refresh ?? token.refreshToken, expiresAt: refreshedTokens.expires_at, } } catch (error) { console.error('Refresh error:', error) return { ...token, error: 'RefreshAccessTokenError', } } } const handler = NextAuth(authOptions) export { handler as GET, handler as POST }