Skip to content

Commit 61bacbc

Browse files
on peut seulement se login avec github ou discord ? : la car j'ai l'url
1 parent ee76696 commit 61bacbc

File tree

5 files changed

+124
-101
lines changed

5 files changed

+124
-101
lines changed
Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11

22
import { type NextRequest, NextResponse } from 'next/server';
3-
import { auth } from '@/lib/authEdge';
4-
import { storeUserDiscordToken } from '@/lib/db';
3+
import { storeUserDiscordToken, getUserByEmail, createUser } from '@/lib/db';
4+
import { createSessionForUser } from '@/lib/authService';
5+
import type { User } from '@/types';
56

67
export async function GET(request: NextRequest) {
78
const searchParams = request.nextUrl.searchParams;
@@ -14,40 +15,35 @@ export async function GET(request: NextRequest) {
1415

1516
if (!DISCORD_CLIENT_ID || !DISCORD_CLIENT_SECRET || !NEXT_PUBLIC_APP_URL) {
1617
console.error('[Discord OAuth Callback] OAuth environment variables not configured.');
17-
return NextResponse.redirect(new URL('/profile?error=oauth_config_error', request.url));
18+
return NextResponse.redirect(new URL('/login?error=oauth_config_error', request.url));
1819
}
1920

2021
const storedStateCookie = request.cookies.get('discord_oauth_state');
2122
request.cookies.delete('discord_oauth_state');
2223

2324
if (!storedStateCookie) {
2425
console.error('[Discord OAuth Callback] Missing OAuth state cookie.');
25-
return NextResponse.redirect(new URL('/profile?error=oauth_state_missing', request.url));
26+
return NextResponse.redirect(new URL('/login?error=oauth_state_missing', request.url));
2627
}
2728

2829
let storedStateData;
2930
try {
3031
storedStateData = JSON.parse(storedStateCookie.value);
3132
} catch (e) {
3233
console.error('[Discord OAuth Callback] Error parsing OAuth state cookie:', e);
33-
return NextResponse.redirect(new URL('/profile?error=oauth_state_invalid_parse', request.url));
34+
return NextResponse.redirect(new URL('/login?error=oauth_state_invalid_parse', request.url));
3435
}
3536

3637
if (!stateFromDiscord || stateFromDiscord !== storedStateData.csrf) {
3738
console.error('[Discord OAuth Callback] OAuth state mismatch.');
38-
return NextResponse.redirect(new URL('/profile?error=oauth_state_mismatch', request.url));
39-
}
40-
41-
const session = await auth();
42-
if (!session?.user?.uuid) {
43-
console.error('[Discord OAuth Callback] No active FlowUp user session found.');
44-
return NextResponse.redirect(new URL('/login?error=oauth_no_session', request.url));
39+
return NextResponse.redirect(new URL('/login?error=oauth_state_mismatch', request.url));
4540
}
4641

4742
if (!code) {
4843
const error = searchParams.get('error');
4944
const errorDescription = searchParams.get('error_description');
50-
return NextResponse.redirect(new URL(`/profile?error=oauth_missing_code&discord_error=${error || ''}&discord_desc=${errorDescription || ''}`, request.url));
45+
console.error(`[Discord OAuth Callback] Authorization failed on Discord's side. Error: ${error}, Desc: ${errorDescription}`);
46+
return NextResponse.redirect(new URL(`/login?error=oauth_provider_error&message=${encodeURIComponent(errorDescription || error || "Unknown Discord error")}`, request.url));
5147
}
5248

5349
try {
@@ -62,43 +58,71 @@ export async function GET(request: NextRequest) {
6258
grant_type: 'authorization_code',
6359
code,
6460
redirect_uri: `${NEXT_PUBLIC_APP_URL}/api/auth/discord/oauth/callback`,
61+
scope: 'identify email',
6562
}),
6663
});
6764

6865
if (!tokenResponse.ok) {
69-
throw new Error(`Discord token exchange failed: ${await tokenResponse.text()}`);
66+
const errorText = await tokenResponse.text();
67+
console.error(`[Discord OAuth Callback] Discord token exchange failed: ${errorText}`);
68+
throw new Error(`Discord token exchange failed: ${errorText}`);
7069
}
7170
const tokenData = await tokenResponse.json();
7271

