Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move redirect check to protected route file in analytics platform #2530

Merged
merged 2 commits into from
Feb 25, 2025

Conversation

Codebmk
Copy link
Member

@Codebmk Codebmk commented Feb 25, 2025

This pull request focuses on refactoring and improving the authentication logic, particularly around handling Google redirects and user session setup. The main changes include moving the authentication logic from src/platform/src/pages/Home/index.jsx to a higher-order component in src/platform/src/core/utils/protectedRoute.js, and removing redundant code from the Home component.

Authentication Refactoring:

Code Cleanup:

  • src/platform/src/pages/Home/index.jsx: Removed the authentication-related logic and redundant imports, including Cookies, jwt_decode, useRouter, and session setup functions. This simplifies the Home component by delegating authentication responsibilities to the higher-order component. [1] [2] [3]

Summary by CodeRabbit

  • New Features
    • Introduced an enhanced authentication process with robust error handling and automatic retries for improved sign-in stability.
    • Upgraded user session management to securely initialize and maintain user access.
  • Refactor
    • Streamlined the home page by removing redundant authentication and loading steps, resulting in a cleaner and faster user experience.

@Codebmk Codebmk self-assigned this Feb 25, 2025
Copy link

coderabbitai bot commented Feb 25, 2025

📝 Walkthrough

Walkthrough

The changes enhance the authentication flow by improving the withAuth higher-order component in protectedRoute.js, introducing functionalities for Google authentication, user session management, and API call retries. A new retryWithDelay function is implemented to manage rate-limiting errors, while setupUserSession initializes user sessions and retrieves preferences. In contrast, the Home component has been simplified by removing user session and Google authentication logic, resulting in a more streamlined structure.

Changes

File(s) Summary
src/platform/.../utils/protectedRoute.js Added new functions: retryWithDelay for handling API rate limits with a retry loop and setupUserSession for initializing user sessions and retrieving preferences. Modified the useEffect hook to incorporate Google authentication checks using cookies and JWT decoding.
src/platform/.../pages/Home/index.jsx Removed various imports (e.g., Cookies, jwt_decode, useRouter) and related logic for Google authentication, user session management, and loading state. Eliminated constants and functions like MAX_RETRIES, RETRY_DELAY, retryWithDelay, and setupUserSession for a simpler control flow.

Possibly related PRs

  • Analytics: optimize chart loading #2268: The changes in the main PR are related to those in the retrieved PR as both introduce retry mechanisms for handling API calls, specifically the retryWithDelay function in the main PR and the retry logic in the fetchAnalytics function of the retrieved PR.

Suggested reviewers

  • OchiengPaul442
  • Baalmart

Poem

In the realm of code so bright,
New routes emerge within the night.
Cookies check and tokens sing,
Sessions wake to life and spring.
With retries dancing in a loop,
Our code now soars in a graceful swoop!

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • JIRA integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/platform/src/core/utils/protectedRoute.js (3)

5-6: Consider security implications of storing tokens.
Importing Cookies and jwt_decode can be convenient, but the subsequent use in local storage (lines 67–68) might expose tokens to XSS attacks. Consider using httpOnly cookies or a secure session storage mechanism.


23-33: Implement exponential backoff in retry logic.
Currently, retryWithDelay uses a fixed delay of 1 second. A growing or exponential backoff is often more robust against ongoing 429 rate-limit issues.

- const RETRY_DELAY = 1000;
...
-       await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
+ const RETRY_BASE_DELAY = 1000;
...
+       const attempt = MAX_RETRIES - retries + 1; 
+       await new Promise((resolve) => 
+         setTimeout(resolve, RETRY_BASE_DELAY * 2 ** (attempt - 1))
+       );

86-86: Review dependency array to avoid unexpected useEffect triggers.
retryWithDelay in the dependency array can trigger re-renders whenever it changes (even if functionally stable). Consider memoizing or extracting it outside the component if unnecessary for re-renders.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dae166f and 30c0325.

📒 Files selected for processing (2)
  • src/platform/src/core/utils/protectedRoute.js (2 hunks)
  • src/platform/src/pages/Home/index.jsx (0 hunks)
