diff --git a/backend/internal/service/auth_oauth_email_flow.go b/backend/internal/service/auth_oauth_email_flow.go index ca3403d4..4ab4e245 100644 --- a/backend/internal/service/auth_oauth_email_flow.go +++ b/backend/internal/service/auth_oauth_email_flow.go @@ -147,5 +147,11 @@ func (s *AuthService) ValidatePasswordCredentials(ctx context.Context, email, pa // RecordSuccessfulLogin updates last-login activity after a non-standard login // flow finishes with a real session. func (s *AuthService) RecordSuccessfulLogin(ctx context.Context, userID int64) { + if s != nil && s.userRepo != nil && userID > 0 { + user, err := s.userRepo.GetByID(ctx, userID) + if err == nil { + s.backfillEmailIdentityOnSuccessfulLogin(ctx, user) + } + } s.touchUserLogin(ctx, userID) } diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go index 40753139..dda6df04 100644 --- a/backend/internal/service/auth_service.go +++ b/backend/internal/service/auth_service.go @@ -430,6 +430,7 @@ func (s *AuthService) Login(ctx context.Context, email, password string) (string if !user.IsActive() { return "", nil, ErrUserNotActive } + s.backfillEmailIdentityOnSuccessfulLogin(ctx, user) s.touchUserLogin(ctx, user.ID) // 生成JWT token @@ -802,6 +803,13 @@ func (s *AuthService) touchUserLogin(ctx context.Context, userID int64) { } } +func (s *AuthService) backfillEmailIdentityOnSuccessfulLogin(ctx context.Context, user *User) { + if s == nil || user == nil || user.ID <= 0 { + return + } + s.ensureEmailAuthIdentity(ctx, user) +} + func (s *AuthService) ensureEmailAuthIdentity(ctx context.Context, user *User) { if s == nil || s.entClient == nil || user == nil || user.ID <= 0 { return diff --git a/backend/internal/service/auth_service_identity_sync_test.go b/backend/internal/service/auth_service_identity_sync_test.go index 5bd2b25d..fcb4813b 100644 --- a/backend/internal/service/auth_service_identity_sync_test.go +++ b/backend/internal/service/auth_service_identity_sync_test.go @@ -150,4 +150,41 @@ func TestAuthServiceLoginTouchesLastLoginAt(t *testing.T) { require.NotNil(t, storedUser.LastActiveAt) require.True(t, storedUser.LastLoginAt.After(old)) require.True(t, storedUser.LastActiveAt.After(old)) + + identity, err := client.AuthIdentity.Query(). + Where( + authidentity.ProviderTypeEQ("email"), + authidentity.ProviderKeyEQ("email"), + authidentity.ProviderSubjectEQ("login@example.com"), + ). + Only(ctx) + require.NoError(t, err) + require.Equal(t, user.ID, identity.UserID) +} + +func TestAuthServiceRecordSuccessfulLoginBackfillsEmailIdentity(t *testing.T) { + svc, repo, client := newAuthServiceWithEnt(t) + ctx := context.Background() + + user := &service.User{ + Email: "record@example.com", + Role: service.RoleUser, + Status: service.StatusActive, + Balance: 1, + Concurrency: 1, + } + require.NoError(t, user.SetPassword("password")) + require.NoError(t, repo.Create(ctx, user)) + + svc.RecordSuccessfulLogin(ctx, user.ID) + + identity, err := client.AuthIdentity.Query(). + Where( + authidentity.ProviderTypeEQ("email"), + authidentity.ProviderKeyEQ("email"), + authidentity.ProviderSubjectEQ("record@example.com"), + ). + Only(ctx) + require.NoError(t, err) + require.Equal(t, user.ID, identity.UserID) }