import { 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 } accessToken?: string refreshToken?: string expiresAt?: number } interface User { id: string email: string name?: string accessToken?: string refreshToken?: string } interface JWT { accessToken?: string refreshToken?: string expiresAt?: number error?: string } } interface GoogleToken extends JWT { accessToken?: string refreshToken?: string expiresAt?: number | undefined error?: string } async function refreshAccessToken(token: GoogleToken): Promise { try { const BACKEND = process.env.BACKEND_URL || 'http://127.0.0.1:8000/api/v1' const response = await fetch(`${BACKEND}/auth/refresh/`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ refresh: token.refreshToken, }), }) if (!response.ok) { const errorText = await response.text() let errorData: { error?: string; [key: string]: unknown } = {} try { errorData = JSON.parse(errorText) } catch { errorData = { error: errorText } } throw new Error(errorData.error || 'Token refresh failed') } 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', } } } export const authOptions: NextAuthOptions = { providers: [ //логин CredentialsProvider({ id: 'credentials', name: 'Credentials', credentials: { login: { label: 'Login', type: 'text' }, password: { label: 'Password', type: 'password' }, role: { label: 'Role', type: 'text' }, }, async authorize(credentials) { try { const BACKEND = process.env.BACKEND_URL || 'http://127.0.0.1:8000/api/v1' const res = await fetch(`${BACKEND}/auth/login/`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ login: credentials?.login, password: credentials?.password, role: credentials?.role, }), }) const raw = await res.text() let data: { error?: string user?: { id: string | number email: string name: string role: string } access?: string refresh?: string [key: string]: unknown } try { data = JSON.parse(raw) } catch { data = { error: raw } } if (!res.ok) { throw new Error(data.error || `Authentication failed (${res.status})`) } return { id: data.user?.id?.toString?.() ?? String(data.user?.id), email: data.user?.email ?? '', name: data.user?.name ?? '', // backend uses `name`, not `firstName` accessToken: data.access ?? '', refreshToken: data.refresh ?? '', } } catch (error) { console.error('Login error:', error) return null } }, }), ], callbacks: { async jwt({ token, user, account }) { if (user && account) { if (account.type === 'credentials') { return { ...token, accessToken: user.accessToken, refreshToken: user.refreshToken, expiresAt: Math.floor(Date.now() / 1000) + 15 * 60, // 15 минут } } else { return { ...token, accessToken: account.access_token, refreshToken: account.refresh_token, expiresAt: account.expires_at, } } } // проверяем не истекает ли токен в ближайшие 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 }) { if (token) { session.accessToken = token.accessToken as string session.refreshToken = token.refreshToken as string } return session }, }, }