diff --git a/backend/controllers/auth.go b/backend/controllers/auth.go index a8d594d..c404fbf 100644 --- a/backend/controllers/auth.go +++ b/backend/controllers/auth.go @@ -157,7 +157,7 @@ func SignUp(ctx *gin.Context) { // Generate verification code verificationCode := utils.GenerateRandomCode(6) - // Create new user + // Create new user (unverified) now := time.Now() newUser := models.User{ Email: request.Email, @@ -172,9 +172,9 @@ func SignUp(ctx *gin.Context) { Password: string(hashedPassword), IsVerified: false, VerificationCode: verificationCode, - Score: 0, // Initialize gamification score - Badges: []string{}, // Initialize badges array - CurrentStreak: 0, // Initialize streak + Score: 0, + Badges: []string{}, + CurrentStreak: 0, CreatedAt: now, UpdatedAt: now, } @@ -194,10 +194,9 @@ func SignUp(ctx *gin.Context) { return } - // Return user details + // Return success response ctx.JSON(200, gin.H{ "message": "Sign-up successful. Please verify your email.", - "user": buildUserResponse(newUser), }) } @@ -213,15 +212,25 @@ func VerifyEmail(ctx *gin.Context) { return } + // Find user with matching email and verification code dbCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var user models.User - err := db.MongoDatabase.Collection("users").FindOne(dbCtx, bson.M{"email": request.Email, "verificationCode": request.ConfirmationCode}).Decode(&user) + err := db.MongoDatabase.Collection("users").FindOne(dbCtx, bson.M{ + "email": request.Email, + "verificationCode": request.ConfirmationCode, + }).Decode(&user) if err != nil { ctx.JSON(400, gin.H{"error": "Invalid email or verification code"}) return } + // Check if verification code is expired (24 hours) + if time.Since(user.CreatedAt) > 24*time.Hour { + ctx.JSON(400, gin.H{"error": "Verification code expired. Please sign up again."}) + return + } + // Update user verification status now := time.Now() update := bson.M{ @@ -237,15 +246,23 @@ func VerifyEmail(ctx *gin.Context) { return } - // Return updated user details + // Update local user object + user.IsVerified = true + user.VerificationCode = "" + user.UpdatedAt = now + + // Generate JWT for immediate login + token, err := generateJWT(user.Email, cfg.JWT.Secret, cfg.JWT.Expiry) + if err != nil { + ctx.JSON(500, gin.H{"error": "Failed to generate token", "message": err.Error()}) + return + } + + // Return user details and access token ctx.JSON(200, gin.H{ - "message": "Email verification successful", - "user": func() gin.H { - response := buildUserResponse(user) - response["isVerified"] = true - response["updatedAt"] = now.Format(time.RFC3339) - return response - }(), + "message": "Email verification successful. You are now logged in.", + "accessToken": token, + "user": buildUserResponse(user), }) } @@ -278,7 +295,7 @@ func Login(ctx *gin.Context) { } // Check if user is verified - if !user.IsVerified && user.Password == "" { + if !user.IsVerified { ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Email not verified"}) return } diff --git a/backend/utils/email.go b/backend/utils/email.go index 7700d3d..3e0be3d 100644 --- a/backend/utils/email.go +++ b/backend/utils/email.go @@ -41,7 +41,9 @@ func SendVerificationEmail(email, code string) error { "MIME-Version: 1.0\r\n"+ "Content-Type: text/html; charset=\"UTF-8\"\r\n"+ "\r\n"+ - "

Your verification code is: %s

\r\n", + "

Welcome to ArgueHub!

\r\n"+ + "

Your verification code is: %s

\r\n"+ + "

This code will expire in 24 hours.

\r\n", email, cfg.SMTP.SenderName, cfg.SMTP.SenderEmail, code)) addr := fmt.Sprintf("%s:%d", cfg.SMTP.Host, cfg.SMTP.Port) diff --git a/frontend/src/Pages/Authentication/forms.tsx b/frontend/src/Pages/Authentication/forms.tsx index d2cf147..6345d93 100644 --- a/frontend/src/Pages/Authentication/forms.tsx +++ b/frontend/src/Pages/Authentication/forms.tsx @@ -118,7 +118,7 @@ export const SignUpForm: React.FC = ({ startOtpVerification }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (password !== confirmPassword) { authContext.handleError('Passwords do not match'); return; @@ -318,7 +318,7 @@ export const ResetPasswordForm: React.FC = ({ email, han const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - + if (newPassword !== confirmNewPassword) { authContext.handleError('Passwords do not match'); return; diff --git a/frontend/src/context/authContext.tsx b/frontend/src/context/authContext.tsx index bd4e6ef..46f51ab 100644 --- a/frontend/src/context/authContext.tsx +++ b/frontend/src/context/authContext.tsx @@ -194,15 +194,42 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => { if (!response.ok) { const data = await response.json(); - throw new Error(data.message || 'Verification failed'); + throw new Error(data.error || 'Verification failed'); + } + + const data = await response.json(); + + // User is now verified and logged in + if (data.accessToken) { + setToken(data.accessToken); + localStorage.setItem('token', data.accessToken); + + // Set user details + const normalizedUser: User = { + id: data.user?.id || data.user?._id || undefined, + email: data.user?.email || email, + displayName: data.user?.displayName || 'User', + bio: data.user?.bio || '', + rating: data.user?.rating || 1200, + rd: data.user?.rd || 350, + volatility: data.user?.volatility || 0.06, + lastRatingUpdate: data.user?.lastRatingUpdate || new Date().toISOString(), + avatarUrl: data.user?.avatarUrl || 'https://avatar.iran.liara.run/public/10', + twitter: data.user?.twitter || undefined, + instagram: data.user?.instagram || undefined, + linkedin: data.user?.linkedin || undefined, + password: '', + nickname: data.user?.nickname || 'User', + isVerified: true, + verificationCode: undefined, + resetPasswordCode: undefined, + createdAt: data.user?.createdAt || new Date().toISOString(), + updatedAt: data.user?.updatedAt || new Date().toISOString(), + }; + setUser(normalizedUser); + localStorage.setItem(USER_CACHE_KEY, JSON.stringify(normalizedUser)); + navigate('/'); } - // Optionally update userAtom with isVerified: true - setUser((prev: User | null) => { - if (!prev) return null; - const updatedUser = { ...prev, isVerified: true, verificationCode: undefined }; - localStorage.setItem(USER_CACHE_KEY, JSON.stringify(updatedUser)); - return updatedUser; - }); } catch (error) { handleError(error); } finally {