7372
const userResponse = await fetch('https://discord.com/api/users/@me', {
74-
headers: {
75-
Authorization: `Bearer ${tokenData.access_token}`,
76-
},
73+
headers: { Authorization: `Bearer ${tokenData.access_token}` },
7774
});
7875

7976
if (!userResponse.ok) {
80-
throw new Error(`Failed to fetch Discord user details: ${await userResponse.text()}`);
77+
const errorText = await userResponse.text();
78+
console.error(`[Discord OAuth Callback] Failed to fetch Discord user details: ${errorText}`);
79+
throw new Error(`Failed to fetch Discord user details: ${errorText}`);
80+
}
81+
const discordUser = await userResponse.json();
82+
83+
if (!discordUser.email || !discordUser.verified) {
84+
console.error(`[Discord OAuth Callback] User's Discord email is missing or not verified.`);
85+
return NextResponse.redirect(new URL(`/login?error=discord_email_unverified`, request.url));
86+
}
87+
88+
let appUser: (User & { hashedPassword?: string }) | null = await getUserByEmail(discordUser.email);
89+
90+
if (!appUser) {
91+
// Signup
92+
console.log(`[Discord OAuth Callback] No user found for email ${discordUser.email}. Creating new user.`);
93+
const newUserInfo = await createUser(
94+
discordUser.username,
95+
discordUser.email
96+
);
97+
appUser = { ...newUserInfo }; // Add necessary properties if createUser returns a different shape
98+
} else {
99+
console.log(`[Discord OAuth Callback] Found existing user for email ${discordUser.email}. Logging in.`);
100+
}
101+
102+
if (!appUser || !appUser.uuid) {
103+
console.error("[Discord OAuth Callback] Failed to get or create a user in FlowUp DB.");
104+
throw new Error("User session could not be established.");
81105
}
82-
const userData = await userResponse.json();
83106

84-
await storeUserDiscordToken(session.user.uuid, {
107+
const { hashedPassword, ...userToReturn } = appUser;
108+
109+
// Create session and store token
110+
await createSessionForUser(userToReturn);
111+
await storeUserDiscordToken(userToReturn.uuid, {
85112
accessToken: tokenData.access_token,
86113
refreshToken: tokenData.refresh_token,
87114
expiresAt: Date.now() + tokenData.expires_in * 1000,
88115
scopes: tokenData.scope,
89-
discordUserId: userData.id,
90-
discordUsername: userData.username,
91-
discordAvatar: userData.avatar,
116+
discordUserId: discordUser.id,
117+
discordUsername: discordUser.username,
118+
discordAvatar: discordUser.avatar,
92119
});
93120

94-
const redirectPath = storedStateData.redirectTo || '/profile';
95-
const redirectUrlObj = new URL(redirectPath, request.nextUrl.origin);
96-
redirectUrlObj.searchParams.set('discord_oauth_status', 'success');
97-
98-
return NextResponse.redirect(redirectUrlObj);
121+
// Redirect to dashboard on successful login/signup
122+
return NextResponse.redirect(new URL('/dashboard', request.url));
99123

100124
} catch (error: any) {
101-
console.error('[Discord OAuth Callback] Error:', error);
102-
return NextResponse.redirect(new URL(`/profile?error=oauth_callback_error&message=${encodeURIComponent(error.message || 'Unknown error')}`, request.url));
125+
console.error('[Discord OAuth Callback] Final catch block error:', error);
126+
return NextResponse.redirect(new URL(`/login?error=oauth_callback_error&message=${encodeURIComponent(error.message || 'Unknown error')}`, request.url));
103127
}
104128
}

