route handler + client register ui

This commit is contained in:
2025-05-17 12:07:19 +03:00
parent a867699855
commit 48d286cf55
12 changed files with 655 additions and 29 deletions

View File

@@ -0,0 +1,352 @@
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<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 }