diff --git a/config/error_page/CAPTCHA.html b/config/error_page/CAPTCHA.html index 17d7519..fa90b8b 100644 --- a/config/error_page/CAPTCHA.html +++ b/config/error_page/CAPTCHA.html @@ -65,6 +65,10 @@ alert("Bad CAPTCHA, please refresh the page and try again.\n" + "您未能通过人机验证,请刷新页面后重试。"); break; + case "timeout": + alert("Verification timeout, please refresh the page and try again.\n" + + "验证超时,请刷新页面后重试。"); + break; default: alert("Unexpected error occurred, please refresh the page and try again.\n" + "发生了意料之外的错误,请刷新页面后重试。"); @@ -72,12 +76,24 @@ } } } + + function checkCaptchaRender() { + const captchaDiv = document.querySelector(".h-captcha"); + + if (captchaDiv && captchaDiv.children.length > 0) { + } else { + document.getElementById("verifyBox").innerHTML = "Loading CAPTCHA failed, please check your internet connection and try again.
" + + "加载人机验证失败,请检查尝试更换网络环境后重试。"; + } + } + setTimeout(checkCaptchaRender, 5000); // 5秒后检查
Checking that you are not a robot
-
+
请完成人机验证
+
diff --git a/config/rules/CAPTCHA.yml b/config/rules/CAPTCHA.example.yml similarity index 53% rename from config/rules/CAPTCHA.yml rename to config/rules/CAPTCHA.example.yml index df022a7..96736f0 100644 --- a/config/rules/CAPTCHA.yml +++ b/config/rules/CAPTCHA.example.yml @@ -1,3 +1,4 @@ secret_key: "0378b0f84c4310279918d71a5647ba5d" -captcha_validate_time: 60 +captcha_validate_time: 600 +captcha_challenge_timeout: 120 hcaptcha_secret: "" \ No newline at end of file diff --git a/config/torii.yml b/config/torii.example.yml similarity index 63% rename from config/torii.yml rename to config/torii.example.yml index cc41179..e03094f 100644 --- a/config/torii.yml +++ b/config/torii.example.yml @@ -1,8 +1,8 @@ port: "25555" web_path: "/torii" -rule_path: "/www/dev/server_torii/config/rules" -error_page: "/www/dev/server_torii/config/error_page" -log_path: "/www/dev/server_torii/log/access.log" +rule_path: "/www/server_torii/config/rules" +error_page: "/www/server_torii/config/error_page" +log_path: "/www/server_torii/log/access.log" node_name: "Server Torii" connecting_host_headers: - "Torii-Real-Host" diff --git a/internal/check/Captcha.go b/internal/check/Captcha.go index ec73e4d..ff8cf10 100644 --- a/internal/check/Captcha.go +++ b/internal/check/Captcha.go @@ -31,7 +31,7 @@ func Captcha(reqData dataType.UserRequest, ruleSet *config.RuleSet, decision *ac } if !verifyClearanceCookie(reqData, *ruleSet) { - decision.SetCode(action.Done, []byte("CAPTCHA")) + decision.SetResponse(action.Done, []byte("CAPTCHA"), genSessionID(reqData, *ruleSet)) return } @@ -51,6 +51,11 @@ func CheckCaptcha(r *http.Request, reqData dataType.UserRequest, ruleSet *config return } + if !verifySessionCookie(reqData, *ruleSet) { + decision.SetResponse(action.Done, []byte("200"), []byte("timeout")) + return + } + data := url.Values{} data.Set("secret", ruleSet.CAPTCHARule.HCaptchaSecret) data.Set("response", hCaptchaResponse) @@ -84,7 +89,7 @@ func CheckCaptcha(r *http.Request, reqData dataType.UserRequest, ruleSet *config } if !hCaptchaResp.Success { - decision.SetResponse(action.Done, []byte("200"), []byte("bad4")) + decision.SetResponse(action.Done, []byte("200"), []byte("bad")) return } @@ -93,10 +98,46 @@ func CheckCaptcha(r *http.Request, reqData dataType.UserRequest, ruleSet *config } +func genSessionID(reqData dataType.UserRequest, ruleSet config.RuleSet) []byte { + timeNow := time.Now().Unix() + mac := hmac.New(sha512.New, []byte(ruleSet.CAPTCHARule.SecretKey)) + mac.Write([]byte(fmt.Sprintf("%d%s%sCAPTCHA-SESSION-ID", timeNow, reqData.Host, reqData.UserAgent))) + return []byte(fmt.Sprintf("%s:%s", fmt.Sprintf("%d", time.Now().Unix()), fmt.Sprintf("%x", mac.Sum(nil)))) +} + +func verifySessionCookie(reqData dataType.UserRequest, ruleSet config.RuleSet) bool { + if reqData.ToriiSessionID == "" { + return false + } + parts := strings.Split(reqData.ToriiSessionID, ":") + if len(parts) != 2 { + return false + } + timestamp := parts[0] + expectedHash := parts[1] + + timeNow := time.Now().Unix() + parsedTimestamp, err := strconv.ParseInt(timestamp, 10, 64) + if err != nil { + log.Printf("Error parsing timestamp: %v", err) + return false + } + + if timeNow-parsedTimestamp > ruleSet.CAPTCHARule.CaptchaChallengeTimeout { + return false + } + + mac := hmac.New(sha512.New, []byte(ruleSet.CAPTCHARule.SecretKey)) + mac.Write([]byte(fmt.Sprintf("%d%s%sCAPTCHA-SESSION-ID", parsedTimestamp, reqData.Host, reqData.UserAgent))) + computedHash := fmt.Sprintf("%x", mac.Sum(nil)) + + return hmac.Equal([]byte(computedHash), []byte(expectedHash)) +} + func GenClearance(reqData dataType.UserRequest, ruleSet config.RuleSet) []byte { timeNow := time.Now().Unix() mac := hmac.New(sha512.New, []byte(ruleSet.CAPTCHARule.SecretKey)) - mac.Write([]byte(fmt.Sprintf("%d%s%s", timeNow, reqData.Host, reqData.UserAgent))) + mac.Write([]byte(fmt.Sprintf("%d%s%sCAPTCHA-CLEARANCE", timeNow, reqData.Host, reqData.UserAgent))) return []byte(fmt.Sprintf("%s:%s", fmt.Sprintf("%d", time.Now().Unix()), fmt.Sprintf("%x", mac.Sum(nil)))) } @@ -123,7 +164,7 @@ func verifyClearanceCookie(reqData dataType.UserRequest, ruleSet config.RuleSet) } mac := hmac.New(sha512.New, []byte(ruleSet.CAPTCHARule.SecretKey)) - mac.Write([]byte(fmt.Sprintf("%d%s%s", parsedTimestamp, reqData.Host, reqData.UserAgent))) + mac.Write([]byte(fmt.Sprintf("%d%s%sCAPTCHA-CLEARANCE", parsedTimestamp, reqData.Host, reqData.UserAgent))) computedHash := fmt.Sprintf("%x", mac.Sum(nil)) return hmac.Equal([]byte(computedHash), []byte(expectedHash)) diff --git a/internal/dataType/type.go b/internal/dataType/type.go index db83d66..3052c41 100644 --- a/internal/dataType/type.go +++ b/internal/dataType/type.go @@ -11,7 +11,8 @@ type UserRequest struct { } type CaptchaRule struct { - SecretKey string `yaml:"secret_key"` - CaptchaValidateTime int64 `yaml:"captcha_validate_time"` - HCaptchaSecret string `yaml:"hcaptcha_secret"` + SecretKey string `yaml:"secret_key"` + CaptchaValidateTime int64 `yaml:"captcha_validate_time"` + CaptchaChallengeTimeout int64 `yaml:"captcha_challenge_timeout"` + HCaptchaSecret string `yaml:"hcaptcha_secret"` } diff --git a/internal/server/checker.go b/internal/server/checker.go index 9d048f3..8e5df8a 100644 --- a/internal/server/checker.go +++ b/internal/server/checker.go @@ -70,8 +70,9 @@ func CheckMain(w http.ResponseWriter, userRequestData dataType.UserRequest, rule http.Error(w, "500 - Internal Server Error", http.StatusInternalServerError) return } - w.WriteHeader(http.StatusServiceUnavailable) + w.Header().Set("Set-Cookie", "__torii_session_id="+string(decision.ResponseData)+"; Path=/; Max-Age=86400; Priority=High; HttpOnly;") w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusServiceUnavailable) if err = tpl.Execute(w, nil); err != nil { log.Printf("Error template: %v", err) http.Error(w, "500 - Internal Server Error", http.StatusInternalServerError) diff --git a/internal/server/torii.go b/internal/server/torii.go index c45a74f..c2696db 100644 --- a/internal/server/torii.go +++ b/internal/server/torii.go @@ -29,13 +29,21 @@ func CheckTorii(w http.ResponseWriter, r *http.Request, reqData dataType.UserReq } return } else if bytes.Compare(decision.ResponseData, []byte("good")) == 0 { - w.Header().Set("Set-Cookie", "__torii_clearance="+string(check.GenClearance(reqData, *ruleSet))+"; Path=/; HttpOnly") + w.Header().Set("Set-Cookie", "__torii_clearance="+string(check.GenClearance(reqData, *ruleSet))+"; Path=/; Max-Age=86400; Priority=High; HttpOnly;") w.WriteHeader(http.StatusOK) _, err := w.Write(decision.ResponseData) if err != nil { log.Printf("Error writing response: %v", err) return } + } else if bytes.Compare(decision.ResponseData, []byte("timeout")) == 0 { + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte("timeout")) + if err != nil { + log.Printf("Error writing response: %v", err) + return + } + return } else { //should not be here w.WriteHeader(http.StatusInternalServerError)