enhance oauth for edge cases
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user