This commit is contained in:
parent
b365c2535f
commit
64749a578b
@ -12,10 +12,12 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var IsInit bool
|
||||
var legacyInitState atomic.Bool
|
||||
var DevelopmentMode bool
|
||||
|
||||
type Config struct {
|
||||
@ -197,7 +199,7 @@ func Init() error {
|
||||
Instance.Domains = []string{Instance.Domain}
|
||||
}
|
||||
|
||||
IsInit = Instance.IsSetupReady()
|
||||
SetInitState(Instance.IsSetupReady())
|
||||
PublishConfigSnapshot(Instance)
|
||||
|
||||
// 设置日志格式为json格式
|
||||
@ -238,6 +240,15 @@ func (c *Config) IsSetupReady() bool {
|
||||
return c != nil && c.SetupState == SetupStateReady
|
||||
}
|
||||
|
||||
func SetInitState(value bool) {
|
||||
legacyInitState.Store(value)
|
||||
IsInit = value
|
||||
}
|
||||
|
||||
func legacyInitReady() bool {
|
||||
return legacyInitState.Load()
|
||||
}
|
||||
|
||||
func ReadPrivateKey() (*ecdsa.PrivateKey, bool, error) {
|
||||
privateKey, created, err := loadOrCreateACMEPrivateKey(filepath.Join(ROOT_PATH, "config", "ssl"), "account_private.pem")
|
||||
if err != nil {
|
||||
|
||||
@ -16,7 +16,7 @@ func IsSetupReady() bool {
|
||||
if value != nil && value.SetupState.Valid() {
|
||||
return value.SetupState == SetupStateReady
|
||||
}
|
||||
return IsInit
|
||||
return legacyInitReady()
|
||||
}
|
||||
|
||||
// PublishConfigSnapshot 发布一份深拷贝,供运行时只读访问。
|
||||
|
||||
@ -99,8 +99,8 @@ func TestV1RouteDoesNotReceiveLegacyDeprecationHeaders(t *testing.T) {
|
||||
|
||||
func TestV1LoginUsesSameRequestOriginGuard(t *testing.T) {
|
||||
oldInit := config.IsInit
|
||||
t.Cleanup(func() { config.IsInit = oldInit })
|
||||
config.IsInit = false
|
||||
t.Cleanup(func() { config.SetInitState(oldInit) })
|
||||
config.SetInitState(false)
|
||||
|
||||
handler := contextIterceptor(controllers.Login)
|
||||
request := httptest.NewRequest(http.MethodPost, "http://mail.example.com/api/v1/login", nil)
|
||||
|
||||
@ -47,10 +47,10 @@ func TestCSRFRejectsCrossOriginCookieRequest(t *testing.T) {
|
||||
oldInit := config.IsInit
|
||||
oldSession := session.Instance
|
||||
defer func() {
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
session.Instance = oldSession
|
||||
}()
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
session.Instance = scs.New()
|
||||
session.Instance.Cookie.Name = "smail_session"
|
||||
|
||||
@ -81,10 +81,10 @@ func TestCSRFAllowsSameOriginCookieRequest(t *testing.T) {
|
||||
oldInit := config.IsInit
|
||||
oldSession := session.Instance
|
||||
defer func() {
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
session.Instance = oldSession
|
||||
}()
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
session.Instance = scs.New()
|
||||
session.Instance.Cookie.Name = "smail_session"
|
||||
|
||||
|
||||
@ -25,12 +25,12 @@ func TestModifyPasswordRouteRejectsUnauthenticatedRequest(t *testing.T) {
|
||||
oldSession := session.Instance
|
||||
t.Cleanup(func() {
|
||||
config.Instance = oldConfig
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
session.Instance = oldSession
|
||||
})
|
||||
|
||||
config.Instance = &config.Config{}
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
session.Instance = scs.New()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@ -66,7 +66,7 @@ func TestSuperAdminCannotChangeOwnPasswordAfterLogin(t *testing.T) {
|
||||
_ = db.Instance.Close()
|
||||
}
|
||||
config.Instance = oldConfig
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
db.Instance = oldDB
|
||||
session.Instance = oldSession
|
||||
})
|
||||
@ -75,7 +75,7 @@ func TestSuperAdminCannotChangeOwnPasswordAfterLogin(t *testing.T) {
|
||||
DbType: config.DBTypeSQLite,
|
||||
DbDSN: filepath.Join(testRoot, "super-admin-password.db"),
|
||||
}
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
if err := db.Init(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -29,13 +29,13 @@ func TestContextInterceptorValidatesCurrentUserState(t *testing.T) {
|
||||
_ = db.Instance.Close()
|
||||
}
|
||||
config.Instance = oldConfig
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
db.Instance = oldDB
|
||||
session.Instance = oldSession
|
||||
})
|
||||
|
||||
config.Instance = &config.Config{DbType: config.DBTypeSQLite, DbDSN: filepath.Join(testRoot, "session-validation.db")}
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
if err := db.Init(""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -21,9 +21,9 @@ func withSetupSecurity(t *testing.T, token string) *setupSecurity {
|
||||
if err := os.Unsetenv("SMAIL_SETUP_ALLOWED_CIDRS"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
t.Cleanup(func() {
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
_ = os.Setenv("SMAIL_SETUP_TOKEN", oldToken)
|
||||
_ = os.Setenv("SMAIL_SETUP_ALLOWED_CIDRS", oldCIDRs)
|
||||
})
|
||||
@ -65,7 +65,7 @@ func TestSetupSecurityAcceptsTokenOnce(t *testing.T) {
|
||||
calls := 0
|
||||
handler := security.authorize(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
@ -77,7 +77,7 @@ func TestSetupSecurityAcceptsTokenOnce(t *testing.T) {
|
||||
t.Fatalf("first request failed: code=%d calls=%d", recorder.Code, calls)
|
||||
}
|
||||
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
request = setupRequest(http.MethodPost)
|
||||
request.Header.Set("X-Setup-Token", token)
|
||||
recorder = httptest.NewRecorder()
|
||||
@ -100,7 +100,7 @@ func TestSetupSecuritySerializesConcurrentTokenRequests(t *testing.T) {
|
||||
callsMu.Unlock()
|
||||
close(started)
|
||||
<-continueRequest
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
@ -243,7 +243,7 @@ func TestSetupAPIRoutesBecomeInvalidAfterSetupCompletes(t *testing.T) {
|
||||
calls := 0
|
||||
registerSetupAPIRoutes(mux, security, setupOnlyPost(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
|
||||
@ -256,7 +256,7 @@ func TestSetupAPIRoutesBecomeInvalidAfterSetupCompletes(t *testing.T) {
|
||||
t.Fatalf("first setup returned code=%d calls=%d", recorder.Code, calls)
|
||||
}
|
||||
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
for _, endpoint := range []string{"/api/setup", "/api/v1/setup"} {
|
||||
request = httptest.NewRequest(http.MethodPost, endpoint, nil)
|
||||
request.RemoteAddr = "127.0.0.1:12345"
|
||||
@ -280,9 +280,9 @@ func TestSetupSecurityBootstrapCookieForAllowedRemote(t *testing.T) {
|
||||
if err := os.Setenv("SMAIL_SETUP_ALLOWED_CIDRS", "192.168.2.0/24"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.IsInit = false
|
||||
config.SetInitState(false)
|
||||
t.Cleanup(func() {
|
||||
config.IsInit = oldInit
|
||||
config.SetInitState(oldInit)
|
||||
_ = os.Setenv("SMAIL_SETUP_TOKEN", oldToken)
|
||||
_ = os.Setenv("SMAIL_SETUP_ALLOWED_CIDRS", oldCIDRs)
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@ type UserEmail struct {
|
||||
UserID int `xorm:"user_id int not null index('idx_eid') index('idx_user_email_user_primary')"`
|
||||
EmailID int `xorm:"email_id not null index('idx_eid') index"`
|
||||
Address string `xorm:"address varchar(254) null" json:"address,omitempty"`
|
||||
AddressCanonical string `xorm:"address_canonical varchar(254) null unique" json:"-"`
|
||||
AddressCanonical *string `xorm:"address_canonical varchar(254) null unique" json:"-"`
|
||||
IsPrimary int8 `xorm:"is_primary tinyint(1) notnull default(0)" json:"is_primary"`
|
||||
VerifiedAt *time.Time `xorm:"verified_at datetime null" json:"verified_at,omitempty"`
|
||||
CreatedAt time.Time `xorm:"created_at datetime created" json:"created_at"`
|
||||
|
||||
@ -135,10 +135,11 @@ func TestProtocolAuthenticationAcceptsConfiguredFullEmailIdentifier(t *testing.T
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifiedAt := time.Now().UTC()
|
||||
canonicalAddress := "alice@example.com"
|
||||
if _, err := db.Instance.Insert(&models.UserEmail{
|
||||
UserID: user.ID,
|
||||
Address: "alice@example.com",
|
||||
AddressCanonical: "alice@example.com",
|
||||
AddressCanonical: &canonicalAddress,
|
||||
VerifiedAt: &verifiedAt,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@ -37,18 +37,20 @@ func TestResolveIdentifierUsesVerifiedFullEmailBinding(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifiedAt := time.Now().UTC()
|
||||
canonicalAddress := "alice@example.com"
|
||||
if _, err := db.Instance.Insert(&models.UserEmail{
|
||||
UserID: user.ID,
|
||||
Address: "Alice@Example.com",
|
||||
AddressCanonical: "alice@example.com",
|
||||
AddressCanonical: &canonicalAddress,
|
||||
VerifiedAt: &verifiedAt,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pendingAddress := "pending@example.com"
|
||||
if _, err := db.Instance.Insert(&models.UserEmail{
|
||||
UserID: user.ID,
|
||||
Address: "pending@example.com",
|
||||
AddressCanonical: "pending@example.com",
|
||||
AddressCanonical: &pendingAddress,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ func Finish() error {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
if cfg.IsSetupReady() {
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
return nil
|
||||
}
|
||||
cfg.SetupState = config.SetupStateReady
|
||||
@ -28,7 +28,7 @@ func Finish() error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
config.IsInit = true
|
||||
config.SetInitState(true)
|
||||
// 初始化完成
|
||||
select {
|
||||
case signal.InitChan <- true:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user