src/app/api/auth/discord/oauth/login/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ export async function GET(request: NextRequest) {
3131
discordAuthUrl.searchParams.append('redirect_uri', `${NEXT_PUBLIC_APP_URL}/api/auth/discord/oauth/callback`);
3232
discordAuthUrl.searchParams.append('response_type', 'code');
3333
// Requesting 'identify' and 'email' for basic user profile information.
34-
// The 'bot' scope should be requested in a separate flow if DM permissions are needed later.
3534
discordAuthUrl.searchParams.append('scope', 'identify email');
3635
discordAuthUrl.searchParams.append('state', state);
36+
discordAuthUrl.searchParams.append('prompt', 'consent'); // Always ask for consent, useful for testing
3737

3838
console.log('[Discord OAuth Login] Redirecting to Discord:', discordAuthUrl.toString());
3939
return NextResponse.redirect(discordAuthUrl.toString());
Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11

22
import { type NextRequest, NextResponse } from 'next/server';
3-
import { auth } from '@/lib/authEdge';
4-
import { storeUserGithubOAuthToken } from '@/lib/db';
53
import { Octokit } from 'octokit';
4+
import { storeUserGithubOAuthToken, getUserByEmail, createUser } from '@/lib/db';
5+
import { createSessionForUser } from '@/lib/authService';
6+
import type { User } from '@/types';
67

78
export async function GET(request: NextRequest) {
89
const searchParams = request.nextUrl.searchParams;
@@ -15,97 +16,99 @@ export async function GET(request: NextRequest) {
1516

1617
if (!GITHUB_CLIENT_ID || !GITHUB_CLIENT_SECRET || !NEXT_PUBLIC_APP_URL) {
1718
console.error('[GitHub OAuth Callback] OAuth environment variables not configured.');
18-
return NextResponse.redirect(new URL('/dashboard?error=oauth_config_error', request.url));
19+
return NextResponse.redirect(new URL('/login?error=oauth_config_error', request.url));
1920
}
2021

2122
const storedStateCookie = request.cookies.get('github_oauth_state');
22-
request.cookies.delete('github_oauth_state'); // Clean up state cookie
23+
request.cookies.delete('github_oauth_state');
2324

2425
if (!storedStateCookie) {
2526
console.error('[GitHub OAuth Callback] Missing OAuth state cookie.');
26-
return NextResponse.redirect(new URL('/dashboard?error=oauth_state_missing', request.url));
27+
return NextResponse.redirect(new URL('/login?error=oauth_state_missing', request.url));
2728
}
2829

2930
let storedStateData;
3031
try {
3132
storedStateData = JSON.parse(storedStateCookie.value);
3233
} catch (e) {
3334
console.error('[GitHub OAuth Callback] Error parsing OAuth state cookie:', e);
34-
return NextResponse.redirect(new URL('/dashboard?error=oauth_state_invalid_parse', request.url));
35+
return NextResponse.redirect(new URL('/login?error=oauth_state_invalid_parse', request.url));
3536
}
3637

3738
if (!stateFromGitHub || stateFromGitHub !== storedStateData.csrf) {
3839
console.error('[GitHub OAuth Callback] OAuth state mismatch.', { stateFromGitHub, storedStateCSRF: storedStateData.csrf });
39-
return NextResponse.redirect(new URL('/dashboard?error=oauth_state_mismatch', request.url));
40-
}
41-
42-
const session = await auth();
43-
if (!session?.user?.uuid) {
44-
console.error('[GitHub OAuth Callback] No active FlowUp user session found.');
45-
return NextResponse.redirect(new URL('/login?error=oauth_no_session', request.url));
40+
return NextResponse.redirect(new URL('/login?error=oauth_state_mismatch', request.url));
4641
}
4742

4843
if (!code) {
49-
console.error('[GitHub OAuth Callback] Missing authorization code from GitHub.');
5044
const error = searchParams.get('error');
5145
const errorDescription = searchParams.get('error_description');
52-
return NextResponse.redirect(new URL(`/dashboard?error=oauth_missing_code&gh_error=${error || ''}&gh_desc=${errorDescription || ''}`, request.url));
46+
console.error(`[GitHub OAuth Callback] Authorization failed on GitHub's side. Error: ${error}, Desc: ${errorDescription}`);
47+
return NextResponse.redirect(new URL(`/login?error=oauth_provider_error&message=${encodeURIComponent(errorDescription || error || 'Unknown GitHub error')}`, request.url));
5348
}
5449

5550
try {
56-
console.log('[GitHub OAuth Callback] Exchanging code for access token...');
5751
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
5852
method: 'POST',
59-
headers: {
60-
'Content-Type': 'application/json',
61-
'Accept': 'application/json',
62-
},
53+
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
6354
body: JSON.stringify({
6455
client_id: GITHUB_CLIENT_ID,
6556
client_secret: GITHUB_CLIENT_SECRET,
66-
code: code,
57+
code,
6758
redirect_uri: `${NEXT_PUBLIC_APP_URL}/api/auth/github/oauth/callback`,
6859
}),
6960
});
7061

7162
if (!tokenResponse.ok) {
72-
const errorBody = await tokenResponse.text();
73-
console.error('[GitHub OAuth Callback] Error exchanging code for token:', tokenResponse.status, errorBody);
74-
throw new Error(`GitHub token exchange failed with status ${tokenResponse.status}: ${errorBody}`);
63+
throw new Error(`GitHub token exchange failed: ${await tokenResponse.text()}`);
7564
}
76-
7765
const tokenData = await tokenResponse.json();
78-
7966
if (tokenData.error || !tokenData.access_token) {
80-
console.error('[GitHub OAuth Callback] GitHub returned error or no access_token:', tokenData);
8167
throw new Error(tokenData.error_description || 'Failed to retrieve access token from GitHub.');
8268
}
8369

70+
const octokit = new Octokit({ auth: tokenData.access_token });
71+
const { data: githubUser } = await octokit.rest.users.getAuthenticated();
72+
73+
// Find primary, verified email
74+
const { data: emails } = await octokit.rest.users.listEmailsForAuthenticatedUser();
75+
const primaryEmail = emails.find(email => email.primary && email.verified)?.email;
76+
77+
if (!primaryEmail) {
78+
return NextResponse.redirect(new URL('/login?error=github_no_verified_email', request.url));
79+
}
80+
81+
let appUser: (User & { hashedPassword?: string }) | null = await getUserByEmail(primaryEmail);
82+
83+
if (!appUser) {
84+
console.log(`[GitHub OAuth Callback] No user found for email ${primaryEmail}. Creating new user.`);
85+
const newUserInfo = await createUser(githubUser.name || githubUser.login, primaryEmail);
86+
appUser = { ...newUserInfo };
87+
} else {
88+
console.log(`[GitHub OAuth Callback] Found existing user for email ${primaryEmail}. Logging in.`);
89+
}
90+
91+
if (!appUser || !appUser.uuid) {
92+
throw new Error("User session could not be established after DB operation.");
93+
}
94+
95+
const { hashedPassword, ...userToReturn } = appUser;
96+
97+
await createSessionForUser(userToReturn);
8498
await storeUserGithubOAuthToken(
85-
session.user.uuid,
99+
userToReturn.uuid,
86100
tokenData.access_token,
87101
tokenData.scope,
88102
tokenData.token_type,
89-
tokenData.refresh_token, // May not always be present
90-
tokenData.expires_in // May not always be present
103+
tokenData.refresh_token,
104+
tokenData.expires_in
91105
);
92-
console.log(`[GitHub OAuth Callback] Stored OAuth token for FlowUp user ${session.user.uuid}.`);
93-
94-
// Determine redirect URL from stored state
95-
let redirectPath = storedStateData.redirectTo || '/dashboard';
96-
if (storedStateData.projectUuid) {
97-
redirectPath = `/projects/${storedStateData.projectUuid}?tab=codespace&oauth_status=success`;
98-
} else {
99-
const redirectUrlObj = new URL(redirectPath, request.nextUrl.origin);
100-
redirectUrlObj.searchParams.set('oauth_status', 'success');
101-
redirectPath = `${redirectUrlObj.pathname}${redirectUrlObj.search}`;
102-
}
103106

104-
console.log(`[GitHub OAuth Callback] Redirecting to: ${redirectPath}`);
105-
return NextResponse.redirect(new URL(redirectPath, request.url));
107+
console.log(`[GitHub OAuth Callback] Successfully logged in/signed up user ${userToReturn.email}. Redirecting to dashboard.`);
108+
return NextResponse.redirect(new URL('/dashboard', request.url));
106109

107110
} catch (error: any) {
108111
console.error('[GitHub OAuth Callback] Error in callback:', error);
109-
return NextResponse.redirect(new URL(`/dashboard?error=oauth_callback_error&message=${encodeURIComponent(error.message || 'Unknown error')}`, request.url));
112+
return NextResponse.redirect(new URL(`/login?error=oauth_callback_error&message=${encodeURIComponent(error.message || 'Unknown error')}`, request.url));
110113
}
111114
}

