completionId: cgen-f0e5498443774fa8af7b48894e4726fc
cgen-f0e5498443774fa8af7b48894e4726fc
This commit is contained in:
parent
91403037b9
commit
e75bde98e9
1 changed files with 184 additions and 68 deletions
252
server/index.ts
252
server/index.ts
|
|
@ -801,11 +801,60 @@ export function createServer() {
|
||||||
app.get("/api/discord/oauth/callback", async (req, res) => {
|
app.get("/api/discord/oauth/callback", async (req, res) => {
|
||||||
const code = req.query.code as string;
|
const code = req.query.code as string;
|
||||||
const state = req.query.state as string;
|
const state = req.query.state as string;
|
||||||
|
const error = req.query.error as string;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.redirect(`/login?error=${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
return res
|
return res.redirect("/login?error=no_code");
|
||||||
.status(400)
|
}
|
||||||
.json({ error: "Authorization code is required" });
|
|
||||||
|
// Parse state to determine if this is a linking or login flow
|
||||||
|
let isLinkingFlow = false;
|
||||||
|
let redirectTo = "/dashboard";
|
||||||
|
let authenticatedUserId: string | null = null;
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
try {
|
||||||
|
const stateData = JSON.parse(decodeURIComponent(state));
|
||||||
|
isLinkingFlow = stateData.action === "link";
|
||||||
|
redirectTo = stateData.redirectTo || redirectTo;
|
||||||
|
|
||||||
|
if (isLinkingFlow && stateData.sessionToken) {
|
||||||
|
// Look up the linking session to get the user ID
|
||||||
|
const { data: session, error: sessionError } = await adminSupabase
|
||||||
|
.from("discord_linking_sessions")
|
||||||
|
.select("user_id")
|
||||||
|
.eq("session_token", stateData.sessionToken)
|
||||||
|
.gt("expires_at", new Date().toISOString())
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (sessionError || !session) {
|
||||||
|
console.error(
|
||||||
|
"[Discord OAuth] Linking session not found or expired",
|
||||||
|
);
|
||||||
|
return res.redirect(
|
||||||
|
"/login?error=session_lost&message=Session%20expired.%20Please%20try%20linking%20Discord%20again.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticatedUserId = session.user_id;
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Linking session found, user_id:",
|
||||||
|
authenticatedUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clean up the temporary session
|
||||||
|
await adminSupabase
|
||||||
|
.from("discord_linking_sessions")
|
||||||
|
.delete()
|
||||||
|
.eq("session_token", stateData.sessionToken);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("[Discord OAuth] Could not parse state:", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -834,15 +883,9 @@ export function createServer() {
|
||||||
redirectUri,
|
redirectUri,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!clientSecret) {
|
if (!clientId || !clientSecret) {
|
||||||
console.warn(
|
console.error("[Discord OAuth] Missing client credentials");
|
||||||
"[Discord OAuth] DISCORD_CLIENT_SECRET not configured, skipping token exchange",
|
return res.redirect("/login?error=config");
|
||||||
);
|
|
||||||
return res.json({
|
|
||||||
ok: true,
|
|
||||||
access_token: null,
|
|
||||||
message: "Discord auth configured for Activity context only",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchange authorization code for access token
|
// Exchange authorization code for access token
|
||||||
|
|
@ -859,84 +902,157 @@ export function createServer() {
|
||||||
code,
|
code,
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
}),
|
}).toString(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!tokenResponse.ok) {
|
if (!tokenResponse.ok) {
|
||||||
const errorData = await tokenResponse.text();
|
const errorData = await tokenResponse.json();
|
||||||
console.error("[Discord OAuth] Token exchange failed:", {
|
console.error("[Discord OAuth] Token exchange failed:", errorData);
|
||||||
status: tokenResponse.status,
|
return res.redirect("/login?error=token_exchange");
|
||||||
error: errorData,
|
|
||||||
});
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "Failed to exchange authorization code",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenData = await tokenResponse.json();
|
const tokenData = await tokenResponse.json();
|
||||||
|
|
||||||
// Get Discord user information
|
// Get Discord user information
|
||||||
const userResponse = await fetch("https://discord.com/api/users/@me", {
|
const userResponse = await fetch(
|
||||||
headers: {
|
"https://discord.com/api/v10/users/@me",
|
||||||
Authorization: `Bearer ${tokenData.access_token}`,
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${tokenData.access_token}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
);
|
||||||
|
|
||||||
if (!userResponse.ok) {
|
if (!userResponse.ok) {
|
||||||
return res.status(400).json({
|
console.error(
|
||||||
error: "Failed to retrieve Discord user information",
|
"[Discord OAuth] User fetch failed:",
|
||||||
});
|
userResponse.status,
|
||||||
|
);
|
||||||
|
return res.redirect("/login?error=user_fetch");
|
||||||
}
|
}
|
||||||
|
|
||||||
const discordUser = await userResponse.json();
|
const discordUser = await userResponse.json();
|
||||||
|
|
||||||
// Optionally: create or update Supabase user linked to Discord account
|
if (!discordUser.email) {
|
||||||
if (adminSupabase && discordUser.id) {
|
console.error("[Discord OAuth] Discord user has no email");
|
||||||
try {
|
return res.redirect(
|
||||||
// Check if user with Discord ID exists
|
"/login?error=no_email&message=Please+enable+email+on+your+Discord+account",
|
||||||
const { data: existingUser } = await adminSupabase
|
);
|
||||||
.from("user_profiles")
|
}
|
||||||
.select("id")
|
|
||||||
.eq("discord_id", discordUser.id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (!existingUser && discordUser.email) {
|
// LINKING FLOW: Link Discord to authenticated user
|
||||||
// Attempt to find by email
|
if (isLinkingFlow && authenticatedUserId) {
|
||||||
const { data: userByEmail } = await adminSupabase
|
console.log(
|
||||||
.from("user_profiles")
|
"[Discord OAuth] Linking Discord to user:",
|
||||||
.select("id")
|
authenticatedUserId,
|
||||||
.eq("email", discordUser.email)
|
);
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (userByEmail) {
|
// Check if Discord ID is already linked to someone else
|
||||||
// Update existing user with Discord ID
|
const { data: existingLink } = await adminSupabase
|
||||||
await adminSupabase
|
.from("discord_links")
|
||||||
.from("user_profiles")
|
.select("user_id")
|
||||||
.update({ discord_id: discordUser.id })
|
.eq("discord_id", discordUser.id)
|
||||||
.eq("id", userByEmail.id);
|
.single();
|
||||||
}
|
|
||||||
}
|
if (existingLink && existingLink.user_id !== authenticatedUserId) {
|
||||||
} catch (err) {
|
console.error(
|
||||||
console.warn("[Discord OAuth] Failed to link Discord ID:", err);
|
"[Discord OAuth] Discord ID already linked to different user",
|
||||||
|
);
|
||||||
|
return res.redirect(
|
||||||
|
`/dashboard?error=already_linked&message=${encodeURIComponent("This Discord account is already linked to another AeThex account")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or update Discord link
|
||||||
|
const { error: linkError } = await adminSupabase
|
||||||
|
.from("discord_links")
|
||||||
|
.upsert({
|
||||||
|
discord_id: discordUser.id,
|
||||||
|
user_id: authenticatedUserId,
|
||||||
|
linked_at: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (linkError) {
|
||||||
|
console.error("[Discord OAuth] Link creation failed:", linkError);
|
||||||
|
return res.redirect(
|
||||||
|
`/dashboard?error=link_failed&message=${encodeURIComponent("Failed to link Discord account")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Successfully linked Discord:",
|
||||||
|
discordUser.id,
|
||||||
|
);
|
||||||
|
return res.redirect(redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOGIN FLOW: Check if Discord user already exists
|
||||||
|
const { data: existingLink } = await adminSupabase
|
||||||
|
.from("discord_links")
|
||||||
|
.select("user_id")
|
||||||
|
.eq("discord_id", discordUser.id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
let userId: string;
|
||||||
|
|
||||||
|
if (existingLink) {
|
||||||
|
// Discord ID already linked - use existing user
|
||||||
|
userId = existingLink.user_id;
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Discord ID already linked to user:",
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Check if email matches existing account
|
||||||
|
const { data: existingUserProfile } = await adminSupabase
|
||||||
|
.from("user_profiles")
|
||||||
|
.select("id")
|
||||||
|
.eq("email", discordUser.email)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (existingUserProfile) {
|
||||||
|
// Discord email matches existing user profile - link it
|
||||||
|
userId = existingUserProfile.id;
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Discord email matches existing user profile, linking Discord",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Discord email doesn't match any existing account
|
||||||
|
// Don't auto-create - ask user to sign in with email first
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Discord email not found in existing accounts, redirecting to sign in",
|
||||||
|
);
|
||||||
|
return res.redirect(
|
||||||
|
`/login?error=discord_no_match&message=${encodeURIComponent(`Discord email (${discordUser.email}) not found. Please sign in with your email account first, then link Discord from settings.`)}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json({
|
// Create Discord link
|
||||||
ok: true,
|
const { error: linkError } = await adminSupabase
|
||||||
access_token: tokenData.access_token,
|
.from("discord_links")
|
||||||
discord_user: {
|
.upsert({
|
||||||
id: discordUser.id,
|
discord_id: discordUser.id,
|
||||||
username: discordUser.username,
|
user_id: userId,
|
||||||
avatar: discordUser.avatar,
|
linked_at: new Date().toISOString(),
|
||||||
email: discordUser.email,
|
});
|
||||||
},
|
|
||||||
});
|
if (linkError) {
|
||||||
|
console.error("[Discord OAuth] Link creation failed:", linkError);
|
||||||
|
return res.redirect("/login?error=link_create");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discord is now linked! Redirect to login for user to sign in
|
||||||
|
console.log(
|
||||||
|
"[Discord OAuth] Discord linked successfully, redirecting to login",
|
||||||
|
);
|
||||||
|
return res.redirect(
|
||||||
|
`/login?discord_linked=true&email=${encodeURIComponent(discordUser.email)}`,
|
||||||
|
);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("[Discord OAuth] Error:", e);
|
console.error("[Discord OAuth] Callback error:", e);
|
||||||
return res.status(500).json({
|
return res.redirect("/login?error=unknown");
|
||||||
error: e?.message || "Internal server error during Discord OAuth",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue