feat: Enhance LoginComponent and SocketManager for improved session management and reauthentication
This commit is contained in:
@@ -28,6 +28,27 @@ import GoogleIcon from '@mui/icons-material/Google';
|
|||||||
// Lazy load GoogleAuthProvider
|
// Lazy load GoogleAuthProvider
|
||||||
const GoogleAuthProvider = lazy(() => import('../providers/GoogleAuthProvider.js'));
|
const GoogleAuthProvider = lazy(() => import('../providers/GoogleAuthProvider.js'));
|
||||||
|
|
||||||
|
const getTokenFromAuthResponse = (response) =>
|
||||||
|
response?.token ||
|
||||||
|
response?.accessToken ||
|
||||||
|
response?.jwt ||
|
||||||
|
response?.user?.token ||
|
||||||
|
response?.user?.accessToken ||
|
||||||
|
null;
|
||||||
|
|
||||||
|
const persistSessionAuth = (response) => {
|
||||||
|
if (response?.user) {
|
||||||
|
sessionStorage.setItem('user', JSON.stringify(response.user));
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = getTokenFromAuthResponse(response);
|
||||||
|
if (token) {
|
||||||
|
sessionStorage.setItem('authToken', token);
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem('authToken');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Function to check if user is logged in
|
// Function to check if user is logged in
|
||||||
export const isUserLoggedIn = () => {
|
export const isUserLoggedIn = () => {
|
||||||
const storedUser = sessionStorage.getItem('user');
|
const storedUser = sessionStorage.getItem('user');
|
||||||
@@ -77,6 +98,7 @@ function cartsAreIdentical(cartA, cartB) {
|
|||||||
export class LoginComponent extends Component {
|
export class LoginComponent extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const { isLoggedIn, user, isAdmin } = isUserLoggedIn();
|
||||||
this.state = {
|
this.state = {
|
||||||
open: false,
|
open: false,
|
||||||
tabValue: 0,
|
tabValue: 0,
|
||||||
@@ -86,9 +108,9 @@ export class LoginComponent extends Component {
|
|||||||
error: '',
|
error: '',
|
||||||
loading: false,
|
loading: false,
|
||||||
success: '',
|
success: '',
|
||||||
isLoggedIn: false,
|
isLoggedIn,
|
||||||
isAdmin: false,
|
isAdmin,
|
||||||
user: null,
|
user,
|
||||||
anchorEl: null,
|
anchorEl: null,
|
||||||
showGoogleAuth: false,
|
showGoogleAuth: false,
|
||||||
cartSyncOpen: false,
|
cartSyncOpen: false,
|
||||||
@@ -102,16 +124,6 @@ export class LoginComponent extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Make the open function available globally
|
// Make the open function available globally
|
||||||
window.openLoginDrawer = this.handleOpen;
|
window.openLoginDrawer = this.handleOpen;
|
||||||
|
|
||||||
// Check if user is logged in
|
|
||||||
const { isLoggedIn: userIsLoggedIn, user: storedUser } = isUserLoggedIn();
|
|
||||||
if (userIsLoggedIn) {
|
|
||||||
this.setState({
|
|
||||||
user: storedUser,
|
|
||||||
isAdmin: !!storedUser.admin,
|
|
||||||
isLoggedIn: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.open) {
|
if (this.props.open) {
|
||||||
this.setState({ open: true });
|
this.setState({ open: true });
|
||||||
@@ -190,7 +202,7 @@ export class LoginComponent extends Component {
|
|||||||
window.socketManager.emit('verifyUser', { email, password }, (response) => {
|
window.socketManager.emit('verifyUser', { email, password }, (response) => {
|
||||||
console.log('LoginComponent: verifyUser', response);
|
console.log('LoginComponent: verifyUser', response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
sessionStorage.setItem('user', JSON.stringify(response.user));
|
persistSessionAuth(response);
|
||||||
this.setState({
|
this.setState({
|
||||||
user: response.user,
|
user: response.user,
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
@@ -306,6 +318,7 @@ export class LoginComponent extends Component {
|
|||||||
window.socketManager.emit('logout', (response) => {
|
window.socketManager.emit('logout', (response) => {
|
||||||
if(response.success){
|
if(response.success){
|
||||||
sessionStorage.removeItem('user');
|
sessionStorage.removeItem('user');
|
||||||
|
sessionStorage.removeItem('authToken');
|
||||||
window.dispatchEvent(new CustomEvent('userLoggedIn'));
|
window.dispatchEvent(new CustomEvent('userLoggedIn'));
|
||||||
this.props.navigate('/');
|
this.props.navigate('/');
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -362,7 +375,7 @@ export class LoginComponent extends Component {
|
|||||||
window.socketManager.emit('verifyGoogleUser', { credential: credentialResponse.credential }, (response) => {
|
window.socketManager.emit('verifyGoogleUser', { credential: credentialResponse.credential }, (response) => {
|
||||||
console.log('google respo',response);
|
console.log('google respo',response);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
sessionStorage.setItem('user', JSON.stringify(response.user));
|
persistSessionAuth(response);
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
isAdmin: !!response.user.admin,
|
isAdmin: !!response.user.admin,
|
||||||
|
|||||||
@@ -9,9 +9,82 @@ class SocketManager {
|
|||||||
this.on = this.on.bind(this);
|
this.on = this.on.bind(this);
|
||||||
this.off = this.off.bind(this);
|
this.off = this.off.bind(this);
|
||||||
this.connectPromise = null;
|
this.connectPromise = null;
|
||||||
|
this.reauthPromise = null;
|
||||||
this.pendingListeners = new Map();
|
this.pendingListeners = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getStoredUser() {
|
||||||
|
const storedUser = sessionStorage.getItem('user');
|
||||||
|
if (!storedUser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(storedUser);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse stored user for socket reauth:', error);
|
||||||
|
sessionStorage.removeItem('user');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildSocketAuth() {
|
||||||
|
const token = sessionStorage.getItem('authToken');
|
||||||
|
const user = this._getStoredUser();
|
||||||
|
if (!token && !user) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide both nested and flat shapes for backend compatibility.
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
user,
|
||||||
|
userId: user ? user.id : undefined,
|
||||||
|
email: user ? user.email : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_reauthenticate() {
|
||||||
|
if (!this._socket || !this._socket.connected) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = sessionStorage.getItem('authToken');
|
||||||
|
if (!token) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.reauthPromise) {
|
||||||
|
return this.reauthPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.reauthPromise = new Promise((resolve) => {
|
||||||
|
let settled = false;
|
||||||
|
const done = () => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
this.reauthPromise = null;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't block emits indefinitely if backend ignores this event.
|
||||||
|
const timeoutId = setTimeout(done, 800);
|
||||||
|
|
||||||
|
this._socket.emit('verifyToken', { token }, (response) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
if (response && response.success && response.user) {
|
||||||
|
sessionStorage.setItem('user', JSON.stringify(response.user));
|
||||||
|
}
|
||||||
|
if (response && response.success && response.token) {
|
||||||
|
sessionStorage.setItem('authToken', response.token);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.reauthPromise;
|
||||||
|
}
|
||||||
|
|
||||||
// Lazily import socket.io-client and create the socket on first use.
|
// Lazily import socket.io-client and create the socket on first use.
|
||||||
// Subsequent calls return the same promise.
|
// Subsequent calls return the same promise.
|
||||||
_ensureSocket() {
|
_ensureSocket() {
|
||||||
@@ -22,6 +95,20 @@ class SocketManager {
|
|||||||
this._socket = io('', {
|
this._socket = io('', {
|
||||||
transports: ['websocket', 'polling'],
|
transports: ['websocket', 'polling'],
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
|
withCredentials: true,
|
||||||
|
auth: this._buildSocketAuth()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Always refresh auth data before reconnect attempts.
|
||||||
|
if (this._socket.io && this._socket.io.on) {
|
||||||
|
this._socket.io.on('reconnect_attempt', () => {
|
||||||
|
this._socket.auth = this._buildSocketAuth();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-authenticate every time a new socket connection is established.
|
||||||
|
this._socket.on('connect', () => {
|
||||||
|
this.reauthPromise = this._reauthenticate();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register any listeners that arrived before the socket was ready
|
// Register any listeners that arrived before the socket was ready
|
||||||
@@ -81,9 +168,12 @@ class SocketManager {
|
|||||||
this.connectPromise = this._ensureSocket().then(
|
this.connectPromise = this._ensureSocket().then(
|
||||||
(socket) =>
|
(socket) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
|
socket.auth = this._buildSocketAuth();
|
||||||
socket.connect();
|
socket.connect();
|
||||||
socket.once('connect', () => {
|
socket.once('connect', () => {
|
||||||
resolve();
|
this._reauthenticate()
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(() => resolve());
|
||||||
});
|
});
|
||||||
socket.once('connect_error', (error) => {
|
socket.once('connect_error', (error) => {
|
||||||
this.connectPromise = null;
|
this.connectPromise = null;
|
||||||
@@ -112,8 +202,16 @@ class SocketManager {
|
|||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
socket.emit(event, ...args);
|
const emitNow = () => {
|
||||||
resolve();
|
socket.emit(event, ...args);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.reauthPromise) {
|
||||||
|
this.reauthPromise.then(emitNow).catch(emitNow);
|
||||||
|
} else {
|
||||||
|
emitNow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
|
|||||||
Reference in New Issue
Block a user