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

[Feature] OAuth implementation (Twitter & Github) #682

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from

Conversation

fonstack
Copy link
Member

@fonstack fonstack commented May 10, 2024

Summary by CodeRabbit

  • New Features

    • Added OAuth integration for Twitter and GitHub to enhance user profile capabilities.
    • Introduced new OAuth-related UI elements and routes in the web application.
    • Updated localization files to support OAuth account linking and unlinking.
  • Enhancements

    • Updated application version to improve compatibility and performance.
  • Bug Fixes

    • Implemented new icons and updated UI components to ensure consistency across the platform.
  • Documentation

    • Enhanced user guidance on OAuth functionalities through updated descriptions in localization files.

Copy link
Contributor

coderabbitai bot commented May 10, 2024

Walkthrough

This update primarily focuses on integrating OAuth functionalities for Twitter and GitHub, enhancing user profile management in the web application. Changes include the addition of OAuth fields in user profiles, new OAuth-related UI components, and updates to various backend services to support OAuth linking and unlinking.

Changes

File Path Change Summary
.../package.json, .../constants.ts Updated version in package.json and added oauthData enum in constants.ts.
.../types/profile.ts, .../profilesService.ts Added OAuth fields to IHackerProfile and functions for OAuth linking in profilesService.ts.
.../assets/icons/social/x.icon.js Added new XIcon React component.
.../languages/en.json, .../navigation/paths.ts Updated localization for OAuth and added new OAuth route in navigation.
.../navigation/routes.tsx, .../HackerProfilePage/... Modified navigation to include OAuth routes and updated HackerProfilePage with OAuth features.
.../_oauth/... Added new OAuth handling components and routes for GitHub and Twitter.

Poem

🐇✨
In the land of code, where the bits align,
A rabbit hopped through, leaving marks so fine.
OAuth twines 'round profiles, a dance of keys,
GitHub and Twitter join with the greatest ease.
With every hop, a new connection made,
In the digital burrow, where trust is laid. 🌟🐾


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>.
    • 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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @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.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration 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
Contributor

@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: 3

Review Details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits Files that changed from the base of the PR and between b68ebc9 and bafbc44.
Files selected for processing (14)
  • packages/shared/package.json (1 hunks)
  • packages/shared/src/types/profile.ts (1 hunks)
  • packages/web/src/assets/icons/social/x.icon.js (1 hunks)
  • packages/web/src/constants/constants.ts (1 hunks)
  • packages/web/src/languages/en.json (1 hunks)
  • packages/web/src/navigation/paths.ts (1 hunks)
  • packages/web/src/navigation/routes.tsx (2 hunks)
  • packages/web/src/pages/HackerProfile/HackerProfilePage/HackerProfilePage.tsx (7 hunks)
  • packages/web/src/pages/HackerProfile/HackerProfilePage/styles.ts (2 hunks)
  • packages/web/src/pages/HackerProfile/hooks.ts (2 hunks)
  • packages/web/src/pages/HackerProfile/profilesService.ts (1 hunks)
  • packages/web/src/pages/_oauth/GithubOauth.tsx (1 hunks)
  • packages/web/src/pages/_oauth/TwitterOauth.tsx (1 hunks)
  • packages/web/src/pages/_oauth/router.tsx (1 hunks)
Files skipped from review due to trivial changes (4)
  • packages/shared/package.json
  • packages/web/src/assets/icons/social/x.icon.js
  • packages/web/src/constants/constants.ts
  • packages/web/src/navigation/paths.ts
Additional comments not posted (20)
packages/shared/src/types/profile.ts (1)

12-15: The addition of the oauth field to the IHackerProfile interface is correctly implemented to support OAuth integration for Twitter and GitHub.

packages/web/src/pages/_oauth/router.tsx (1)

1-22: The oauthRouter function is correctly implemented with appropriate routes for OAuth operations, ensuring proper navigation and component rendering for GitHub and Twitter OAuth processes.

packages/web/src/navigation/routes.tsx (1)

Line range hint 8-25: The integration of oauthRouter into the main routes configuration is correctly implemented, ensuring that OAuth-specific routes are part of the application's routing structure.

packages/web/src/pages/_oauth/GithubOauth.tsx (1)

