安全加固

This commit is contained in:
ryan
2026-06-08 19:50:33 +08:00
parent 0f65202659
commit d99bd5231a
8 changed files with 155 additions and 2 deletions
+64
View File
@@ -722,6 +722,70 @@ func TestCallbackLoginAndUserInfo(t *testing.T) {
if collisionResp.Data.User.Username != "test_oauth_user-1" {
t.Errorf("expected collision renamed username, got %s", collisionResp.Data.User.Username)
}
t.Run("OIDC login when registration disabled - need bind", func(t *testing.T) {
// Disable registration in database
dbConn.Create(&model.SystemConfig{
Key: model.ConfigKeyRegistrationEnabled,
Value: "false",
})
defer func() {
dbConn.Where("key = ?", model.ConfigKeyRegistrationEnabled).Delete(&model.SystemConfig{})
}()
state4 := uuid.NewString()
payloadValue4, _ := encodeOAuthStatePayload(oauthStatePayload{
SourceName: testSourceName,
Purpose: OAuthPurposeLogin,
})
mockRedis.Set(context.Background(), db.PrefixedKey(fmt.Sprintf(OAuthStateCacheKeyFormat, state4)), payloadValue4, OAuthStateCacheKeyExpiration)
httpMock4 := &http.Client{
Transport: &mockRoundTripper{
roundTripFunc: func(req *http.Request) (*http.Response, error) {
if req.Method == http.MethodGet && strings.Contains(req.URL.String(), "/.well-known/openid-configuration") {
return oidcDiscoveryResponse(), nil
}
if req.Method == http.MethodGet && req.URL.String() == testJWKSURL {
return jwksResponse(), nil
}
if req.Method == http.MethodPost && req.URL.String() == testTokenURL {
idToken := generateMockIDToken(testIssuerURL, "77777", testClientID, state4, "need_bind_user", "needbind@linux.do", "Need Bind User")
body := fmt.Sprintf(`{"access_token":"mock_access_token","token_type":"Bearer","expires_in":3600,"id_token":"%s"}`, idToken)
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(body)),
Header: make(http.Header),
}, nil
}
return nil, fmt.Errorf("unexpected request")
},
},
}
util.SetHTTPClient(httpMock4)
router4 := setupTestRouter(dbConn, mockRedis, httpMock4)
reqBody4 := fmt.Sprintf(`{"state":"%s","code":"test_auth_code"}`, state4)
w4 := performRequest(router4, http.MethodPost, "/api/v1/oauth/callback", []byte(reqBody4), map[string]string{
"Content-Type": "application/json",
}, nil)
if w4.Code != http.StatusOK {
t.Fatalf("callback failed: %d, body: %s", w4.Code, w4.Body.String())
}
var needBindResp struct {
Data OAuthCallbackResult `json:"data"`
}
_ = json.Unmarshal(w4.Body.Bytes(), &needBindResp)
if needBindResp.Data.Status != "need_bind" {
t.Errorf("expected status 'need_bind', got %s", needBindResp.Data.Status)
}
if needBindResp.Data.User != nil {
t.Errorf("expected User to be nil, got %+v", needBindResp.Data.User)
}
})
}
func TestCallbackBind(t *testing.T) {
+21
View File
@@ -481,6 +481,27 @@ func Callback(c *gin.Context) {
return
}
case errors.Is(err, gorm.ErrRecordNotFound):
// 检查系统是否允许注册
registrationEnabled, regErr := model.GetBoolByKey(ctx, model.ConfigKeyRegistrationEnabled)
if regErr != nil {
registrationEnabled = true // 默认允许注册
}
if !registrationEnabled {
// 如果不允许注册,临时记录到 session 并向前端返回 "need_bind" 状态
session := sessions.Default(c)
session.Set("pending_oauth_source_id", source.ID)
session.Set("pending_oauth_external_id", userInfo.Sub)
session.Set("pending_oauth_external_username", userInfo.Username)
session.Set("pending_oauth_email", userInfo.Email)
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, util.Err(err.Error()))
return
}
c.JSON(http.StatusOK, util.OK(buildCallbackResult(nil, "need_bind")))
return
}
username, uniqueErr := uniqueUsername(ctx, userInfo.Username)
if uniqueErr != nil {
c.JSON(http.StatusInternalServerError, util.Err(uniqueErr.Error()))