import NextAuth, { NextAuthOptions } from 'next-auth' import GoogleProvider from 'next-auth/providers/google' 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 } 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: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, name: { label: 'Name', type: 'text' }, phone_number: { label: 'Phone Number', type: 'tel' }, privacy_accepted: { label: 'Privacy Accepted', type: 'boolean' }, }, async authorize(credentials) { try { 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, phone_number: credentials?.phone_number, privacy_accepted: credentials?.privacy_accepted === 'true', }), } ) const data = await res.json() if (!res.ok) { throw new Error( data.error || data.details?.toString() || 'Registration 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('Registration error:', error) return null } }, }), //регистрация менеджера CredentialsProvider({ id: 'register-credentials-manager', name: 'RegisterManager', credentials: { email: { label: 'Email', type: 'email' }, password: { label: 'Password', type: 'password' }, username: { label: 'Username', type: 'text' }, }, async authorize(credentials) { try { const res = await fetch( `${process.env.BACKEND_URL}/register/business/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: credentials?.email, password: credentials?.password, username: credentials?.username, }), } ) if (!res.ok) { const text = await res.text() console.error('Backend error response:', text) throw new Error('Registration failed: ' + text) } const data = await res.json() return { id: data.user.id.toString(), email: data.user.email, name: data.user.firstName, accessToken: data.access, refreshToken: data.refresh, userType: data.user.userType || 'manager', } } catch (error) { console.error('Registration error:', error) return null } }, }), //логин обычный 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 } }, }), //логин для менеджеров CredentialsProvider({ id: 'login-credentials-manager', 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/business/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: credentials?.email, password: credentials?.password, }), } ) const data = await res.json() // console.log('Business login response:', data) if (!res.ok) { throw new Error(data.error || 'Authentication failed') } return { id: data.user.id.toString(), email: data.user.email || `${data.user.phone_number}@example.com`, name: data.user.username || data.user.title, accessToken: data.access, refreshToken: data.refresh, userType: data.user.userType || 'manager', } } 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 }