feat(user): customizable ban duration with automatic lift (#2945)
Admins can now set an optional ban expiry when banning a user; the ban lifts automatically once it passes. Empty expiry keeps the permanent behavior. - schema: User.ban_expires (nillable, additive) - inventory: LiftExpiredBan restores expired bans; Upsert propagates ban_expires for banned statuses and clears it otherwise - login, password reset, and SSO all lift expired bans before the ban check - admin user dialog: ban-expiry datetime field shown while a banned status is selected - tests: lift matrix (expired/future/permanent/active) and upsert propagation Authored By: TDvorak <info@tdvorak.dev> Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>pull/3582/head
parent
b85216fd51
commit
e1c2864ae7
File diff suppressed because one or more lines are too long
@ -0,0 +1,93 @@
|
||||
package inventory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudreve/Cloudreve/v4/ent/enttest"
|
||||
entuser "github.com/cloudreve/Cloudreve/v4/ent/user"
|
||||
"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLiftExpiredBan(t *testing.T) {
|
||||
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
t.Cleanup(func() { require.NoError(t, client.Close()) })
|
||||
ctx := context.Background()
|
||||
|
||||
group := client.Group.Create().SetName("g").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
|
||||
past := time.Now().Add(-time.Hour)
|
||||
future := time.Now().Add(time.Hour)
|
||||
uc := NewUserClient(client)
|
||||
|
||||
t.Run("expired manual ban lifted", func(t *testing.T) {
|
||||
u := client.User.Create().SetEmail("lift-exp@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusManualBanned).SetBanExpires(past).SetGroup(group).SaveX(ctx)
|
||||
out, err := uc.LiftExpiredBan(ctx, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entuser.StatusActive, out.Status)
|
||||
require.Nil(t, client.User.GetX(ctx, u.ID).BanExpires)
|
||||
})
|
||||
|
||||
t.Run("expired sys ban lifted", func(t *testing.T) {
|
||||
u := client.User.Create().SetEmail("lift-sys@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusSysBanned).SetBanExpires(past).SetGroup(group).SaveX(ctx)
|
||||
out, err := uc.LiftExpiredBan(ctx, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entuser.StatusActive, out.Status)
|
||||
})
|
||||
|
||||
t.Run("future ban untouched", func(t *testing.T) {
|
||||
u := client.User.Create().SetEmail("lift-future@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusManualBanned).SetBanExpires(future).SetGroup(group).SaveX(ctx)
|
||||
out, err := uc.LiftExpiredBan(ctx, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entuser.StatusManualBanned, out.Status)
|
||||
require.NotNil(t, client.User.GetX(ctx, u.ID).BanExpires)
|
||||
})
|
||||
|
||||
t.Run("permanent ban untouched", func(t *testing.T) {
|
||||
u := client.User.Create().SetEmail("lift-perm@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusManualBanned).SetGroup(group).SaveX(ctx)
|
||||
out, err := uc.LiftExpiredBan(ctx, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entuser.StatusManualBanned, out.Status)
|
||||
})
|
||||
|
||||
t.Run("active user untouched", func(t *testing.T) {
|
||||
u := client.User.Create().SetEmail("lift-active@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusActive).SetGroup(group).SaveX(ctx)
|
||||
out, err := uc.LiftExpiredBan(ctx, u)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entuser.StatusActive, out.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpsertBanExpires(t *testing.T) {
|
||||
client := enttest.Open(t, "sqlite3", "file:"+t.Name()+"?mode=memory&cache=shared")
|
||||
t.Cleanup(func() { require.NoError(t, client.Close()) })
|
||||
ctx := context.Background()
|
||||
|
||||
group := client.Group.Create().SetName("g").SetPermissions(&boolset.BooleanSet{}).SaveX(ctx)
|
||||
uc := NewUserClient(client)
|
||||
future := time.Now().Add(24 * time.Hour).UTC().Truncate(time.Second)
|
||||
|
||||
u := client.User.Create().SetEmail("upsert-ban@example.com").SetNick("u").
|
||||
SetStatus(entuser.StatusActive).SetGroup(group).SaveX(ctx)
|
||||
|
||||
// Ban with expiry
|
||||
u.Status = entuser.StatusManualBanned
|
||||
u.BanExpires = &future
|
||||
_, err := uc.Upsert(ctx, u, "", "")
|
||||
require.NoError(t, err)
|
||||
got := client.User.GetX(ctx, u.ID)
|
||||
require.Equal(t, future, got.BanExpires.UTC())
|
||||
|
||||
// Unban clears the expiry
|
||||
u.Status = entuser.StatusActive
|
||||
u.BanExpires = nil
|
||||
_, err = uc.Upsert(ctx, u, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, client.User.GetX(ctx, u.ID).BanExpires)
|
||||
}
|
||||
Loading…
Reference in new issue