diff --git a/backend/internal/service/auth_service.go b/backend/internal/service/auth_service.go index 5b6e5fef..c3d9cc32 100644 --- a/backend/internal/service/auth_service.go +++ b/backend/internal/service/auth_service.go @@ -796,7 +796,7 @@ func (s *AuthService) backfillEmailIdentityOnSuccessfulLogin(ctx context.Context if s == nil || user == nil || user.ID <= 0 { return } - identity, created := s.ensureEmailAuthIdentity(ctx, user) + identity, created := s.ensureEmailAuthIdentity(ctx, user, "auth_service_login_backfill") if s.shouldApplyEmailFirstBindDefaults(ctx, user.ID, identity, created) { if err := s.ApplyProviderDefaultSettingsOnFirstBind(ctx, user.ID, "email"); err != nil { logger.LegacyPrintf("service.auth", "[Auth] Failed to apply email first bind defaults: user_id=%d err=%v", user.ID, err) @@ -810,13 +810,17 @@ func (s *AuthService) shouldApplyEmailFirstBindDefaults( identity *dbent.AuthIdentity, created bool, ) bool { + source := emailAuthIdentitySource(identity.Metadata) + if source == "auth_service_login_backfill" { + return false + } if created { return true } if s == nil || s.entClient == nil || userID <= 0 || identity == nil || identity.UserID != userID { return false } - if emailAuthIdentitySource(identity.Metadata) != "auth_service_dual_write" { + if source != "auth_service_dual_write" { return false } @@ -863,7 +867,7 @@ func (s *AuthService) hasProviderGrantRecord( return rows.Next(), rows.Err() } -func (s *AuthService) ensureEmailAuthIdentity(ctx context.Context, user *User) (*dbent.AuthIdentity, bool) { +func (s *AuthService) ensureEmailAuthIdentity(ctx context.Context, user *User, source string) (*dbent.AuthIdentity, bool) { if s == nil || s.entClient == nil || user == nil || user.ID <= 0 { return nil, false } @@ -872,6 +876,9 @@ func (s *AuthService) ensureEmailAuthIdentity(ctx context.Context, user *User) ( if email == "" || isReservedEmail(email) { return nil, false } + if strings.TrimSpace(source) == "" { + source = "auth_service_dual_write" + } client := s.entClient if tx := dbent.TxFromContext(ctx); tx != nil { @@ -900,7 +907,7 @@ func (s *AuthService) ensureEmailAuthIdentity(ctx context.Context, user *User) ( SetProviderSubject(email). SetVerifiedAt(time.Now().UTC()). SetMetadata(map[string]any{ - "source": "auth_service_dual_write", + "source": strings.TrimSpace(source), }). OnConflictColumns( authidentity.FieldProviderType, diff --git a/backend/internal/service/auth_service_identity_sync_test.go b/backend/internal/service/auth_service_identity_sync_test.go index 4d2a840f..2233e427 100644 --- a/backend/internal/service/auth_service_identity_sync_test.go +++ b/backend/internal/service/auth_service_identity_sync_test.go @@ -259,7 +259,7 @@ func TestAuthServiceRecordSuccessfulLoginBackfillsEmailIdentity(t *testing.T) { require.Equal(t, user.ID, identity.UserID) } -func TestAuthServiceLogin_AppliesEmailFirstBindDefaultsOnlyWhenEmailIdentityIsNew(t *testing.T) { +func TestAuthServiceLogin_DoesNotApplyEmailFirstBindDefaultsWhenBackfillingLegacyEmailIdentity(t *testing.T) { assigner := &authIdentityDefaultSubAssignerStub{} svc, _, client := newAuthServiceWithEnt(t, map[string]string{ service.SettingKeyRegistrationEnabled: "true", @@ -291,11 +291,9 @@ func TestAuthServiceLogin_AppliesEmailFirstBindDefaultsOnlyWhenEmailIdentityIsNe storedUser, err := client.User.Get(ctx, user.ID) require.NoError(t, err) - require.Equal(t, 10.0, storedUser.Balance) - require.Equal(t, 6, storedUser.Concurrency) - require.Len(t, assigner.calls, 1) - require.Equal(t, int64(11), assigner.calls[0].GroupID) - require.Equal(t, 30, assigner.calls[0].ValidityDays) + require.Equal(t, 1.5, storedUser.Balance) + require.Equal(t, 2, storedUser.Concurrency) + require.Empty(t, assigner.calls) identityCount, err := client.AuthIdentity.Query(). Where( @@ -306,7 +304,7 @@ func TestAuthServiceLogin_AppliesEmailFirstBindDefaultsOnlyWhenEmailIdentityIsNe Count(ctx) require.NoError(t, err) require.Equal(t, 1, identityCount) - require.Equal(t, 1, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) + require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) token, gotUser, err = svc.Login(ctx, user.Email, "password") require.NoError(t, err) @@ -315,13 +313,13 @@ func TestAuthServiceLogin_AppliesEmailFirstBindDefaultsOnlyWhenEmailIdentityIsNe storedUser, err = client.User.Get(ctx, user.ID) require.NoError(t, err) - require.Equal(t, 10.0, storedUser.Balance) - require.Equal(t, 6, storedUser.Concurrency) - require.Len(t, assigner.calls, 1) - require.Equal(t, 1, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) + require.Equal(t, 1.5, storedUser.Balance) + require.Equal(t, 2, storedUser.Concurrency) + require.Empty(t, assigner.calls) + require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) } -func TestAuthServiceLogin_MergesEmailFirstBindSourceOverridesWithGlobalDefaults(t *testing.T) { +func TestAuthServiceLogin_DoesNotApplyMergedEmailFirstBindDefaultsWhenBackfillingLegacyEmailIdentity(t *testing.T) { assigner := &authIdentityDefaultSubAssignerStub{} svc, _, client := newAuthServiceWithEnt(t, map[string]string{ service.SettingKeyRegistrationEnabled: "true", @@ -354,12 +352,10 @@ func TestAuthServiceLogin_MergesEmailFirstBindSourceOverridesWithGlobalDefaults( storedUser, err := client.User.Get(ctx, user.ID) require.NoError(t, err) - require.Equal(t, 10.0, storedUser.Balance) - require.Equal(t, 4, storedUser.Concurrency) - require.Len(t, assigner.calls, 1) - require.Equal(t, int64(21), assigner.calls[0].GroupID) - require.Equal(t, 14, assigner.calls[0].ValidityDays) - require.Equal(t, 1, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) + require.Equal(t, 1.5, storedUser.Balance) + require.Equal(t, 2, storedUser.Concurrency) + require.Empty(t, assigner.calls) + require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) } func TestAuthServiceLogin_DoesNotApplyEmailFirstBindDefaultsWhenIdentityAlreadyExists(t *testing.T) { @@ -409,7 +405,7 @@ func TestAuthServiceLogin_DoesNotApplyEmailFirstBindDefaultsWhenIdentityAlreadyE require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) } -func TestAuthServiceLogin_RetriesEmailFirstBindDefaultsAfterPreviousFailure(t *testing.T) { +func TestAuthServiceLogin_DoesNotRetryEmailFirstBindDefaultsForBackfilledEmailIdentity(t *testing.T) { assigner := &flakyAuthIdentityDefaultSubAssignerStub{failuresRemaining: 1} svc, _, client := newAuthServiceWithEnt(t, map[string]string{ service.SettingKeyRegistrationEnabled: "true", @@ -443,7 +439,7 @@ func TestAuthServiceLogin_RetriesEmailFirstBindDefaultsAfterPreviousFailure(t *t require.NoError(t, err) require.Equal(t, 1.5, storedUser.Balance) require.Equal(t, 2, storedUser.Concurrency) - require.Len(t, assigner.calls, 1) + require.Empty(t, assigner.calls) require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) token, gotUser, err = svc.Login(ctx, user.Email, "password") @@ -454,10 +450,10 @@ func TestAuthServiceLogin_RetriesEmailFirstBindDefaultsAfterPreviousFailure(t *t storedUser, err = client.User.Get(ctx, user.ID) require.NoError(t, err) - require.Equal(t, 10.0, storedUser.Balance) - require.Equal(t, 6, storedUser.Concurrency) - require.Len(t, assigner.calls, 2) - require.Equal(t, 1, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) + require.Equal(t, 1.5, storedUser.Balance) + require.Equal(t, 2, storedUser.Concurrency) + require.Empty(t, assigner.calls) + require.Equal(t, 0, countProviderGrantRecords(t, client, user.ID, "email", "first_bind")) } func countProviderGrantRecords(