Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]'

Expand Down
2 changes: 1 addition & 1 deletion bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 3 additions & 5 deletions ceremony_store_race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
8 changes: 3 additions & 5 deletions engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"os"
"slices"
"strings"

log "github.com/xraph/go-utils/log"
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion engine_issue_session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions permission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion refresh_race_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion refresh_replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion service_otp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
8 changes: 4 additions & 4 deletions service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ func TestSignIn_AccountLockout_LocksAfterMaxAttempts(t *testing.T) {
signUpTestUser(t, eng, "[email protected]", "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: "[email protected]",
Expand Down Expand Up @@ -641,7 +641,7 @@ func TestSignIn_AccountLockout_ResetOnSuccess(t *testing.T) {
signUpTestUser(t, eng, "[email protected]", "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: "[email protected]",
Expand All @@ -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: "[email protected]",
Expand All @@ -685,7 +685,7 @@ func TestSignIn_NoLockout_WhenTrackerNil(t *testing.T) {
signUpTestUser(t, eng, "[email protected]", "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: "[email protected]",
Expand Down
15 changes: 6 additions & 9 deletions session_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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),
)
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
// ──────────────────────────────────────────────────
Expand Down
Loading