Skip to content
Merged
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
49 changes: 33 additions & 16 deletions backend/controllers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
}
Expand All @@ -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),
})
}

Expand All @@ -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{
Expand All @@ -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),
})
}

Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 3 additions & 1 deletion backend/utils/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"+
"<p>Your verification code is: <strong>%s</strong></p>\r\n",
"<h2>Welcome to ArgueHub!</h2>\r\n"+
"<p>Your verification code is: <strong>%s</strong></p>\r\n"+
"<p><em>This code will expire in 24 hours.</em></p>\r\n",
email, cfg.SMTP.SenderName, cfg.SMTP.SenderEmail, code))

addr := fmt.Sprintf("%s:%d", cfg.SMTP.Host, cfg.SMTP.Port)
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/Pages/Authentication/forms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const SignUpForm: React.FC<SignUpFormProps> = ({ startOtpVerification })

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (password !== confirmPassword) {
authContext.handleError('Passwords do not match');
return;
Expand Down Expand Up @@ -318,7 +318,7 @@ export const ResetPasswordForm: React.FC<ResetPasswordFormProps> = ({ email, han

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (newPassword !== confirmNewPassword) {
authContext.handleError('Passwords do not match');
return;
Expand Down
43 changes: 35 additions & 8 deletions frontend/src/context/authContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down