enhance oauth for edge cases

This commit is contained in:
Krispire Admin
2026-06-02 15:21:32 +05:30
parent 0a348593b1
commit 59b3687e43
+28 -3
View File
@@ -178,8 +178,12 @@ var ErrOAuthUnavailable = errors.New("oauth service unavailable")
//
// 1. Existing linkage on (provider, provider_user_id) → log that user in.
// 2. Existing user with same email → link this provider to them, log in.
// 3. Otherwise create a new user (no password, email_verified iff provider
// said so) and link.
// 3. Otherwise create a new user (no password, email_verified) and link.
//
// Every successful OAuth round-trip is treated as proof the user controls
// the email address — Google + Facebook both verify email ownership before
// returning it to us, so we always mark the email as verified on sign-in
// regardless of the provider's `email_verified` flag.
//
// In every case the resulting user gets a fresh JWT just like LogIn.
func (s *AuthService) SignInWithOAuth(ctx context.Context, prof OAuthProfile) (*AuthResult, error) {
@@ -197,6 +201,7 @@ func (s *AuthService) SignInWithOAuth(ctx context.Context, prof OAuthProfile) (*
// avatar may have changed at the provider).
_, _ = s.oauth.Link(ctx, u.ID, prof.Provider, prof.ProviderUserID,
optStr(prof.Email), optStr(prof.Name), optStr(prof.AvatarURL))
s.ensureEmailVerified(ctx, u)
return s.issueSession(u)
} else if !errors.Is(err, repository.ErrOAuthAccountNotFound) {
return nil, err
@@ -214,6 +219,7 @@ func (s *AuthService) SignInWithOAuth(ctx context.Context, prof OAuthProfile) (*
optStr(prof.Email), optStr(prof.Name), optStr(prof.AvatarURL)); err != nil {
return nil, err
}
s.ensureEmailVerified(ctx, u)
return s.issueSession(u)
} else if !errors.Is(err, repository.ErrUserNotFound) {
return nil, err
@@ -232,7 +238,8 @@ func (s *AuthService) SignInWithOAuth(ctx context.Context, prof OAuthProfile) (*
if attempt > 0 {
handle = fmt.Sprintf("%s-%s", base, randomHandleSuffix())
}
u, err = s.users.CreateOAuth(ctx, prof.Email, displayName, handle, optStr(prof.AvatarURL), prof.EmailVerified)
// Always create as verified — successful OAuth = email ownership proof.
u, err = s.users.CreateOAuth(ctx, prof.Email, displayName, handle, optStr(prof.AvatarURL), true)
if err == nil {
break
}
@@ -241,6 +248,7 @@ func (s *AuthService) SignInWithOAuth(ctx context.Context, prof OAuthProfile) (*
if u2, err2 := s.users.FindByEmail(ctx, prof.Email); err2 == nil {
_, _ = s.oauth.Link(ctx, u2.ID, prof.Provider, prof.ProviderUserID,
optStr(prof.Email), optStr(prof.Name), optStr(prof.AvatarURL))
s.ensureEmailVerified(ctx, u2)
return s.issueSession(u2)
}
return nil, err
@@ -269,6 +277,23 @@ func optStr(s string) *string {
return &s
}
// ensureEmailVerified flips email_verified_at to now() on the given user
// if it's not already set. Called from every SignInWithOAuth branch so a
// successful OAuth round-trip immediately satisfies email verification
// for an existing-but-unverified account. Mutates u in place so callers
// see the updated timestamp without re-fetching.
func (s *AuthService) ensureEmailVerified(ctx context.Context, u *model.User) {
if u == nil || u.EmailVerifiedAt != nil {
return
}
if err := s.users.MarkEmailVerified(ctx, u.ID); err != nil {
s.logger.Warn("mark email verified after oauth", zap.String("user", u.ID), zap.Error(err))
return
}
now := time.Now()
u.EmailVerifiedAt = &now
}
// Me looks up a user by id and re-verifies the JWT's token_version against
// the stored value. Mismatch means the user has logged-out-everywhere or
// changed password since this JWT was issued → reject.