1-31: The GithubOauth component is correctly implemented to handle the OAuth process for GitHub, including code retrieval, local storage interaction, and navigation post-authentication.

packages/web/src/pages/_oauth/TwitterOauth.tsx (1)

1-32: The TwitterOauth component is correctly implemented to handle the OAuth process for Twitter, including state and code verification, local storage interaction, and navigation post-authentication.

packages/web/src/pages/HackerProfile/hooks.ts (1)

101-127: The new hooks useLinkOAuth and useUnlinkOAuth are correctly implemented to manage the linking and unlinking of OAuth accounts to user profiles, using the useMutation hook for asynchronous operations.

packages/web/src/pages/HackerProfile/profilesService.ts (1)

133-165: The functions linkOAuthToProfile and unlinkOAuthFromProfile are correctly implemented to handle the linking and unlinking of OAuth accounts, with appropriate error handling and interaction with the backend API.

packages/web/src/pages/HackerProfile/HackerProfilePage/styles.ts (1)

Line range hint 125-287: The new styles for .oauth-connectors and .button-container are correctly implemented, enhancing the UI for OAuth functionalities and providing a consistent user experience.

packages/web/src/pages/HackerProfile/HackerProfilePage/HackerProfilePage.tsx (7)

7-7: Import of XIcon is added to replace TwitterIcon.


10-10: Import of LocalStorage from constants is crucial for managing OAuth data in local storage.


17-17: Import of RoutePaths is used for navigation, which is essential for handling OAuth redirections.


28-34: Introduction of new hooks related to OAuth and address linking/unlinking.


50-50: State oAuthLinking is introduced to manage the current OAuth process. Ensure proper reset of this state to avoid stale state issues.

Verification successful

The verification process has confirmed that the setOAuthLinking function is used appropriately within the HackerProfilePage.tsx file to manage the state of oAuthLinking. The state is reset to undefined in multiple places, which aligns with the best practices for managing state in React to avoid stale state issues.

Proper Reset of oAuthLinking State Verified:

  • The state is initialized with useState.
  • It is conditionally set based on the oauth variable.
  • It is explicitly reset to undefined in necessary scenarios to ensure no stale state.

This confirms that the original review comment concern has been addressed correctly in the code.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify proper reset of `oAuthLinking` state across all components.

# Test: Search for all occurrences of `setOAuthLinking`. Expect: Proper reset in all relevant places.
rg --type tsx $'setOAuthLinking'

Length of output: 59



Script:

#!/bin/bash
# Description: Verify proper reset of `oAuthLinking` state across all components.

# Test: Search for all occurrences of `setOAuthLinking` in TypeScript files.
rg --type ts 'setOAuthLinking'

Length of output: 531


65-66: Hooks useLinkOAuth and useUnlinkOAuth are used for managing OAuth operations. Ensure these hooks handle all edge cases, such as network failures or unauthorized access.

Verification successful

The hooks useLinkOAuth and useUnlinkOAuth are indeed implemented in the hooks.ts file, and both utilize the useMutation hook from @tanstack/react-query. This setup is generally used to handle asynchronous operations, such as network requests, which are common in OAuth operations.

Analysis of useLinkOAuth and useUnlinkOAuth:

  • Error Handling: Both hooks use the useMutation hook, which inherently supports error handling by capturing errors during the mutation operation and providing them in the mutation result. This allows the consuming components to handle errors appropriately, such as displaying error messages or taking other corrective actions.
  • Unauthorized Access: While the hooks themselves do not explicitly handle unauthorized access, this is typically managed by the backend services (linkOAuthToProfile and unlinkOAuthFromProfile). The frontend hook would receive an error if the backend service rejects the operation due to authorization issues.
  • Network Failures: The useMutation hook also handles network failures by default. If the network request fails, the error is captured and can be handled by the components using these hooks.

Given this information, it appears that the hooks are designed to handle edge cases like network failures and unauthorized access through the mechanisms provided by useMutation. This aligns with best practices for handling such cases in React components using hooks.

Conclusion:

The hooks useLinkOAuth and useUnlinkOAuth are implemented with appropriate error handling mechanisms for typical OAuth operation issues, including network failures and unauthorized access. This confirms that the original review comment concern has been addressed in the implementation.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify error handling in `useLinkOAuth` and `useUnlinkOAuth` hooks.

