diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6f191ab..31abe7e1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,10 @@ jobs: ci: uses: xraph/workflows/.github/workflows/go-ci.yml@v1 with: + # Deliberately a bare minor. The matrix job name embeds this string and the + # branch ruleset requires "ci / Test (ubuntu-latest, go1.26)", so pinning a + # patch here renames the job and that check can never report. The shared + # workflow sets check-latest, so this still resolves to the newest patch. go-versions: '["1.26"]' os: '["ubuntu-latest"]' diff --git a/bootstrap_test.go b/bootstrap_test.go index 0d6042d1..2b7a6567 100644 --- a/bootstrap_test.go +++ b/bootstrap_test.go @@ -241,7 +241,7 @@ func TestBootstrap_InitialOwnerCount_PromotesFirstN(t *testing.T) { } // Users 1–3 should have platform-owner. - for i := 0; i < 3; i++ { + for i := range 3 { roles, err := eng.ListUserRoles(ctx, users[i]) require.NoError(t, err) hasOwner := false diff --git a/ceremony_store_race_test.go b/ceremony_store_race_test.go index baf38b4e..2316eff6 100644 --- a/ceremony_store_race_test.go +++ b/ceremony_store_race_test.go @@ -47,14 +47,12 @@ func TestCeremonyStore_ConcurrentAccessNoRace(t *testing.T) { eng := newEngineForCeremonyTest(t) var wg sync.WaitGroup - for i := 0; i < 64; i++ { - wg.Add(1) - go func() { - defer wg.Done() + for range 64 { + wg.Go(func() { if s := eng.ceremonyStoreOrFallback(); s == nil { t.Error("ceremony store must never be nil") } - }() + }) } wg.Wait() } diff --git a/engine.go b/engine.go index 71161f66..20b5b3f6 100644 --- a/engine.go +++ b/engine.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "os" + "slices" "strings" log "github.com/xraph/go-utils/log" @@ -1145,11 +1146,8 @@ func (e *Engine) ClientConfig(ctx context.Context, appID id.AppID) *ClientConfig } // Device authorization: enabled when oauth2provider plugin is registered. - for _, name := range pluginNames { - if name == "oauth2provider" { - resp.DeviceAuthorization = &ClientConfigToggle{Enabled: true} - break - } + if slices.Contains(pluginNames, "oauth2provider") { + resp.DeviceAuthorization = &ClientConfigToggle{Enabled: true} } // Captcha: per-app setting. Always emit the section so the frontend diff --git a/engine_issue_session_test.go b/engine_issue_session_test.go index 69acddb4..72cde20d 100644 --- a/engine_issue_session_test.go +++ b/engine_issue_session_test.go @@ -244,7 +244,6 @@ func TestIssueSession_GateFiresForEveryAuthMethod(t *testing.T) { } for _, method := range cases { - method := method t.Run(method, func(t *testing.T) { t.Parallel() eng, u, appID := issueSessionFixture(t) diff --git a/permission_test.go b/permission_test.go index f6e5d72b..120256c7 100644 --- a/permission_test.go +++ b/permission_test.go @@ -246,13 +246,13 @@ func TestHasPermission_AllFirstNUsersHavePermission(t *testing.T) { ctx := context.Background() users := make([]id.UserID, 4) - for i := 0; i < 4; i++ { + for i := range 4 { email := "user" + string(rune('1'+i)) + "@example.com" users[i] = signUpOnPlatform(t, eng, email) } // Users 0–2 (first 3) must have app:manage. - for i := 0; i < 3; i++ { + for i := range 3 { allowed, err := eng.HasPermission(ctx, users[i], "manage", "app") require.NoError(t, err) assert.True(t, allowed, "user[%d] (one of first 3) must have app:manage", i) diff --git a/refresh_race_test.go b/refresh_race_test.go index 26ce3cb4..68afa3fc 100644 --- a/refresh_race_test.go +++ b/refresh_race_test.go @@ -35,7 +35,7 @@ func TestRefresh_ConcurrentRotation_ExactlyOneWinner(t *testing.T) { var wg sync.WaitGroup errs := make([]error, n) start := make(chan struct{}) - for i := 0; i < n; i++ { + for i := range n { wg.Add(1) go func(i int) { defer wg.Done() diff --git a/refresh_replay_test.go b/refresh_replay_test.go index db6aeb05..d753dfe8 100644 --- a/refresh_replay_test.go +++ b/refresh_replay_test.go @@ -118,7 +118,7 @@ func TestRefresh_ReplayStormEmitsAlertOnce(t *testing.T) { }) // A stuck client hammers the endpoint with the same dead token. - for i := 0; i < 5; i++ { + for range 5 { _, rerr := eng.Refresh(ctx, staleToken) assert.ErrorIs(t, rerr, account.ErrInvalidCredentials, "every replay attempt is refused") diff --git a/service_otp_test.go b/service_otp_test.go index 9e0cc8d9..f76ca755 100644 --- a/service_otp_test.go +++ b/service_otp_test.go @@ -93,7 +93,7 @@ func TestVerifyEmailCode_MaxAttempts(t *testing.T) { wrong := wrongCode(code) // Exhaust the attempt budget with wrong codes. - for i := 0; i < 5; i++ { + for range 5 { require.ErrorIs(t, eng.VerifyEmailCode(ctx, u.ID, wrong), account.ErrInvalidCredentials) } diff --git a/service_test.go b/service_test.go index 692924f9..72490552 100644 --- a/service_test.go +++ b/service_test.go @@ -604,7 +604,7 @@ func TestSignIn_AccountLockout_LocksAfterMaxAttempts(t *testing.T) { signUpTestUser(t, eng, "lockout@example.com", "SecureP@ss1") // 3 failed attempts with wrong password - for i := 0; i < 3; i++ { + for i := range 3 { _, _, err := eng.SignIn(ctx, &account.SignInRequest{ AppID: appID, Email: "lockout@example.com", @@ -641,7 +641,7 @@ func TestSignIn_AccountLockout_ResetOnSuccess(t *testing.T) { signUpTestUser(t, eng, "reset@example.com", "SecureP@ss1") // 2 failed attempts (below threshold) - for i := 0; i < 2; i++ { + for range 2 { _, _, err := eng.SignIn(ctx, &account.SignInRequest{ AppID: appID, Email: "reset@example.com", @@ -659,7 +659,7 @@ func TestSignIn_AccountLockout_ResetOnSuccess(t *testing.T) { require.NoError(t, err) // Another 2 failed attempts should NOT lock (counter was reset) - for i := 0; i < 2; i++ { + for range 2 { _, _, signInErr := eng.SignIn(ctx, &account.SignInRequest{ AppID: appID, Email: "reset@example.com", @@ -685,7 +685,7 @@ func TestSignIn_NoLockout_WhenTrackerNil(t *testing.T) { signUpTestUser(t, eng, "nolockout@example.com", "SecureP@ss1") // Many failed attempts without lockout - for i := 0; i < 10; i++ { + for range 10 { _, _, err := eng.SignIn(ctx, &account.SignInRequest{ AppID: appID, Email: "nolockout@example.com", diff --git a/session_settings.go b/session_settings.go index 3a27ab6c..b1e55357 100644 --- a/session_settings.go +++ b/session_settings.go @@ -47,7 +47,7 @@ var ( settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp), settings.WithEnforceable(), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(60), Max: intPtr(86400)}), + settings.WithUIValidation(formconfig.Validation{Required: true, Min: new(60), Max: new(86400)}), settings.WithHelpText("How long access tokens remain valid. Default: 3600 (1 hour)"), settings.WithOrder(10), settings.WithValidation(validateTokenTTL), @@ -61,7 +61,7 @@ var ( settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp), settings.WithEnforceable(), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(3600), Max: intPtr(7776000)}), + settings.WithUIValidation(formconfig.Validation{Required: true, Min: new(3600), Max: new(7776000)}), settings.WithHelpText("How long refresh tokens remain valid. Default: 2592000 (30 days)"), settings.WithOrder(20), settings.WithValidation(validateRefreshTokenTTL), @@ -111,7 +111,7 @@ var ( settings.WithCategory("Session Behavior"), settings.WithScopes(settings.ScopeGlobal), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(60), Max: intPtr(86400)}), + settings.WithUIValidation(formconfig.Validation{Required: true, Min: new(60), Max: new(86400)}), settings.WithHelpText("How often the engine removes expired sessions. Default: 3600 (1 hour)"), settings.WithOrder(60), ) @@ -139,7 +139,7 @@ var ( settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp), settings.WithEnforceable(), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Min: intPtr(0), Max: intPtr(100)}), + settings.WithUIValidation(formconfig.Validation{Min: new(0), Max: new(100)}), settings.WithHelpText("When set, oldest sessions are evicted when the limit is reached. 0 = unlimited."), settings.WithOrder(80), settings.WithVisibleWhen("session.multi_session_enabled", true), @@ -167,7 +167,7 @@ var ( settings.WithCategory("Auto-Refresh"), settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(30), Max: intPtr(3600)}), + settings.WithUIValidation(formconfig.Validation{Required: true, Min: new(30), Max: new(3600)}), settings.WithHelpText("Access tokens within this many seconds of expiry will be auto-refreshed. Default: 300 (5 minutes)"), settings.WithOrder(100), settings.WithVisibleWhen("session.auto_refresh_enabled", true), @@ -223,7 +223,7 @@ var ( settings.WithCategory("Session Extension"), settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp), settings.WithInputType(formconfig.FieldNumber), - settings.WithUIValidation(formconfig.Validation{Required: true, Min: intPtr(60), Max: intPtr(2592000)}), + settings.WithUIValidation(formconfig.Validation{Required: true, Min: new(60), Max: new(2592000)}), settings.WithHelpText("The session expiry is reset to now + this value on each request. Default: 604800 (7 days)"), settings.WithOrder(106), settings.WithVisibleWhen("session.extend_on_activity", true), @@ -401,9 +401,6 @@ func registerCoreSessionSettings(m *settings.Manager) error { return settings.RegisterTyped(m, "session", SettingCookieUseHostPrefix) } -// intPtr returns a pointer to the given int. -func intPtr(v int) *int { return &v } - // ────────────────────────────────────────────────── // Validators // ──────────────────────────────────────────────────