feat(caddy): 支持分销站点独立域名 On-Demand TLS 自动签发 SSL 证书
- 后端新增 GET /api/distribution/check-domain 作为 Caddy on_demand_tls ask 白名单端点 - 补充 CheckDomain 相关场景自动化测试 - Caddyfile 启用 on_demand_tls 并配置动态分销前台入口
This commit is contained in:
@@ -378,6 +378,27 @@ func (h *Handler) GetPublicSiteInfo(c *gin.Context) {
|
||||
response.OK(c, info)
|
||||
}
|
||||
|
||||
// CheckDomain 供网关(如 Caddy on_demand_tls ask)在按需申请 SSL 证书时校验域名白名单。
|
||||
// 仅当域名属于系统中正常运行(active)的分销站点时返回 200,其余情况返回 404,
|
||||
// 防止任意外部未授权域名解析到该服务器刷爆 Let's Encrypt 证书申请配额。
|
||||
func (h *Handler) CheckDomain(c *gin.Context) {
|
||||
domain := strings.ToLower(strings.TrimSpace(c.Query("domain")))
|
||||
if domain == "" {
|
||||
c.String(http.StatusBadRequest, "domain required")
|
||||
return
|
||||
}
|
||||
if colon := strings.Index(domain, ":"); colon != -1 {
|
||||
domain = domain[:colon]
|
||||
}
|
||||
|
||||
info, err := h.service.GetPublicSiteInfo(c.Request.Context(), "", domain)
|
||||
if err != nil || info == nil || info.Status != "active" {
|
||||
c.String(http.StatusNotFound, "domain not allowed")
|
||||
return
|
||||
}
|
||||
c.String(http.StatusOK, "ok")
|
||||
}
|
||||
|
||||
func (h *Handler) ListPaymentChannels(c *gin.Context) {
|
||||
siteID := parseQueryUint(c, "site_id")
|
||||
if siteID <= 0 {
|
||||
|
||||
@@ -55,6 +55,7 @@ func setupHandlerTestEnv(t *testing.T) *handlerTestEnv {
|
||||
pub := r.Group("/api/v1/distribution")
|
||||
{
|
||||
pub.GET("/site-info", h.GetPublicSiteInfo)
|
||||
pub.GET("/check-domain", h.CheckDomain)
|
||||
pub.GET("/sites/:id/listings", h.ListPublicSiteListings)
|
||||
pub.POST("/auth/login", h.Login)
|
||||
}
|
||||
@@ -187,6 +188,28 @@ func TestHandler_PublicEndpoints(t *testing.T) {
|
||||
t.Fatalf("expected 404 for not found site, got %d: %s", w4.Code, w4.Body.String())
|
||||
}
|
||||
|
||||
// 1.5 CheckDomain 验证(Caddy on_demand_tls ask 接口)
|
||||
// 活跃站点域名返回 200 "ok"
|
||||
w5 := env.request("GET", "/api/v1/distribution/check-domain?domain=pub.example.com", nil, 0, nil)
|
||||
if w5.Code != http.StatusOK || w5.Body.String() != "ok" {
|
||||
t.Fatalf("expected 200 ok, got %d: %s", w5.Code, w5.Body.String())
|
||||
}
|
||||
// 带端口号时正常截断并校验通过
|
||||
w5Port := env.request("GET", "/api/v1/distribution/check-domain?domain=pub.example.com:443", nil, 0, nil)
|
||||
if w5Port.Code != http.StatusOK || w5Port.Body.String() != "ok" {
|
||||
t.Fatalf("expected 200 ok with port, got %d: %s", w5Port.Code, w5Port.Body.String())
|
||||
}
|
||||
// 未绑定或不存在的域名返回 404
|
||||
w6 := env.request("GET", "/api/v1/distribution/check-domain?domain=unregistered.com", nil, 0, nil)
|
||||
if w6.Code != http.StatusNotFound {
|
||||
t.Fatalf("expected 404 for unregistered domain, got %d: %s", w6.Code, w6.Body.String())
|
||||
}
|
||||
// 空域名返回 400
|
||||
w7 := env.request("GET", "/api/v1/distribution/check-domain", nil, 0, nil)
|
||||
if w7.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected 400 for empty domain, got %d: %s", w7.Code, w7.Body.String())
|
||||
}
|
||||
|
||||
// 2. ListPublicSiteListings 验证
|
||||
acc := model.GameAccount{
|
||||
Title: "极品号-1", GameName: "cfhd", HafCoinAmount: 50_000_000, Status: "active",
|
||||
|
||||
Reference in New Issue
Block a user