diff --git a/backend/services/auth/identifier.go b/backend/services/auth/identifier.go index b1edba6..b6a8547 100644 --- a/backend/services/auth/identifier.go +++ b/backend/services/auth/identifier.go @@ -72,24 +72,44 @@ func ResolveIdentifier(identifier string) (ResolvedIdentity, error) { Find(&bindings); err != nil { return ResolvedIdentity{}, fmt.Errorf("load login email binding: %w", err) } - if len(bindings) != 1 || bindings[0].UserID <= 0 { - return ResolvedIdentity{}, errors.New("invalid identifier") + if len(bindings) == 1 && bindings[0].UserID > 0 { + var user models.User + has, err := db.Instance.ID(bindings[0].UserID).Get(&user) + if err != nil { + return ResolvedIdentity{}, fmt.Errorf("load login user: %w", err) + } + if has && user.ID > 0 && user.Account != "" && user.Disabled == 0 { + return ResolvedIdentity{ + UserID: user.ID, + Account: user.Account, + LoginAddress: normalized, + Kind: IdentifierEmail, + }, nil + } } - var user models.User - has, err := db.Instance.ID(bindings[0].UserID).Get(&user) - if err != nil { - return ResolvedIdentity{}, fmt.Errorf("load login user: %w", err) + // 回退机制:若未绑定独立外部邮箱,但属于系统配置的域名,则按账号匹配有效系统用户 + parts := strings.SplitN(normalized, "@", 2) + if len(parts) == 2 && isConfiguredLoginDomain(parts[1]) { + account, err := validation.NormalizeAccount(parts[0]) + if err == nil { + var user models.User + has, err := db.Instance.Where("account = ? AND disabled = 0", account).Get(&user) + if err != nil { + return ResolvedIdentity{}, fmt.Errorf("load domain user: %w", err) + } + if has && user.ID > 0 && user.Account != "" { + return ResolvedIdentity{ + UserID: user.ID, + Account: user.Account, + LoginAddress: normalized, + Kind: IdentifierEmail, + }, nil + } + } } - if !has || user.ID <= 0 || user.Account == "" { - return ResolvedIdentity{}, errors.New("invalid identifier") - } - return ResolvedIdentity{ - UserID: user.ID, - Account: user.Account, - LoginAddress: normalized, - Kind: IdentifierEmail, - }, nil + + return ResolvedIdentity{}, errors.New("invalid identifier") } func parseLoginIdentifier(identifier string) (string, IdentifierKind, error) { diff --git a/backend/services/auth/identifier_test.go b/backend/services/auth/identifier_test.go index 3551d19..f7ef0d4 100644 --- a/backend/services/auth/identifier_test.go +++ b/backend/services/auth/identifier_test.go @@ -92,3 +92,53 @@ func TestNormalizeIdentifierKeepsLegacyAccountCompatibility(t *testing.T) { t.Fatalf("normalized email key = %q, err = %v", got, err) } } + + +func TestResolveIdentifierFallbackToConfiguredDomainUser(t *testing.T) { + oldConfig := config.Instance + oldDB := db.Instance + config.Instance = &config.Config{ + DbType: config.DBTypeSQLite, + DbDSN: filepath.Join(t.TempDir(), "domain_fallback.db"), + Domain: "solit.cn", + Domains: []string{"solit.cn"}, + } + if err := db.Init(""); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if db.Instance != nil && db.Instance != oldDB { + _ = db.Instance.Close() + } + db.Instance = oldDB + config.Instance = oldConfig + }) + + user := models.User{Account: "bob", Name: "Bob", Disabled: 0} + if err := SetPassword(&user, "CorrectP12"); err != nil { + t.Fatal(err) + } + if _, err := db.Instance.Insert(&user); err != nil { + t.Fatal(err) + } + + identity, err := ResolveIdentifier(" Bob@Solit.cn ") + if err != nil { + t.Fatalf("resolve configured domain user failed: %v", err) + } + if identity.Kind != IdentifierEmail || identity.UserID != user.ID || identity.Account != "bob" || identity.LoginAddress != "bob@solit.cn" { + t.Fatalf("unexpected resolved identity: %+v", identity) + } + + disabledUser := models.User{Account: "charlie", Name: "Charlie", Disabled: 1} + if _, err := db.Instance.Insert(&disabledUser); err != nil { + t.Fatal(err) + } + if _, err := ResolveIdentifier("charlie@solit.cn"); err == nil { + t.Fatal("disabled domain user must not be resolved") + } + + if _, err := ResolveIdentifier("bob@other.com"); err == nil { + t.Fatal("unconfigured domain must not be resolved") + } +} \ No newline at end of file