# Test: Retrieve implementations of these hooks. Expect: Comprehensive error handling.
ast-grep --lang typescript --pattern $'function useLinkOAuth($_, $_) { $$$ }'
ast-grep --lang typescript --pattern $'function useUnlinkOAuth($_, $_) { $$$ }'

Length of output: 159



Script:

#!/bin/bash
# Broaden the search to find any occurrences of `useLinkOAuth` and `useUnlinkOAuth` in the codebase.
ast-grep --lang typescript --pattern $'$$$ useLinkOAuth($$$) $$$'
ast-grep --lang typescript --pattern $'$$$ useUnlinkOAuth($$$) $$$'

Length of output: 1033



Script:

#!/bin/bash
# Search for TypeScript files that might contain the hooks `useLinkOAuth` and `useUnlinkOAuth`.
rg --files-with-matches 'useLinkOAuth' --glob '*.ts' --glob '*.tsx'
rg --files-with-matches 'useUnlinkOAuth' --glob '*.ts' --glob '*.tsx'

Length of output: 383



Script:

#!/bin/bash
# Retrieve the content of the file where the hooks are defined to inspect their implementation.
cat packages/web/src/pages/HackerProfile/hooks.ts

Length of output: 3241


390-413: Modal for linking OAuth accounts. Ensure that the modal is accessible and properly handles all user interactions, especially when OAuth linking is in progress.

packages/web/src/languages/en.json (5)

769-773: The added localization strings for OAuth functionality are correctly implemented and formatted.


769-769: The localization string for 'streakExplanationDescription' is clear and well-formatted.


770-770: The localization string for 'connectOauthAccount' is correctly implemented and provides flexibility for different OAuth providers.


771-772: The localization strings for 'unlinkOAuth' and 'unlinkOAuthExplanation' are well-implemented, providing clear instructions for unlinking OAuth accounts.


773-773: The localization string for 'linkYourAccount' is correctly implemented and formatted, enhancing the user interface for linking OAuth accounts.

Comment on lines +95 to +115
useEffect(() => {
const oauth = searchParams.get("oauth") as typeof oAuthLinking;
if (!oauth || !createdProfile) return;
if (linkOAuth.isLoading || linkOAuth.isSuccess) return;
if (isSigningIn || oAuthLinking) return;

// If the user is not the owner of the profile, redirect to the home page
if (createdProfile.username.toLowerCase() !== username?.toLowerCase()) {
return navigate(`/`);
}

// Check if account has already a connected oauth account
if (createdProfile.oauth?.[oauth]) {
return navigate(`${RoutePaths.profile}/${createdProfile.username}`);
}

const oauthData = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oauth}`) ?? "null");
if (!oauthData) return;

setOAuthLinking(oauth ?? undefined);
}, [searchParams, createdProfile, navigate, username, linkOAuth, isSigningIn, oAuthLinking]);
Copy link
Contributor

Choose a reason for hiding this comment

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

The effect handles OAuth linking based on URL parameters. It should also handle cases where oauth parameter is manipulated or invalid.

-    if (!oauth || !createdProfile) return;
+    if (!oauth || !createdProfile || !['twitter', 'github'].includes(oauth)) return;

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
useEffect(() => {
const oauth = searchParams.get("oauth") as typeof oAuthLinking;
if (!oauth || !createdProfile) return;
if (linkOAuth.isLoading || linkOAuth.isSuccess) return;
if (isSigningIn || oAuthLinking) return;
// If the user is not the owner of the profile, redirect to the home page
if (createdProfile.username.toLowerCase() !== username?.toLowerCase()) {
return navigate(`/`);
}
// Check if account has already a connected oauth account
if (createdProfile.oauth?.[oauth]) {
return navigate(`${RoutePaths.profile}/${createdProfile.username}`);
}
const oauthData = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oauth}`) ?? "null");
if (!oauthData) return;
setOAuthLinking(oauth ?? undefined);
}, [searchParams, createdProfile, navigate, username, linkOAuth, isSigningIn, oAuthLinking]);
useEffect(() => {
const oauth = searchParams.get("oauth") as typeof oAuthLinking;
if (!oauth || !createdProfile || !['twitter', 'github'].includes(oauth)) return;
if (linkOAuth.isLoading || linkOAuth.isSuccess) return;
if (isSigningIn || oAuthLinking) return;
// If the user is not the owner of the profile, redirect to the home page
if (createdProfile.username.toLowerCase() !== username?.toLowerCase()) {
return navigate(`/`);
}
// Check if account has already a connected oauth account
if (createdProfile.oauth?.[oauth]) {
return navigate(`${RoutePaths.profile}/${createdProfile.username}`);
}
const oauthData = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oauth}`) ?? "null");
if (!oauthData) return;
setOAuthLinking(oauth ?? undefined);
}, [searchParams, createdProfile, navigate, username, linkOAuth, isSigningIn, oAuthLinking]);

Comment on lines +186 to +211
const handleLinkOAuth = async () => {
if (!oAuthLinking || !profileFound) return;

const auth = await tryAuthentication();
if (!auth) return;

const oauthParams = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oAuthLinking}`) ?? "null");
if (!oauthParams) return;

try {
await linkOAuth.mutateAsync({
username: profileFound.username,
oauth: oAuthLinking,
oauthParams,
});

queryClient.invalidateQueries({ queryKey: ["hacker-profile-username", profileFound.username] });
queryClient.invalidateQueries({ queryKey: ["all-profiles"] });
setOAuthLinking(undefined);

return navigate(`${RoutePaths.profile}/${profileFound.username}`);
} catch (error) {
console.error("ERROR linking oauth", error);
return navigate(`${RoutePaths.profile}/${profileFound.username}`);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Function handleLinkOAuth manages the OAuth linking process. It lacks error handling for JSON parsing which could throw an exception if localStorage data is corrupted.

-    const oauthParams = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oAuthLinking}`) ?? "null");
+    let oauthParams;
+    try {
+      oauthParams = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oAuthLinking}`) ?? "null");
+    } catch (error) {
+      console.error("Failed to parse OAuth parameters", error);
+      return;
+    }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const handleLinkOAuth = async () => {
if (!oAuthLinking || !profileFound) return;
const auth = await tryAuthentication();
if (!auth) return;
const oauthParams = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oAuthLinking}`) ?? "null");
if (!oauthParams) return;
try {
await linkOAuth.mutateAsync({
username: profileFound.username,
oauth: oAuthLinking,
oauthParams,
});
queryClient.invalidateQueries({ queryKey: ["hacker-profile-username", profileFound.username] });
queryClient.invalidateQueries({ queryKey: ["all-profiles"] });
setOAuthLinking(undefined);
return navigate(`${RoutePaths.profile}/${profileFound.username}`);
} catch (error) {
console.error("ERROR linking oauth", error);
return navigate(`${RoutePaths.profile}/${profileFound.username}`);
}
};
const handleLinkOAuth = async () => {
if (!oAuthLinking || !profileFound) return;
const auth = await tryAuthentication();
if (!auth) return;
let oauthParams;
try {
oauthParams = JSON.parse(localStorage.getItem(`${LocalStorage.oauthData}_${oAuthLinking}`) ?? "null");
} catch (error) {
console.error("Failed to parse OAuth parameters", error);
return;
}
if (!oauthParams) return;
try {
await linkOAuth.mutateAsync({
username: profileFound.username,
oauth: oAuthLinking,
oauthParams,
});
queryClient.invalidateQueries({ queryKey: ["hacker-profile-username", profileFound.username] });
queryClient.invalidateQueries({ queryKey: ["all-profiles"] });
setOAuthLinking(undefined);
return navigate(`${RoutePaths.profile}/${profileFound.username}`);
} catch (error) {
console.error("ERROR linking oauth", error);
return navigate(`${RoutePaths.profile}/${profileFound.username}`);
}
};

@@ -176,7 +270,7 @@
<div className="socials">
{profileFound.twitter_username && (
<a href={`https://twitter.com/${profileFound.twitter_username}`} {...defaultAnchorProps}>
<TwitterIcon />
<XIcon />
Copy link
Contributor

Choose a reason for hiding this comment

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

Usage of XIcon for Twitter profile link seems incorrect as per the previous icon used (TwitterIcon). This might be a visual inconsistency.

-                        <XIcon />
+                        <TwitterIcon />

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
<XIcon />
<TwitterIcon />

@fonstack fonstack marked this pull request as draft June 18, 2024 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant