import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import axios from '../../axios'; // πŸ”Ή Π”Π΅ΠΊΠΎΠ΄ΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ JWT function decodeJwt(token: string) { const [, payload] = token.split('.'); const json = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); return JSON.parse(decodeURIComponent(escape(json))); } // πŸ”Ή Π’ΠΈΠΏΡ‹ interface AuthState { jwt: string | null; refreshToken: string | null; username: string | null; email: string | null; id: string | null; status: 'idle' | 'loading' | 'successful' | 'failed'; error: string | null; } // πŸ”Ή Π˜Π½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡ состояния с синхронной Π·Π°Π³Ρ€ΡƒΠ·ΠΊΠΎΠΉ ΠΈΠ· localStorage const jwtFromStorage = localStorage.getItem('jwt'); const refreshTokenFromStorage = localStorage.getItem('refreshToken'); const initialState: AuthState = { jwt: jwtFromStorage || null, refreshToken: refreshTokenFromStorage || null, username: null, email: null, id: null, status: 'idle', error: null, }; // Если Ρ‚ΠΎΠΊΠ΅Π½ Π΅ΡΡ‚ΡŒ, подставляСм Π² axios ΠΈ Π΄Π΅ΠΊΠΎΠ΄ΠΈΡ€ΡƒΠ΅ΠΌ if (jwtFromStorage) { axios.defaults.headers.common['Authorization'] = `Bearer ${jwtFromStorage}`; try { const decoded = decodeJwt(jwtFromStorage); initialState.username = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' ] || null; initialState.email = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' ] || null; initialState.id = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' ] || null; } catch { localStorage.removeItem('jwt'); localStorage.removeItem('refreshToken'); delete axios.defaults.headers.common['Authorization']; } } // πŸ”Ή AsyncThunk-Ρ‹ (login/register/refresh/whoami) ΠΎΡΡ‚Π°ΡŽΡ‚ΡΡ ΠΊΠ°ΠΊ Π±Ρ‹Π»ΠΈ export const registerUser = createAsyncThunk( 'auth/register', async ( { username, email, password, }: { username: string; email: string; password: string }, { rejectWithValue }, ) => { try { const response = await axios.post('/authentication/register', { username, email, password, }); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Registration failed', ); } }, ); export const loginUser = createAsyncThunk( 'auth/login', async ( { username, password }: { username: string; password: string }, { rejectWithValue }, ) => { try { const response = await axios.post('/authentication/login', { username, password, }); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Login failed', ); } }, ); export const refreshToken = createAsyncThunk( 'auth/refresh', async ({ refreshToken }: { refreshToken: string }, { rejectWithValue }) => { try { const response = await axios.post('/authentication/refresh', { refreshToken, }); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Refresh token failed', ); } }, ); export const fetchWhoAmI = createAsyncThunk( 'auth/whoami', async (_, { rejectWithValue }) => { try { const response = await axios.get('/authentication/whoami'); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Failed to fetch user info', ); } }, ); // πŸ”Ή Slice const authSlice = createSlice({ name: 'auth', initialState, reducers: { logout: (state) => { state.jwt = null; state.refreshToken = null; state.username = null; state.email = null; state.id = null; state.status = 'idle'; state.error = null; localStorage.removeItem('jwt'); localStorage.removeItem('refreshToken'); delete axios.defaults.headers.common['Authorization']; }, }, extraReducers: (builder) => { // ----------------- Register ----------------- builder.addCase(registerUser.pending, (state) => { state.status = 'loading'; state.error = null; }); builder.addCase(registerUser.fulfilled, (state, action) => { state.status = 'successful'; state.jwt = action.payload.jwt; state.refreshToken = action.payload.refreshToken; const decoded = decodeJwt(action.payload.jwt); state.username = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' ] || null; state.email = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' ] || null; state.id = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' ] || null; axios.defaults.headers.common[ 'Authorization' ] = `Bearer ${action.payload.jwt}`; localStorage.setItem('jwt', action.payload.jwt); localStorage.setItem('refreshToken', action.payload.refreshToken); }); builder.addCase(registerUser.rejected, (state, action) => { state.status = 'failed'; state.error = action.payload as string; }); // ----------------- Login ----------------- builder.addCase(loginUser.pending, (state) => { state.status = 'loading'; state.error = null; }); builder.addCase(loginUser.fulfilled, (state, action) => { state.status = 'successful'; state.jwt = action.payload.jwt; state.refreshToken = action.payload.refreshToken; const decoded = decodeJwt(action.payload.jwt); state.username = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' ] || null; state.email = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' ] || null; state.id = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' ] || null; axios.defaults.headers.common[ 'Authorization' ] = `Bearer ${action.payload.jwt}`; localStorage.setItem('jwt', action.payload.jwt); localStorage.setItem('refreshToken', action.payload.refreshToken); }); builder.addCase(loginUser.rejected, (state, action) => { state.status = 'failed'; state.error = action.payload as string; }); // ----------------- Refresh ----------------- builder.addCase(refreshToken.pending, (state) => { state.status = 'loading'; state.error = null; }); builder.addCase(refreshToken.fulfilled, (state, action) => { state.status = 'successful'; state.jwt = action.payload.jwt; state.refreshToken = action.payload.refreshToken; const decoded = decodeJwt(action.payload.jwt); state.username = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name' ] || null; state.email = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress' ] || null; state.id = decoded[ 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier' ] || null; axios.defaults.headers.common[ 'Authorization' ] = `Bearer ${action.payload.jwt}`; localStorage.setItem('jwt', action.payload.jwt); localStorage.setItem('refreshToken', action.payload.refreshToken); }); builder.addCase(refreshToken.rejected, (state, action) => { state.status = 'failed'; state.error = action.payload as string; }); // ----------------- WhoAmI ----------------- builder.addCase(fetchWhoAmI.pending, (state) => { state.status = 'loading'; state.error = null; }); builder.addCase(fetchWhoAmI.fulfilled, (state, action) => { state.status = 'successful'; state.username = action.payload.username; }); builder.addCase(fetchWhoAmI.rejected, (state, action) => { state.status = 'failed'; state.error = action.payload as string; }); }, }); export const { logout } = authSlice.actions; export const authReducer = authSlice.reducer;