diff --git a/backend/src/controllers/authController.ts b/backend/src/controllers/authController.ts index e8891cd..9bdb0e7 100644 --- a/backend/src/controllers/authController.ts +++ b/backend/src/controllers/authController.ts @@ -1,47 +1,41 @@ import type { Request, Response, NextFunction } from "express"; import bcrypt from "bcryptjs"; -import jwt from "jsonwebtoken"; // <-- ADDED +import jwt from "jsonwebtoken"; import User, { type IUser } from "../models/userModel.js"; import { generateToken, - generateAccessToken, // <-- RENAMED/UPDATED - generateRefreshToken, // <-- ADDED + generateRefreshToken, } from "../utils/generateToken.js"; import { userSchema, loginSchema } from "../utils/validateInputs.js"; import dotenv from "dotenv"; - import { Session } from "../models/sessionModel.js"; dotenv.config(); + const asTypedUser = (user: any): IUser & { _id: string } => user as IUser & { _id: string }; -// A helper function to send tokens -const sendTokens = (res: Response, user: IUser & { _id: string }) => { - const accessToken = generateAccessToken(user._id.toString()); - const newRefreshToken = generateRefreshToken(user._id.toString()); - - // Update user's refresh tokens in DB - // Only one refresh token per user is supported (single device). - user.refreshTokens = [newRefreshToken]; - - // Set refresh token in secure httpOnly cookie - res.cookie("jwt", newRefreshToken, { - httpOnly: true, - secure: process.env.NODE_ENV !== "development", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days (matches token expiry) - }); +const cookieOptions = { + httpOnly: true, + secure: process.env.NODE_ENV !== "development", + sameSite: "strict" as const, + maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days +}; + +async function createSessionForAccessToken(userId: string, token: string) { + const decoded = jwt.decode(token) as { exp?: number } | null; + const expiresAt = decoded?.exp + ? new Date(decoded.exp * 1000) + : new Date(Date.now() + 15 * 60 * 1000); - // Send access token in response body - res.json({ - success: true, - message: "Login successful", - accessToken: accessToken, + await Session.create({ + userId, + token, + expiresAt, }); -}; +} -// ✅ SIGNUP CONTROLLER (Updated) +//signup controller export const registerUser = async ( req: Request, res: Response, @@ -70,31 +64,29 @@ export const registerUser = async ( const newUser = await User.create({ name, email, password: hashedPassword }); const typedUser = asTypedUser(newUser); - const token = generateToken(typedUser._id.toString()); -const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { exp?: number }; + // Generate tokens + const accessToken = generateToken(typedUser._id.toString()); + const refreshToken = generateRefreshToken(typedUser._id.toString()); -if (!decoded || !decoded.exp) { - throw new Error("Invalid token format or missing expiration"); -} + typedUser.refreshTokens = [refreshToken]; + await typedUser.save(); + + res.cookie("jwt", refreshToken, cookieOptions); -const expiresAt = new Date(decoded.exp * 1000); -await Session.create({ - userId: typedUser._id, - token, - expiresAt, -}); + //a session document + await createSessionForAccessToken(typedUser._id.toString(), accessToken); res.status(201).json({ success: true, message: "User registered successfully", - token, + accessToken, }); } catch (err) { next(err); } }; -// ✅ LOGIN CONTROLLER (Updated) +//login controller export const loginUser = async ( req: Request, res: Response, @@ -132,40 +124,31 @@ export const loginUser = async ( const typedUser = asTypedUser(foundUser); - const token = generateToken(typedUser._id.toString()); -const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { exp?: number }; - -if (!decoded.exp) { - throw new Error("Token missing expiration claim"); -} + // Generate tokens + const accessToken = generateToken(typedUser._id.toString()); + const refreshToken = generateRefreshToken(typedUser._id.toString()); -const expiresAt = new Date(decoded.exp * 1000); + // Store refresh token + typedUser.refreshTokens = [refreshToken]; + await typedUser.save(); -await Session.create({ - userId: typedUser._id, - token, - expiresAt, -}); + // Set cookie + res.cookie("jwt", refreshToken, cookieOptions); + // Create session + await createSessionForAccessToken(typedUser._id.toString(), accessToken); - res.json({ + return res.json({ success: true, message: "Login successful", - token, + accessToken, }); - - // Redirect to the frontend without passing the token in the URL - const redirectUrl = `${process.env.FRONTEND_URL}/auth-success`; - res.redirect(redirectUrl); - } catch (err) { next(err); } }; -// ... (keep all other functions as they are) - -// ✅ REFRESH TOKEN CONTROLLER (Updated with fixes) +//refresh token controller export const handleRefreshToken = async ( req: Request, res: Response, @@ -181,11 +164,14 @@ export const handleRefreshToken = async ( const refreshToken = cookies.jwt; // Clear the old cookie immediately - res.clearCookie("jwt", { httpOnly: true, sameSite: "strict", secure: process.env.NODE_ENV !== "development" }); + res.clearCookie("jwt", { + httpOnly: true, + sameSite: "strict", + secure: process.env.NODE_ENV !== "development", + }); const foundUser = await User.findOne({ refreshTokens: refreshToken }); - // Detected refresh token reuse! if (!foundUser) { try { const decoded = jwt.verify( @@ -193,15 +179,13 @@ export const handleRefreshToken = async ( process.env.JWT_REFRESH_SECRET as string ) as { id: string }; - // We know who the user is, now we hack-proof them - // by deleting all their refresh tokens const compromisedUser = await User.findById(decoded.id); if (compromisedUser) { compromisedUser.refreshTokens = []; await compromisedUser.save(); } } catch (err) { - // Token was invalid in the first place + // nothing to do } finally { return res .status(403) @@ -209,48 +193,35 @@ export const handleRefreshToken = async ( } } - // Valid token, let's rotate it const typedUser = asTypedUser(foundUser); try { - // Verify the token is still valid + // Verify jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET as string) as { id: string; }; // Generate new tokens - const newAccessToken = generateAccessToken(typedUser._id.toString()); + const newAccessToken = generateToken(typedUser._id.toString()); const newRefreshToken = generateRefreshToken(typedUser._id.toString()); - // ================== - // FIX #1 - // ================== - // Filter out the old token and default to an empty array const otherRefreshTokens = typedUser.refreshTokens?.filter((rt) => rt !== refreshToken) || []; - // Assign the new array (filtered list + new token) typedUser.refreshTokens = [...otherRefreshTokens, newRefreshToken]; await typedUser.save(); - // Send new tokens - res.cookie("jwt", newRefreshToken, { - httpOnly: true, - secure: process.env.NODE_ENV !== "development", - sameSite: "strict", - maxAge: 7 * 24 * 60 * 60 * 1000, - }); + //new refresh cookie + res.cookie("jwt", newRefreshToken, cookieOptions); + + //new session + await createSessionForAccessToken(typedUser._id.toString(), newAccessToken); - res.json({ + return res.json({ success: true, accessToken: newAccessToken, }); } catch (err) { - // Token expired or invalid - // ================== - // FIX #2 - // ================== - // Clear out the bad token, default to an empty array typedUser.refreshTokens = typedUser.refreshTokens?.filter((rt) => rt !== refreshToken) || []; await typedUser.save(); @@ -264,8 +235,7 @@ export const handleRefreshToken = async ( } }; - -// ✅ LOGOUT CONTROLLER (Updated with fix) +//logout controller export const logoutUser = async ( req: Request, res: Response, @@ -273,40 +243,39 @@ export const logoutUser = async ( ) => { try { const cookies = req.cookies; - if (!cookies?.jwt) { - return res.sendStatus(204); // No cookie, already logged out - } - - const refreshToken = cookies.jwt; - - // Find user and remove this specific refresh token - const foundUser = await User.findOne({ refreshTokens: refreshToken }); - if (foundUser) { - foundUser.refreshTokens = - foundUser.refreshTokens?.filter((rt) => rt !== refreshToken) || []; + const authHeader = req.headers.authorization; + const accessToken = authHeader?.split(" ")[1]; + + // If cookie exists- remove refresh token + if (cookies?.jwt) { + const refreshToken = cookies.jwt; + const foundUser = await User.findOne({ refreshTokens: refreshToken }); + if (foundUser) { + foundUser.refreshTokens = + foundUser.refreshTokens?.filter((rt) => rt !== refreshToken) || []; + await foundUser.save(); + } - await foundUser.save(); + res.clearCookie("jwt", { + httpOnly: true, + sameSite: "strict", + secure: process.env.NODE_ENV !== "development", + }); + } + if (accessToken) { + await Session.deleteOne({ token: accessToken }); } - // Clear the cookie - res.clearCookie("jwt", { - httpOnly: true, - sameSite: "strict", - secure: process.env.NODE_ENV !== "development", - }); - - res.status(200).json({ success: true, message: "Logged out successfully" }); + return res.status(200).json({ success: true, message: "Logged out successfully" }); } catch (err) { next(err); } }; -// ✅ GET PROFILE CONTROLLER (Unchanged, but for completeness) + //get profile controller export const getUserProfile = async (req: Request, res: Response) => { try { - // req.userId comes from the 'protect' middleware - // @ts-ignore - const user = await User.findById(req.userId).select("-password -refreshTokens"); + const user = await User.findById((req as any).userId).select("-password -refreshTokens"); if (!user) { return res.status(404).json({ diff --git a/backend/src/middleware/authMiddleware.ts b/backend/src/middleware/authMiddleware.ts index 8ba88e9..1115c78 100644 --- a/backend/src/middleware/authMiddleware.ts +++ b/backend/src/middleware/authMiddleware.ts @@ -1,12 +1,11 @@ import type { Request, Response, NextFunction } from "express"; import jwt from "jsonwebtoken"; -import { Session } from "../models/sessionModel.js"; +import { Session } from "../models/sessionModel.js"; interface AuthRequest extends Request { userId?: string; } - export const protect = async (req: AuthRequest, res: Response, next: NextFunction) => { try { const token = req.headers.authorization?.split(" ")[1]; @@ -17,10 +16,8 @@ export const protect = async (req: AuthRequest, res: Response, next: NextFunctio }); } - - const decoded = jwt.verify(token, process.env.JWT_SECRET as string) as { id: string }; + const decoded = jwt.verify(token, process.env.JWT_ACCESS_SECRET as string) as { id: string }; - const activeSession = await Session.findOne({ token }); if (!activeSession) { return res.status(401).json({ @@ -29,7 +26,7 @@ export const protect = async (req: AuthRequest, res: Response, next: NextFunctio }); } - + // If expired then remove it if (activeSession.expiresAt < new Date()) { await Session.deleteOne({ token }); return res.status(401).json({ @@ -46,4 +43,4 @@ export const protect = async (req: AuthRequest, res: Response, next: NextFunctio message: "Invalid or expired token", }); } -}; +}; \ No newline at end of file diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts index ce6130b..f02e067 100644 --- a/backend/src/routes/authRoutes.ts +++ b/backend/src/routes/authRoutes.ts @@ -1,58 +1,123 @@ import express from "express"; -import { registerUser, loginUser, getUserProfile, logoutUser,handleRefreshToken} from "../controllers/authController.js"; +import { + registerUser, + loginUser, + getUserProfile, + logoutUser, + handleRefreshToken, +} from "../controllers/authController.js"; import passport from "passport"; import { Session } from "../models/sessionModel.js"; -import {protect} from "../middleware/authMiddleware.js"; +import { protect } from "../middleware/authMiddleware.js"; +import { generateToken, generateRefreshToken } from "../utils/generateToken.js"; +import jwt from "jsonwebtoken"; +import dotenv from "dotenv"; +import User, { type IUser } from "../models/userModel.js"; + +dotenv.config(); const router = express.Router(); +const FRONTEND_URL = process.env.FRONTEND_URL || "http://localhost:5173"; -// Auth +// REST endpoints router.post("/signup", registerUser); router.post("/signin", loginUser); -router.post("/logout", logoutUser); // <-- ADDED -router.get("/refresh", handleRefreshToken); // <-- ADDED +router.post("/logout", logoutUser); +router.get("/refresh", handleRefreshToken); router.get("/me", protect, getUserProfile); -router.post("/logout", protect, async (req, res) => { - try { - const token = req.headers.authorization?.split(" ")[1]; - if (!token) - return res.status(400).json({ success: false, message: "Token missing" }); - - // Delete session for this token - const result = await Session.deleteOne({ token }); - - if (result.deletedCount === 0) { - return res.status(404).json({ success: false, message: "Session not found or already logged out" }); - } - - res.json({ success: true, message: "Logged out successfully" }); - } catch (error) { - console.error("❌ Logout error:", error); - res.status(500).json({ success: false, message: "Server error during logout" }); - } -}); - -// Google OAuth +// OAuth - Google router.get( "/google", passport.authenticate("google", { scope: ["profile", "email"], session: false }) ); + router.get( "/google/callback", - passport.authenticate("google", { session: false, failureRedirect: process.env.FRONTEND_URL ? `${process.env.FRONTEND_URL}/signin` : "/signin" }), - + passport.authenticate("google", { session: false, failureRedirect: `${FRONTEND_URL}/signin` }), + async (req, res) => { + try { + const user = req.user as (IUser & { _id: string }) | undefined; + if (!user) { + return res.redirect(`${FRONTEND_URL}/signin`); + } + + const accessToken = generateToken(user._id.toString()); + const refreshToken = generateRefreshToken(user._id.toString()); + + user.refreshTokens = [refreshToken]; + await user.save(); + + // Create session for access token + const decoded = jwt.decode(accessToken) as { exp?: number } | null; + const expiresAt = decoded?.exp ? new Date(decoded.exp * 1000) : new Date(Date.now() + 15 * 60 * 1000); + await Session.create({ + userId: user._id, + token: accessToken, + expiresAt, + }); + + // Set refresh cookie + res.cookie("jwt", refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV !== "development", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, + }); + + // Redirect to success page on frontend + return res.redirect(`${FRONTEND_URL}/oauth-success`); + } catch (err) { + console.error("Google callback error:", err); + return res.redirect(`${FRONTEND_URL}/signin`); + } + } ); -// GitHub OAuth +// OAuth - GitHub router.get( "/github", passport.authenticate("github", { scope: ["user:email"], session: false }) ); + router.get( "/github/callback", - passport.authenticate("github", { session: false, failureRedirect: process.env.FRONTEND_URL ? `${process.env.FRONTEND_URL}/signin` : "/signin" }), - + passport.authenticate("github", { session: false, failureRedirect: `${FRONTEND_URL}/signin` }), + async (req, res) => { + try { + const user = req.user as (IUser & { _id: string }) | undefined; + if (!user) { + return res.redirect(`${FRONTEND_URL}/signin`); + } + const accessToken = generateToken(user._id.toString()); + const refreshToken = generateRefreshToken(user._id.toString()); + + user.refreshTokens = [refreshToken]; + await user.save(); + + // Create session for access token + const decoded = jwt.decode(accessToken) as { exp?: number } | null; + const expiresAt = decoded?.exp ? new Date(decoded.exp * 1000) : new Date(Date.now() + 15 * 60 * 1000); + await Session.create({ + userId: user._id, + token: accessToken, + expiresAt, + }); + + // Set refresh cookie + res.cookie("jwt", refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV !== "development", + sameSite: "strict", + maxAge: 7 * 24 * 60 * 60 * 1000, + }); + + return res.redirect(`${FRONTEND_URL}/oauth-success`); + } catch (err) { + console.error("GitHub callback error:", err); + return res.redirect(`${FRONTEND_URL}/signin`); + } + } ); export default router; \ No newline at end of file diff --git a/backend/src/utils/generateToken.ts b/backend/src/utils/generateToken.ts index 14a70bd..065be8f 100644 --- a/backend/src/utils/generateToken.ts +++ b/backend/src/utils/generateToken.ts @@ -1,40 +1,32 @@ import jwt from "jsonwebtoken"; -import type { SignOptions } from "jsonwebtoken"; -import dotenv from 'dotenv'; +import type { SignOptions, Secret } from "jsonwebtoken"; +import dotenv from "dotenv"; + dotenv.config(); -const accessTokenSecret = process.env.JWT_ACCESS_SECRET; -const refreshTokenSecret = process.env.JWT_REFRESH_SECRET; -export const generateToken = (userId: string) => { - const expiresIn = "7d"; - const token = jwt.sign({ id: userId }, process.env.JWT_SECRET as string, { - expiresIn, - }); - return token; -}; +const accessTokenSecret = process.env.JWT_ACCESS_SECRET as Secret; +const refreshTokenSecret = process.env.JWT_REFRESH_SECRET as Secret; -const parseExpiration = (val: string | undefined, fallback: number | string): number | string => { - if (!val) return fallback; - const trimmed = val.trim(); - return /^\d+$/.test(trimmed) ? Number(trimmed) : trimmed; -}; +function parseExpiration(value: string | undefined, fallback: string): any { + return value && value.trim().length > 0 ? value : fallback; +} -export const generateAccessToken = (id: string) => { - if (!accessTokenSecret) throw new Error("JWT_ACCESS_SECRET is not defined"); +export const generateToken = (userId: string) => { + if (!accessTokenSecret) throw new Error("JWT_ACCESS_SECRET is not defined"); - const options = { - expiresIn: parseExpiration(process.env.JWT_ACCESS_EXPIRATION, 900), - } as SignOptions; + const options: SignOptions = { + expiresIn: parseExpiration(process.env.JWT_ACCESS_EXPIRES, "15m") as any, + }; - return jwt.sign({ id }, accessTokenSecret, options); + return jwt.sign({ id: userId }, accessTokenSecret, options); }; -export const generateRefreshToken = (id: string) => { - if (!refreshTokenSecret) throw new Error("JWT_REFRESH_SECRET is not defined"); +export const generateRefreshToken = (userId: string) => { + if (!refreshTokenSecret) throw new Error("JWT_REFRESH_SECRET is not defined"); - const options = { - expiresIn: parseExpiration(process.env.JWT_REFRESH_EXPIRATION, "7d"), - } as SignOptions; + const options: SignOptions = { + expiresIn: parseExpiration(process.env.JWT_REFRESH_EXPIRES, "7d") as any, + }; - return jwt.sign({ id }, refreshTokenSecret, options); -}; \ No newline at end of file + return jwt.sign({ id: userId }, refreshTokenSecret, options); +}; diff --git a/backend/src/utils/passport.ts b/backend/src/utils/passport.ts index d1d9bef..eff7a82 100644 --- a/backend/src/utils/passport.ts +++ b/backend/src/utils/passport.ts @@ -3,7 +3,6 @@ import { Strategy as GoogleStrategy } from "passport-google-oauth20"; import { Strategy as GitHubStrategy } from "passport-github2"; import dotenv from "dotenv"; import User, { type IUser } from "../models/userModel.js"; -import type { Document } from "mongoose"; dotenv.config(); @@ -15,7 +14,7 @@ const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID!; const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET!; const GITHUB_CALLBACK_URL = process.env.GITHUB_CALLBACK_URL!; -//google strategy +// Google strategy passport.use( new GoogleStrategy( { @@ -58,7 +57,8 @@ passport.use( } ) ); -//github strategy + +// GitHub strategy passport.use( new GitHubStrategy( { @@ -109,7 +109,8 @@ passport.use( } ) ); -// Serialize and deserialize user + +// Serialize / deserialize passport.serializeUser((user: any, done) => { done(null, user?._id?.toString() || undefined); }); @@ -123,4 +124,4 @@ passport.deserializeUser(async (id: string, done) => { } }); -export default passport; +export default passport; \ No newline at end of file diff --git a/frontend/src/pages/OAuthSuccess.tsx b/frontend/src/pages/OAuthSuccess.tsx index 13f2c4f..cacc124 100644 --- a/frontend/src/pages/OAuthSuccess.tsx +++ b/frontend/src/pages/OAuthSuccess.tsx @@ -1,32 +1,90 @@ import React, { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; +const API_URL = (import.meta.env.VITE_API_URL as string) || "http://localhost:3000"; + const OAuthSuccess: React.FC = () => { const navigate = useNavigate(); const [message, setMessage] = useState("Finalizing sign in..."); + const extractTokenFromUrl = (): string | null => { + try { + const urlParams = new URLSearchParams(window.location.search); + const qToken = urlParams.get("token") || urlParams.get("accessToken") || urlParams.get("authToken"); + if (qToken) return qToken; + + const hash = window.location.hash || ""; + const fragment = hash.startsWith("#") ? hash.slice(1) : hash; + const fragParams = new URLSearchParams(fragment); + const hToken = fragParams.get("token") || fragParams.get("accessToken") || fragParams.get("authToken"); + if (hToken) return hToken; + } catch (err) { + // ignore + } + return null; + }; + useEffect(() => { - const hash = window.location.hash || ""; - const fragment = hash.startsWith("#") ? hash.slice(1) : hash; - const params = new URLSearchParams(fragment); - const token = params.get("token"); + (async () => { + //If backend included token in URL prefer that. + const tokenFromUrl = extractTokenFromUrl(); + if (tokenFromUrl) { + try { + localStorage.setItem("token", tokenFromUrl); + setMessage("Sign in successful — redirecting..."); + setTimeout(() => navigate("/", { replace: true }), 700); + return; + } catch (err) { + console.warn("Unable to persist token from URL:", err); + setMessage("Signed in but could not persist session locally. Redirecting..."); + setTimeout(() => navigate("/", { replace: true }), 1200); + return; + } + } - if (token) { + //Otherwise call refresh endpoint try { - localStorage.setItem("token", token); + const res = await fetch(`${API_URL}/api/auth/refresh`, { + method: "GET", + credentials: "include", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + }); + + if (!res.ok) { + const body = await res.json().catch(() => null); + const errMsg = body?.message || `Failed to refresh token (status ${res.status})`; + setMessage(errMsg); + // redirect to signin + setTimeout(() => navigate("/signin"), 1400); + return; + } + + const data = await res.json().catch(() => null); + const token = data?.accessToken || data?.token || null; + + if (!token) { + setMessage("No access token returned from server. Please try signing in again."); + setTimeout(() => navigate("/signin"), 1300); + return; + } + + try { + localStorage.setItem("token", token); + } catch (err) { + console.warn("Failed to persist access token:", err); + } + setMessage("Sign in successful — redirecting..."); - setTimeout(() => { - navigate("/", { replace: true }); - }, 700); + setTimeout(() => navigate("/", { replace: true }), 700); } catch (err) { - console.error("Failed to save token", err); - setMessage("Sign in succeeded but we couldn't save the session locally."); - setTimeout(() => navigate("/signin"), 1500); + console.error("Network error while refreshing token:", err); + setMessage("Network error finalizing sign-in. Please try again."); + setTimeout(() => navigate("/signin"), 1400); } - } else { - setMessage("No token found in redirect. Please try signing in again."); - setTimeout(() => navigate("/signin"), 1300); - } + })(); }, [navigate]); return ( diff --git a/frontend/src/pages/Signin.tsx b/frontend/src/pages/Signin.tsx index 05a3822..f2b09c8 100644 --- a/frontend/src/pages/Signin.tsx +++ b/frontend/src/pages/Signin.tsx @@ -14,13 +14,14 @@ import { import { InputField } from "../components/InputField"; import { toast } from "../hooks/use-toast"; import { Loader2 } from "lucide-react"; -import SocialLogin from "../components/SocialLogin"; interface SignInData { email: string; password: string; } +const API_URL = (import.meta.env.VITE_API_URL as string) || "http://localhost:3000"; + const SignIn = () => { const [isLoading, setIsLoading] = useState(false); const navigate = useNavigate(); @@ -35,9 +36,9 @@ const SignIn = () => { setIsLoading(true); try { const response = await axios.post( - "http://localhost:3000/api/auth/signin", + `${API_URL}/api/auth/signin`, { email: data.email, password: data.password }, - { headers: { "Content-Type": "application/json" } } + { headers: { "Content-Type": "application/json" }, withCredentials: true } ); toast({ @@ -45,8 +46,9 @@ const SignIn = () => { description: response.data.message || "Login successful", }); - if (response.data.token) { - localStorage.setItem("token", response.data.token); + const token = response.data.accessToken || response.data.token || null; + if (token) { + localStorage.setItem("token", token); } reset(); @@ -55,8 +57,7 @@ const SignIn = () => { toast({ title: "Error", description: - error.response?.data?.message || - "Login failed. Please try again.", + error.response?.data?.message || "Login failed. Please try again.", variant: "destructive", }); } finally { @@ -64,6 +65,15 @@ const SignIn = () => { } }; + // Launch OAuth + const startGoogle = () => { + window.location.href = `${API_URL}/api/auth/google`; + }; + + const startGithub = () => { + window.location.href = `${API_URL}/api/auth/github`; + }; + return (
+ You will be redirected to the provider to complete sign-in. +