优化结构

This commit is contained in:
Yvan
2026-08-22 14:12:00 +08:00
parent b27a66e9d3
commit 9dd62c9fb6
37 changed files with 352 additions and 130 deletions
+13 -13
View File
@@ -57,6 +57,9 @@ func NewAuthenticationUsecase(users *UserUsecase, security *SecurityUsecase, iss
}
func (uc *AuthenticationUsecase) recordLogin(ctx context.Context, attempt *LoginAttempt, status bool, message string, userID uint) {
if uc.audit == nil || attempt == nil {
return
}
_ = uc.audit.RecordLogin(ctx, &LoginLog{Username: attempt.Username, IP: attempt.IP, Status: status, ErrorMessage: message, Agent: attempt.Agent, UserID: userID})
}
@@ -65,7 +68,7 @@ func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttemp
if err != nil || config == nil {
return nil, fmt.Errorf("%w: security config unavailable", ErrLoginState)
}
if config != nil && config.LockEnable {
if config.LockEnable {
locked, lockErr := uc.security.LoginLocked(ctx, attempt.Username)
if lockErr != nil {
return nil, fmt.Errorf("%w: %v", ErrLoginState, lockErr)
@@ -77,17 +80,14 @@ func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttemp
}
ipTTL := time.Hour
requireCaptcha := config == nil || config.CaptchaOpen == 0
if config != nil {
if config.CaptchaTimeout > 0 {
ipTTL = time.Duration(config.CaptchaTimeout) * time.Second
}
failures, counterErr := uc.security.EnsureLoginIPCounter(ctx, attempt.IP, ipTTL)
if counterErr != nil {
return nil, fmt.Errorf("%w: %v", ErrLoginState, counterErr)
}
requireCaptcha = config.CaptchaOpen == 0 || failures > config.CaptchaOpen
if config.CaptchaTimeout > 0 {
ipTTL = time.Duration(config.CaptchaTimeout) * time.Second
}
failures, counterErr := uc.security.EnsureLoginIPCounter(ctx, attempt.IP, ipTTL)
if counterErr != nil {
return nil, fmt.Errorf("%w: %v", ErrLoginState, counterErr)
}
requireCaptcha := config.CaptchaOpen == 0 || failures > config.CaptchaOpen
if requireCaptcha && !uc.security.VerifyCaptcha(ctx, attempt.CaptchaID, attempt.Captcha, true) {
_, _ = uc.security.IncrementLoginIP(ctx, attempt.IP, ipTTL)
uc.recordLogin(ctx, attempt, false, "验证码错误", 0)
@@ -97,7 +97,7 @@ func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttemp
user, err := uc.users.Login(ctx, attempt.Username, attempt.Password)
if err != nil {
_, _ = uc.security.IncrementLoginIP(ctx, attempt.IP, ipTTL)
if config != nil && config.LockEnable {
if config.LockEnable {
lockTTL := time.Duration(config.LockDuration) * time.Minute
failures, stateErr := uc.security.IncrementLoginFailure(ctx, attempt.Username, lockTTL)
if stateErr != nil {
@@ -119,7 +119,7 @@ func (uc *AuthenticationUsecase) Login(ctx context.Context, attempt *LoginAttemp
}
uc.security.ClearLoginState(ctx, attempt.Username)
if config != nil && config.PwdExpireEnable && config.PwdExpireDays > 0 && user.PasswordUpdatedAt != nil && time.Now().After((*user.PasswordUpdatedAt).AddDate(0, 0, config.PwdExpireDays)) {
if config.PwdExpireEnable && config.PwdExpireDays > 0 && user.PasswordUpdatedAt != nil && time.Now().After((*user.PasswordUpdatedAt).AddDate(0, 0, config.PwdExpireDays)) {
user.MustChangePassword = true
}
issued, err := uc.issuer.IssueToken(user, user.AuthorityID, user.MustChangePassword, 0)
@@ -140,6 +140,11 @@ func TestLoginRecordsSuccessBeforeMultipointCacheFailure(t *testing.T) {
}
}
func TestRecordLoginAllowsMissingAuditRecorder(t *testing.T) {
uc := &AuthenticationUsecase{}
uc.recordLogin(context.Background(), &LoginAttempt{Username: "admin"}, false, "failed", 0)
}
func TestSwitchAuthorityReissuesCurrentClaimsWithoutReloadingUser(t *testing.T) {
repo := &switchAuthorityUserRepo{}
issuer := &switchAuthorityIssuer{}
-39
View File
@@ -1,39 +0,0 @@
package system
import (
"go.einride.tech/aip/filtering"
"go.einride.tech/aip/ordering"
)
type ListOption func(*ListOptions)
type ListOptions struct {
Filter filtering.Filter
OrderBy ordering.OrderBy
Offset int
Limit int
}
func ListFilter(filter filtering.Filter) ListOption {
return func(o *ListOptions) {
o.Filter = filter
}
}
func ListOrderBy(orderBy ordering.OrderBy) ListOption {
return func(o *ListOptions) {
o.OrderBy = orderBy
}
}
func ListOffset(offset int) ListOption {
return func(o *ListOptions) {
o.Offset = offset
}
}
func ListLimit(limit int) ListOption {
return func(o *ListOptions) {
o.Limit = limit
}
}
+34
View File
@@ -0,0 +1,34 @@
package system
import "github.com/google/wire"
// ProviderSet wires the system domain usecases. The parent biz package keeps
// the aggregate entry point used by Wire while each domain owns its providers.
var ProviderSet = wire.NewSet(
NewUserUsecase,
NewAuthenticationUsecase,
NewSystemConfigUsecase,
NewAuthorityUsecase,
NewAPIUsecase,
NewPermissionUsecase,
NewAccessControlUsecase,
NewMenuUsecase,
NewDepartmentUsecase,
NewPositionUsecase,
NewDictionaryUsecase,
NewParameterUsecase,
NewTokenUsecase,
NewSecurityUsecase,
NewVersionUsecase,
NewExportUsecase,
NewAuditUsecase,
NewAuditRecorderUsecase,
NewLogViewerUsecase,
NewTaskUsecaseWithRegistry,
NewTaskApplicationUsecase,
NewMediaUsecase,
NewAnnouncementUsecase,
NewEmailUsecase,
NewPaymentUsecase,
NewIntegrationConfigUsecase,
)
+8 -4
View File
@@ -30,12 +30,16 @@ type InitializationRepo interface {
DiskMountPoints() []string
}
type SystemConfigUsecase struct {
repo InitializationRepo
tasks TaskRuntime
type TaskReloader interface {
Reload(context.Context) error
}
func NewSystemConfigUsecase(repo InitializationRepo, tasks TaskRuntime) *SystemConfigUsecase {
type SystemConfigUsecase struct {
repo InitializationRepo
tasks TaskReloader
}
func NewSystemConfigUsecase(repo InitializationRepo, tasks TaskReloader) *SystemConfigUsecase {
return &SystemConfigUsecase{repo: repo, tasks: tasks}
}
+1 -1
View File
@@ -62,7 +62,7 @@ func NewUserUsecase(repo UserRepo) *UserUsecase { return &UserUsecase{repo: repo
func (uc *UserUsecase) Login(ctx context.Context, username, password string) (*User, error) {
u, err := uc.repo.FindUserByUsername(ctx, username)
if err != nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil {
if err != nil || u == nil || bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) != nil {
return nil, ErrInvalidCredentials
}
uc.fallbackDefaultRouter(ctx, u)
+17
View File
@@ -12,6 +12,16 @@ type defaultRouterUserRepo struct {
err error
}
type loginUserRepo struct {
UserRepo
user *User
err error
}
func (r *loginUserRepo) FindUserByUsername(context.Context, string) (*User, error) {
return r.user, r.err
}
func (r *defaultRouterUserRepo) HasAuthorityMenu(context.Context, uint, string) (bool, error) {
return r.hasMenu, r.err
}
@@ -35,3 +45,10 @@ func TestFallbackDefaultRouterMatchesMenuLookupOutcome(t *testing.T) {
t.Fatalf("empty missing default route = %q", user.Authority.DefaultRouter)
}
}
func TestLoginRejectsMissingUserWithoutPanic(t *testing.T) {
user, err := NewUserUsecase(&loginUserRepo{}).Login(context.Background(), "missing", "password")
if !errors.Is(err, ErrInvalidCredentials) || user != nil {
t.Fatalf("Login() = %#v, %v; want nil, ErrInvalidCredentials", user, err)
}
}