💤 Files with no reviewable changes (1)
  • src/platform/src/pages/Home/index.jsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build-push-deploy-analytics-platform-preview
🔇 Additional comments (1)
src/platform/src/core/utils/protectedRoute.js (1)

63-76: Ensure consistent handling of Google authentication flow.
This flow retrieves tokens, decodes them, and sets session details. Confirm that downstream logic (e.g., UI or data calls) does not break if the Google auth cookie is missing or expired.

Comment on lines +35 to +57
const setupUserSession = async (user) => {
if (!user.groups[0]?.grp_title) {
throw new Error(
'Server error. Contact support to add you to the AirQo Organisation',
);
}

localStorage.setItem('loggedUser', JSON.stringify(user));

const preferencesResponse = await retryWithDelay(() =>
dispatch(getIndividualUserPreferences({ identifier: user._id })),
);
if (preferencesResponse.payload.success) {
const preferences = preferencesResponse.payload.preferences;
const activeGroup = preferences[0]?.group_id
? user.groups.find((group) => group._id === preferences[0].group_id)
: user.groups.find((group) => group.grp_title === 'airqo');
localStorage.setItem('activeGroup', JSON.stringify(activeGroup));
}

dispatch(setUserInfo(user));
dispatch(setSuccess(true));
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle preference-fetch failures and validate group selection.
When getIndividualUserPreferences fails or returns no preferences, the user session lacks a fallback group. Consider providing a default group or clearly warning the user. Additionally, storing the entire user object in local storage (line 42) may risk exposing sensitive data.

Copy link
Contributor

New next-platform changes available for preview here

@Codebmk Codebmk changed the title Move redirect check to protected route in analytics platform Move redirect check to protected route file in analytics platform Feb 25, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/platform/src/core/utils/protectedRoute.js (1)

39-61: 🛠️ Refactor suggestion

Handle preference-fetch failures and validate group selection.

When getIndividualUserPreferences fails or returns no preferences, the user session lacks a fallback group. Consider providing a default group or clearly warning the user. Additionally, storing the entire user object in local storage (line 46) may risk exposing sensitive data.

I recommend implementing a more robust fallback mechanism and filtering sensitive data:

 const setupUserSession = async (user) => {
   if (!user.groups[0]?.grp_title) {
     throw new Error(
       'Server error. Contact support to add you to the AirQo Organisation',
     );
   }

-  localStorage.setItem('loggedUser', JSON.stringify(user));
+  // Filter sensitive data before storing in localStorage
+  const safeUserData = {
+    _id: user._id,
+    name: user.name,
+    email: user.email,
+    groups: user.groups.map(g => ({ _id: g._id, grp_title: g.grp_title }))
+  };
+  localStorage.setItem('loggedUser', JSON.stringify(safeUserData));

   const preferencesResponse = await retryWithDelay(() =>
     dispatch(getIndividualUserPreferences({ identifier: user._id })),
   );
-  if (preferencesResponse.payload.success) {
+  let activeGroup = null;
+  try {
+    if (preferencesResponse.payload.success) {
       const preferences = preferencesResponse.payload.preferences;
-      const activeGroup = preferences[0]?.group_id
+      activeGroup = preferences[0]?.group_id
         ? user.groups.find((group) => group._id === preferences[0].group_id)
         : user.groups.find((group) => group.grp_title === 'airqo');
-      localStorage.setItem('activeGroup', JSON.stringify(activeGroup));
-  }
+    }
+  } catch (err) {
+    console.error('Error processing preferences:', err);
+  }
+  
+  // Ensure we have an active group, defaulting to the first one if needed
+  if (!activeGroup && user.groups.length > 0) {
+    activeGroup = user.groups[0];
+    console.warn('No active group found in preferences, defaulting to:', activeGroup.grp_title);
+  }
+  
+  if (activeGroup) {
+    localStorage.setItem('activeGroup', JSON.stringify(activeGroup));
+  } else {
+    throw new Error('No valid group found for user');
+  }

   dispatch(setUserInfo(user));
   dispatch(setSuccess(true));
 };
🧹 Nitpick comments (3)
src/platform/src/core/utils/protectedRoute.js (3)

27-37: The retry mechanism is well-implemented but lacks logging.

The retryWithDelay function properly handles rate limiting (429) errors with exponential backoff, but consider adding logging before retries to help with debugging.

 const retryWithDelay = async (fn, retries = MAX_RETRIES) => {
   try {
     return await fn();
   } catch (error) {
     if (retries > 0 && error.response?.status === 429) {
+      console.log(`Rate limited. Retrying in ${RETRY_DELAY}ms. Attempts remaining: ${retries - 1}`);
       await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
       return retryWithDelay(fn, retries - 1);
     }
     throw error;
   }
 };

94-94: Consider memoizing functions to optimize useEffect dependencies.

Including retryWithDelay in the dependency array could cause unnecessary re-renders as it's recreated on each render. Consider using useCallback for these functions.

+const memoizedRetryWithDelay = React.useCallback(
+  async (fn, retries = MAX_RETRIES) => {
+    try {
+      return await fn();
+    } catch (error) {
+      if (retries > 0 && error.response?.status === 429) {
+        await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
+        return memoizedRetryWithDelay(fn, retries - 1);
+      }
+      throw error;
+    }
+  },
+  []
+);

// Later in the useEffect dependencies array:
-}, [userCredentials, dispatch, router, retryWithDelay, isRedirecting]);
+}, [userCredentials, dispatch, router, memoizedRetryWithDelay, isRedirecting]);

