Skip to content

Fix GitHub OAuth redirect flow and URL encoding #1824

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@uiw/react-md-editor": "^4.0.4",
"autoprefixer": "^10.4.20",
"axios": "^1.6.2",
"bcrypt": "^5.1.1",
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "1.0.0",
Expand Down
285 changes: 7 additions & 278 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/app/api/admin/services/externalLogin/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { z } from 'zod';
import db from '@/db';
import { NextRequest, NextResponse } from 'next/server';
import bcrypt from 'bcrypt';
import bcrypt from 'bcryptjs';

const loginSchema = z.object({
email: z.string(),
Expand Down
32 changes: 27 additions & 5 deletions src/app/api/github/link/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
import { NextResponse } from 'next/server';

export async function GET() {
const clientId = process.env.GITHUB_ID;
const redirectUri = `${process.env.NEXTAUTH_URL}/api/github/callback`;
const scope = 'read:user user:email';
try {
const clientId = process.env.GITHUB_ID;
const nextAuthUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
const redirectUri = `${nextAuthUrl}/api/github/callback`;
const scope = 'read:user user:email';

const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}`;
if (!clientId) {
console.error('GitHub OAuth client ID is not configured');
return NextResponse.redirect(new URL('/payout-methods?error=github_link_failed', nextAuthUrl));
}

return NextResponse.redirect(githubAuthUrl);
console.log('Redirecting to GitHub OAuth:', {
clientId,
redirectUri,
scope,
nextAuthUrl
});

// Encode the redirectUri to ensure it's properly formatted
const encodedRedirectUri = encodeURIComponent(redirectUri);
const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${clientId}&redirect_uri=${encodedRedirectUri}&scope=${scope}`;

console.log('Final GitHub Auth URL:', githubAuthUrl);

return NextResponse.redirect(githubAuthUrl);
} catch (error) {
console.error('Error in GitHub link API:', error);
return NextResponse.redirect(new URL('/payout-methods?error=github_link_failed', process.env.NEXTAUTH_URL || 'http://localhost:3000'));
}
}
2 changes: 1 addition & 1 deletion src/app/api/mobile/signin/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import db from '@/db';
import { NextRequest, NextResponse } from 'next/server';
import bcrypt from 'bcrypt';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
import { importJWK, JWTPayload, SignJWT } from 'jose';

Expand Down
73 changes: 45 additions & 28 deletions src/components/GitHubLinkButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,35 @@ export const GitHubLinkButton = () => {
}, [searchParams]);

const getGithubData = async () => {
const response = await fetch('/api/github/details');
const resp = await response.json();
setGithubData(resp.data[0]);
try {
const response = await fetch('/api/github/details');
const resp = await response.json();
setGithubData(resp.data && resp.data.length > 0 ? resp.data[0] : null);
} catch (error) {
console.error('Error fetching GitHub data:', error);
}
};

const handleUnlinkAccount = async () => {
setIsProcessing(true);
const response = await fetch('/api/github/details', {
method: 'DELETE',
});
setIsProcessing(false);
const resp = await response.json();
console.log(resp);
if (resp.success) {
toast.success(resp.message);
getGithubData();
} else {
toast.error(resp.message);
try {
const response = await fetch('/api/github/details', {
method: 'DELETE',
});
const resp = await response.json();
console.log(resp);
if (resp.success) {
toast.success(resp.message);
setGithubData(null); // Clear the GitHub data immediately
getGithubData(); // Refresh the data
} else {
toast.error(resp.message || 'Failed to unlink GitHub account');
}
} catch (error) {
console.error('Error unlinking GitHub account:', error);
toast.error('Failed to unlink GitHub account');
} finally {
setIsProcessing(false);
}
};

Expand All @@ -52,9 +63,19 @@ export const GitHubLinkButton = () => {
getGithubData();
}, [searchParams]);

const handleLinkGitHub = async () => {
const handleLinkGitHub = () => {
if (isProcessing) return; // Prevent multiple clicks

setIsProcessing(true);
window.location.href = '/api/github/link';
try {
console.log('Redirecting to GitHub OAuth...');
// Navigate directly to the GitHub OAuth endpoint
window.location.href = '/api/github/link';
} catch (error) {
console.error('Error linking to GitHub:', error);
setIsProcessing(false);
toast.error('Failed to redirect to GitHub login');
}
};

return (
Expand All @@ -72,26 +93,23 @@ export const GitHubLinkButton = () => {
<div className="my-6 flex items-center gap-2">
{githubData ? (
<div className="flex items-center gap-2">
{/* {githubData?.avatarUrl} */}
<Button
onClick={handleUnlinkAccount}
color="white"
variant={'destructive'}
className="font-semi-bold h-[2rem] w-[5rem] text-white"
variant="destructive"
className="h-[2rem] w-[5rem]"
>
{isProcessing ? 'Unlinking...' : 'Unlink'}
</Button>
<Dialog>
<DialogTrigger asChild>
<Button
color="white"
variant={'outline'}
className="font-semi-bold h-[2rem] w-fit text-white"
variant="outline"
className="h-[2rem] w-fit"
>
<div className="flex h-full w-full items-center gap-2">
<Image
src={githubData?.avatarUrl || ''}
alt="Landscape"
alt="GitHub Profile"
width={400}
height={400}
className="h-[1rem] w-[1rem] rounded-full object-cover"
Expand Down Expand Up @@ -120,7 +138,6 @@ export const GitHubLinkButton = () => {
>
{githubData?.profileUrl}
</Link>
{/* <p className="text-sm text-muted-foreground">@oliviadavis</p> */}
</div>
</div>
</DialogContent>
Expand All @@ -130,8 +147,8 @@ export const GitHubLinkButton = () => {
<Button
onClick={handleLinkGitHub}
disabled={isProcessing}
color="white"
className="font-semi-bold h-[2rem] w-[5rem] text-white"
variant="default"
className="h-[2rem] w-[5rem]"
>
{isProcessing ? 'Linking...' : 'Link'}
</Button>
Expand All @@ -141,7 +158,7 @@ export const GitHubLinkButton = () => {
<div className="h-full w-full">
<Image
src={GITHUB}
alt="Landscape"
alt="GitHub"
width={400}
height={400}
className="absolute bottom-[-50px] right-0 h-[10rem] w-[10rem] object-cover opacity-40 transition-all duration-300 ease-in-out group-hover:bottom-[-20px] group-hover:opacity-100 dark:invert"
Expand Down
2 changes: 1 addition & 1 deletion src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import db from '@/db';
import CredentialsProvider from 'next-auth/providers/credentials';
import { JWTPayload, SignJWT, importJWK } from 'jose';
import bcrypt from 'bcrypt';
import bcrypt from 'bcryptjs';
import prisma from '@/db';
import { NextAuthOptions } from 'next-auth';
import { Session } from 'next-auth';
Expand Down