src/lib/authService.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import * as bcrypt from 'bcryptjs';
66
import { createUser as dbCreateUser, getUserByEmail as dbGetUserByEmail, updateUserProfile as dbUpdateUserProfile, getUserByUuid as dbGetUserByUuid } from './db';
77
import jwt from 'jsonwebtoken';
88
import { cookies } from 'next/headers';
9+
import { auth } from '@/lib/authEdge';
10+
911

1012
const AUTH_COOKIE_NAME = 'flowup_auth_token';
1113

@@ -18,7 +20,7 @@ const getJwtSecretOrThrow = (): string => {
1820
return secret;
1921
};
2022

21-
const generateTokenAndSetCookie = (user: Omit<User, 'hashedPassword'>) => {
23+
export const createSessionForUser = async (user: Omit<User, 'hashedPassword'>) => {
2224
const JWT_SECRET = getJwtSecretOrThrow();
2325
const tokenPayload = {
2426
uuid: user.uuid,
@@ -33,12 +35,14 @@ const generateTokenAndSetCookie = (user: Omit<User, 'hashedPassword'>) => {
3335
maxAge: 60 * 60 * 24 * 7, // 7 days
3436
sameSite: 'lax',
3537
});
36-
console.log(`[authService.generateTokenAndSetCookie] Token generated and cookie set for user UUID: ${user.uuid}. Secure flag: ${process.env.NODE_ENV === 'production'}`);
38+
console.log(`[authService.createSessionForUser] Session created for user UUID: ${user.uuid}.`);
3739
} catch (error) {
38-
console.error("[authService.generateTokenAndSetCookie] Error setting cookie:", error);
40+
console.error("[authService.createSessionForUser] Error setting cookie:", error);
41+
throw new Error("Could not create user session.");
3942
}
4043
};
4144