66-94: Add timeout for Google authentication to prevent indefinite spinner.

If the authentication or API calls hang, users might be stuck on the loading spinner. Consider adding a timeout mechanism.

 if (router.query.success === 'google') {
+  // Set a timeout to prevent users from being stuck on the spinner
+  const timeoutId = setTimeout(() => {
+    if (isRedirecting) {
+      console.error('Google authentication timed out');
+      setIsRedirecting(false);
+      router.push('/account/login');
+    }
+  }, 15000); // 15 seconds timeout
+
   const token = Cookies.get('access_token');
   if (token) {
     localStorage.setItem('token', token);
     const decoded = jwt_decode(token);
     retryWithDelay(() => getUserDetails(decoded._id, token))
       .then((response) => setupUserSession(response.users[0]))
       .catch((error) => {
         console.error('Google auth error:', error);
         setIsRedirecting(false);
         router.push('/account/login');
+        clearTimeout(timeoutId);
       });
   } else {
     setIsRedirecting(false);
     router.push('/account/login');
+    clearTimeout(timeoutId);
   }
   return; // Exit early to prevent further checks until redirect is resolved
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 30c0325 and 41c7754.

📒 Files selected for processing (1)
  • src/platform/src/core/utils/protectedRoute.js (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build-push-deploy-analytics-platform-preview
🔇 Additional comments (5)
src/platform/src/core/utils/protectedRoute.js (5)

5-13: The imports align well with the updated authentication flow.

The addition of authentication-related imports like Cookies, jwt_decode, and Redux actions is consistent with moving the Google authentication logic from the Home component to this higher-order component.


15-16: Good use of constants for configuration values.

Defining MAX_RETRIES and RETRY_DELAY as constants improves maintainability and makes the retry behavior easily configurable.


23-25: Appropriate state tracking for Google authentication redirect.

The isRedirecting state is initialized based on the URL query parameter, which is a clean approach to detecting and handling Google authentication redirects.


66-83: Google authentication flow is well-structured.

The Google redirect handling is organized logically with proper error handling and redirection. The early return prevents further execution during redirection, which is a good practice.


97-99: Good UX handling during redirection.

Showing a spinner while processing the Google authentication redirect provides appropriate visual feedback to users.

Copy link
Contributor

New next-platform changes available for preview here

@Baalmart Baalmart merged commit 963fc29 into staging Feb 25, 2025
28 of 29 checks passed
@Baalmart Baalmart deleted the feat-google-login branch February 25, 2025 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants