Files
tripwithbonus/frontend/app/api/auth/[...nextauth]/route.ts

279 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
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<GoogleToken> {
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 }