45+
4246
export const login = async (email: string, password?: string): Promise<User | null> => {
4347
console.log('[authService.login] Attempting login for email:', email);
4448
if (!password) {
@@ -54,7 +58,7 @@ export const login = async (email: string, password?: string): Promise<User | nu
5458
const isValidPassword = await bcrypt.compare(password, userFromDb.hashedPassword);
5559
if (isValidPassword) {
5660
const { hashedPassword, ...userToReturn } = userFromDb;
57-
generateTokenAndSetCookie(userToReturn);
61+
await createSessionForUser(userToReturn);
5862
console.log('[authService.login] Login SUCCESSFUL for email:', email);
5963
return userToReturn;
6064
}
@@ -81,7 +85,7 @@ export const signup = async (name: string, email: string, password?: string, rol
8185
const newUser = await dbCreateUser(name, email, password, role);
8286
if (newUser) {
8387
const { hashedPassword, ...userToReturn } = newUser;
84-
generateTokenAndSetCookie(userToReturn);
88+
await createSessionForUser(userToReturn);
8589
console.log('[authService.signup] Signup SUCCESSFUL for email:', email, 'UUID:', userToReturn.uuid);
8690
return userToReturn;
8791
}
@@ -116,7 +120,7 @@ export const updateUserProfile = async (uuid: string, name: string, email: strin
116120
const session = await auth();
117121
if (session?.user?.uuid === userToReturn.uuid) {
118122
console.log('[authService.updateUserProfile] User data changed, re-issuing token for UUID:', userToReturn.uuid);
119-
generateTokenAndSetCookie(userToReturn);
123+
await createSessionForUser(userToReturn);
120124
}
121125
return userToReturn;
122126
}
@@ -138,6 +142,3 @@ export const getCurrentUserSession = async (): Promise<User | null> => {
138142
console.log('[authService.getCurrentUserSession] No active session found via auth().');
139143
return null;
140144
};
141-
142-
import { auth } from '@/lib/authEdge';
143-

0 commit comments

Comments
 (0)