API route to retrieve RDAP/WHOIS information as domaininfo
This commit is contained in:
parent
e862e24c52
commit
32c09bbb86
13 changed files with 588 additions and 1 deletions
8
go.mod
8
go.mod
|
|
@ -17,8 +17,11 @@ require (
|
|||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/lib/pq v1.11.2
|
||||
github.com/likexian/whois v1.15.7
|
||||
github.com/likexian/whois-parser v1.24.21
|
||||
github.com/miekg/dns v1.1.72
|
||||
github.com/mileusna/useragent v1.3.5
|
||||
github.com/openrdap/rdap v0.9.1
|
||||
github.com/oracle/nosql-go-sdk v1.4.7
|
||||
github.com/ovh/go-ovh v1.9.0
|
||||
github.com/prometheus-community/pro-bing v0.8.0
|
||||
|
|
@ -54,6 +57,8 @@ require (
|
|||
github.com/PuerkitoBio/goquery v1.11.0 // indirect
|
||||
github.com/Shopify/goreferrer v0.0.0-20250617153402-88c1d9a79b05 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.3.0 // indirect
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
|
|
@ -158,6 +163,7 @@ require (
|
|||
github.com/labstack/echo/v4 v4.15.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/likexian/gokit v0.25.16 // indirect
|
||||
github.com/luadns/luadns-go v0.3.0 // indirect
|
||||
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
|
|
@ -208,6 +214,7 @@ require (
|
|||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/vultr/govultr/v2 v2.17.2 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
github.com/yosssi/ace v0.0.5 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
|
|
@ -217,7 +224,6 @@ require (
|
|||
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
go.uber.org/mock v0.6.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
|
|
|
|||
15
go.sum
15
go.sum
|
|
@ -62,6 +62,10 @@ github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY
|
|||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.3.0 h1:iGVPe/gPqzpXggbbmVLWR0TyJ9UoPoqKL+kspjseZzE=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.3.0/go.mod h1:76JtkiCKMwTdTOlKe9goT4Md+oWjfMouGBQgy+u1bgc=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107 h1:qagvUyrgOnBIlVRQWOyCZGVKUIYbMBdGdJ104vBpRFU=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.107/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/altcha-org/altcha-lib-go v1.0.0 h1:7oPti0aUS+YCep8nwt5b9g4jYfCU55ZruWESL8G9K5M=
|
||||
|
|
@ -432,6 +436,12 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/likexian/gokit v0.25.16 h1:wwBeUIN/OdoPp6t00xTnZE8Di/+s969Bl5N2Kw6bzP8=
|
||||
github.com/likexian/gokit v0.25.16/go.mod h1:Wqd4f+iifV0qxA1N3MqePJTUsmRy/lpst9/yXriDx/4=
|
||||
github.com/likexian/whois v1.15.7 h1:sajjDhi2bVD71AHJhjV7jLYxN92H4AWhTwxM8hmj7c0=
|
||||
github.com/likexian/whois v1.15.7/go.mod h1:kdPQtYb+7SQVftBEbCblDadUkycN7Mg1k1/Li/rwvmc=
|
||||
github.com/likexian/whois-parser v1.24.21 h1:MxsrGRxDOiZIVp7q7N/yAIbKuN4QAkGjCpOtTDA5OsM=
|
||||
github.com/likexian/whois-parser v1.24.21/go.mod h1:o3DUruO65Pb8WXCJCTlSVkTbwuYVrBCeoMTw2q0mxY4=
|
||||
github.com/luadns/luadns-go v0.3.0 h1:mN2yhFv/LnGvPw/HmvYUhXe+lc95oXUqjlYVeJeOJng=
|
||||
github.com/luadns/luadns-go v0.3.0/go.mod h1:DmPXbrGMpynq1YNDpvgww3NP5Zf4wXM5raAbGrp5L+8=
|
||||
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
|
||||
|
|
@ -484,6 +494,8 @@ github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGm
|
|||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/openrdap/rdap v0.9.1 h1:Rv6YbanbiVPsKRvOLdUmlU1AL5+2OFuEFLjFN+mQsCM=
|
||||
github.com/openrdap/rdap v0.9.1/go.mod h1:vKSiotbsENrjM/vaHXLddXbW8iQkBfa+ldEuYEjyLTQ=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
|
||||
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
|
||||
github.com/oracle/nosql-go-sdk v1.4.7 h1:dqVBSMulObDj0JHm1mAncTHrQg8wIiQJNC0JRNKPACg=
|
||||
|
|
@ -573,6 +585,7 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
|
|
@ -625,6 +638,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo
|
|||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
|
||||
|
|
|
|||
74
internal/api/controller/domain_info.go
Normal file
74
internal/api/controller/domain_info.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type DomainInfoController struct {
|
||||
diuService happydns.DomainInfoUsecase
|
||||
}
|
||||
|
||||
func NewDomainInfoController(diuService happydns.DomainInfoUsecase) *DomainInfoController {
|
||||
return &DomainInfoController{
|
||||
diuService: diuService,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDomainInfo retrieves domain's administrative information.
|
||||
//
|
||||
// @Summary Get domain administrative information
|
||||
// @Schemes
|
||||
// @Description Retrieve domain's administrative information.
|
||||
// @Tags domains
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security securitydefinitions.basic
|
||||
// @Param domain path string true "Domain name"
|
||||
// @Success 200 {object} happydns.DomainInfo
|
||||
// @Failure 400 {object} happydns.ErrorResponse "Invalid input"
|
||||
// @Failure 500 {object} happydns.ErrorResponse
|
||||
// @Router /domaininfo/{domain} [post]
|
||||
func (dc *DomainInfoController) GetDomainInfo(c *gin.Context) {
|
||||
domain := c.Param("domain")
|
||||
if dn, ok := c.Get("domain"); ok {
|
||||
domain = dn.(*happydns.Domain).DomainName
|
||||
}
|
||||
|
||||
info, err := dc.diuService.GetDomainInfo(c.Request.Context(), happydns.Origin(domain))
|
||||
if err != nil {
|
||||
if errors.Is(err, happydns.DomainDoesNotExist) {
|
||||
c.AbortWithStatusJSON(http.StatusNotFound, happydns.ErrorResponse{Message: err.Error()})
|
||||
} else {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, happydns.ErrorResponse{Message: err.Error()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, info)
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ func DeclareDomainRoutes(
|
|||
checkerUC happydns.CheckerUsecase,
|
||||
checkResultUC happydns.CheckResultUsecase,
|
||||
checkScheduler happydns.SchedulerUsecase,
|
||||
domainInfoUC happydns.DomainInfoUsecase,
|
||||
tpc *controller.CheckerController,
|
||||
) {
|
||||
dc := controller.NewDomainController(
|
||||
|
|
@ -61,6 +62,7 @@ func DeclareDomainRoutes(
|
|||
apiDomainsRoutes.PUT("", dc.UpdateDomain)
|
||||
apiDomainsRoutes.DELETE("", dc.DelDomain)
|
||||
|
||||
DeclareDomainInfoRoutes(apiDomainsRoutes.Group("/info"), domainInfoUC)
|
||||
DeclareDomainLogRoutes(apiDomainsRoutes, domainLogUC)
|
||||
|
||||
// Declare test result routes for domain scope
|
||||
|
|
|
|||
37
internal/api/route/domain_info.go
Normal file
37
internal/api/route/domain_info.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.happydns.org/happyDomain/internal/api/controller"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
func DeclareDomainInfoRoutes(router *gin.RouterGroup, domainInfoUC happydns.DomainInfoUsecase) {
|
||||
dc := controller.NewDomainInfoController(
|
||||
domainInfoUC,
|
||||
)
|
||||
|
||||
router.POST("", dc.GetDomainInfo)
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ type Dependencies struct {
|
|||
CheckerSchedule happydns.CheckerScheduleUsecase
|
||||
CheckScheduler happydns.SchedulerUsecase
|
||||
Domain happydns.DomainUsecase
|
||||
DomainInfo happydns.DomainInfoUsecase
|
||||
DomainLog happydns.DomainLogUsecase
|
||||
FailureTracker happydns.FailureTracker
|
||||
Provider happydns.ProviderUsecase
|
||||
|
|
@ -92,6 +93,7 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.RouterGroup, dep Dependenc
|
|||
dep.FailureTracker,
|
||||
)
|
||||
auc := DeclareAuthUserRoutes(apiRoutes, dep.AuthUser, lc)
|
||||
DeclareDomainInfoRoutes(apiRoutes.Group("/domaininfo/:domain"), dep.DomainInfo)
|
||||
DeclareProviderSpecsRoutes(apiRoutes, dep.ProviderSpecs)
|
||||
DeclareRegistrationRoutes(apiRoutes, dep.AuthUser, dep.CaptchaVerifier)
|
||||
DeclareResolverRoutes(apiRoutes, dep.Resolver)
|
||||
|
|
@ -124,6 +126,7 @@ func DeclareRoutes(cfg *happydns.Options, router *gin.RouterGroup, dep Dependenc
|
|||
dep.Checker,
|
||||
dep.CheckResult,
|
||||
dep.CheckScheduler,
|
||||
dep.DomainInfo,
|
||||
tpc,
|
||||
)
|
||||
DeclareProviderRoutes(apiAuthRoutes, dep.Provider)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import (
|
|||
zoneUC "git.happydns.org/happyDomain/internal/usecase/zone"
|
||||
zoneServiceUC "git.happydns.org/happyDomain/internal/usecase/zone_service"
|
||||
"git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/pkg/domaininfo"
|
||||
"git.happydns.org/happyDomain/web"
|
||||
)
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ type Usecases struct {
|
|||
checkResult happydns.CheckResultUsecase
|
||||
checkerSchedule happydns.CheckerScheduleUsecase
|
||||
domain happydns.DomainUsecase
|
||||
domainInfo happydns.DomainInfoUsecase
|
||||
domainLog happydns.DomainLogUsecase
|
||||
provider happydns.ProviderUsecase
|
||||
providerAdmin happydns.ProviderUsecase
|
||||
|
|
@ -237,6 +239,10 @@ func (app *App) initUsecases() {
|
|||
app.usecases.service = serviceService
|
||||
app.usecases.serviceSpecs = usecase.NewServiceSpecsUsecase()
|
||||
app.usecases.zone = zoneService
|
||||
app.usecases.domainInfo = usecase.NewDomainInfoUsecase(
|
||||
domaininfo.GetDomainRDAPInfo,
|
||||
domaininfo.GetDomainWhoisInfo,
|
||||
)
|
||||
app.usecases.domainLog = domainLogService
|
||||
|
||||
domainService := domainUC.NewService(
|
||||
|
|
@ -313,6 +319,7 @@ func (app *App) setupRouter() {
|
|||
CheckerSchedule: app.usecases.checkerSchedule,
|
||||
CheckScheduler: app.checkScheduler,
|
||||
Domain: app.usecases.domain,
|
||||
DomainInfo: app.usecases.domainInfo,
|
||||
DomainLog: app.usecases.domainLog,
|
||||
FailureTracker: app.failureTracker,
|
||||
Provider: app.usecases.provider,
|
||||
|
|
|
|||
62
internal/usecase/domain_info_usecase.go
Normal file
62
internal/usecase/domain_info_usecase.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
)
|
||||
|
||||
type domainInfoUsecase struct {
|
||||
getters []happydns.DomainInfoGetter
|
||||
}
|
||||
|
||||
func NewDomainInfoUsecase(getters ...happydns.DomainInfoGetter) happydns.DomainInfoUsecase {
|
||||
return &domainInfoUsecase{getters: getters}
|
||||
}
|
||||
|
||||
func (diu *domainInfoUsecase) GetDomainInfo(ctx context.Context, fqdn happydns.Origin) (*happydns.DomainInfo, error) {
|
||||
domain := happydns.Origin(strings.TrimSuffix(string(fqdn), "."))
|
||||
|
||||
var lastErr error
|
||||
for _, getter := range diu.getters {
|
||||
infos, err := getter(ctx, domain)
|
||||
if err != nil {
|
||||
if errors.Is(err, happydns.DomainDoesNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if infos == nil {
|
||||
lastErr = fmt.Errorf("no information found")
|
||||
continue
|
||||
}
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to retrieve RDAP/WHOIS info about the domain name: %w", lastErr)
|
||||
}
|
||||
48
model/domain_info.go
Normal file
48
model/domain_info.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package happydns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
DomainDoesNotExist = errors.New("domain name doesn't exist")
|
||||
)
|
||||
|
||||
type DomainInfo struct {
|
||||
Name string `json:"name"`
|
||||
Nameservers []string `json:"nameservers"`
|
||||
CreationDate *time.Time `json:"creation"`
|
||||
ExpirationDate *time.Time `json:"expiration"`
|
||||
Registrar string `json:"registrar"`
|
||||
RegistrarURL *string `json:"registrar_url"`
|
||||
Status []string `json:"status"`
|
||||
}
|
||||
|
||||
type DomainInfoGetter func(context.Context, Origin) (*DomainInfo, error)
|
||||
|
||||
type DomainInfoUsecase interface {
|
||||
GetDomainInfo(context.Context, Origin) (*DomainInfo, error)
|
||||
}
|
||||
111
pkg/domaininfo/rdap.go
Normal file
111
pkg/domaininfo/rdap.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package domaininfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
|
||||
"github.com/openrdap/rdap"
|
||||
)
|
||||
|
||||
func GetDomainRDAPInfo(ctx context.Context, domain happydns.Origin) (*happydns.DomainInfo, error) {
|
||||
client := &rdap.Client{}
|
||||
req := rdap.NewDomainRequest(string(domain)).WithContext(ctx)
|
||||
resp, err := client.Do(req)
|
||||
var domainInfo *rdap.Domain
|
||||
if err == nil {
|
||||
var ok bool
|
||||
domainInfo, ok = resp.Object.(*rdap.Domain)
|
||||
if !ok {
|
||||
err = context.DeadlineExceeded // shouldn't happen, but guard anyway
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if ce, ok := err.(*rdap.ClientError); ok && ce.Type == rdap.ObjectDoesNotExist {
|
||||
return nil, happydns.DomainDoesNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Registrar
|
||||
registrar := "Unknown"
|
||||
var registrar_url *string
|
||||
for _, ent := range domainInfo.Entities {
|
||||
if ent.Roles != nil {
|
||||
for _, role := range ent.Roles {
|
||||
if role == "registrar" && ent.VCard != nil && len(ent.VCard.Get("fn")) > 0 {
|
||||
registrar = ent.VCard.Get("fn")[0].Value.(string)
|
||||
if len(ent.VCard.Get("url")) > 0 {
|
||||
url := ent.VCard.Get("url")[0].Value.(string)
|
||||
registrar_url = &url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dates
|
||||
var expiration *time.Time
|
||||
var creation *time.Time
|
||||
for _, event := range domainInfo.Events {
|
||||
if (event.Action == "expiration" || event.Action == "registration") && event.Date != "" {
|
||||
date, err := time.Parse(time.RFC3339, event.Date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if event.Action == "expiration" {
|
||||
expiration = &date
|
||||
} else if event.Action == "registration" {
|
||||
creation = &date
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nameservers
|
||||
var nameservers []string
|
||||
for _, nameserver := range domainInfo.Nameservers {
|
||||
if nameserver.UnicodeName != "" {
|
||||
nameservers = append(nameservers, nameserver.UnicodeName)
|
||||
} else {
|
||||
nameservers = append(nameservers, nameserver.LDHName)
|
||||
}
|
||||
}
|
||||
|
||||
name := domainInfo.UnicodeName
|
||||
if name == "" {
|
||||
name = domainInfo.LDHName
|
||||
}
|
||||
|
||||
return &happydns.DomainInfo{
|
||||
Name: name,
|
||||
Nameservers: nameservers,
|
||||
CreationDate: creation,
|
||||
ExpirationDate: expiration,
|
||||
Registrar: registrar,
|
||||
RegistrarURL: registrar_url,
|
||||
Status: domainInfo.Status,
|
||||
}, nil
|
||||
}
|
||||
72
pkg/domaininfo/rdap_test.go
Normal file
72
pkg/domaininfo/rdap_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package domaininfo_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
happydns "git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/pkg/domaininfo"
|
||||
)
|
||||
|
||||
func TestGetDomainRDAPInfo_KnownDomain(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping live RDAP integration test")
|
||||
}
|
||||
|
||||
info, err := domaininfo.GetDomainRDAPInfo(context.Background(), happydns.Origin("example.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for example.com: %v", err)
|
||||
}
|
||||
if info == nil {
|
||||
t.Fatal("expected non-nil DomainInfo")
|
||||
}
|
||||
if info.Name == "" {
|
||||
t.Error("expected Name to be set")
|
||||
}
|
||||
if len(info.Nameservers) == 0 {
|
||||
t.Error("expected at least one nameserver")
|
||||
}
|
||||
if info.ExpirationDate == nil {
|
||||
t.Error("expected ExpirationDate to be set")
|
||||
}
|
||||
if info.CreationDate == nil {
|
||||
t.Error("expected CreationDate to be set")
|
||||
}
|
||||
if info.Registrar == "" {
|
||||
t.Error("expected Registrar to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDomainRDAPInfo_NonExistentDomain(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping live RDAP integration test")
|
||||
}
|
||||
|
||||
_, err := domaininfo.GetDomainRDAPInfo(context.Background(), happydns.Origin("this-domain-definitely-does-not-exist-xyz987654321.com"))
|
||||
if !errors.Is(err, happydns.DomainDoesNotExist) {
|
||||
t.Errorf("expected DomainDoesNotExist error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
75
pkg/domaininfo/whois.go
Normal file
75
pkg/domaininfo/whois.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package domaininfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.happydns.org/happyDomain/model"
|
||||
|
||||
"github.com/likexian/whois"
|
||||
"github.com/likexian/whois-parser"
|
||||
)
|
||||
|
||||
func GetDomainWhoisInfo(ctx context.Context, domain happydns.Origin) (*happydns.DomainInfo, error) {
|
||||
client := whois.NewClient()
|
||||
|
||||
// The whois library has no context support; derive a timeout from the
|
||||
// context deadline so we at least honour it approximately.
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
if remaining := time.Until(deadline); remaining > 0 {
|
||||
client.SetTimeout(remaining)
|
||||
}
|
||||
}
|
||||
|
||||
raw, err := client.Whois(string(domain))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result, err := whoisparser.Parse(raw)
|
||||
if err != nil {
|
||||
if errors.Is(err, whoisparser.ErrNotFoundDomain) {
|
||||
return nil, happydns.DomainDoesNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
registrar := "Unknown"
|
||||
var registrar_url *string
|
||||
if result.Registrar != nil {
|
||||
registrar = result.Registrar.Name
|
||||
registrar_url = &result.Registrar.ReferralURL
|
||||
}
|
||||
|
||||
return &happydns.DomainInfo{
|
||||
Name: result.Domain.Domain,
|
||||
Nameservers: result.Domain.NameServers,
|
||||
CreationDate: result.Domain.CreatedDateInTime,
|
||||
ExpirationDate: result.Domain.ExpirationDateInTime,
|
||||
Registrar: registrar,
|
||||
RegistrarURL: registrar_url,
|
||||
Status: result.Domain.Status,
|
||||
}, nil
|
||||
}
|
||||
75
pkg/domaininfo/whois_test.go
Normal file
75
pkg/domaininfo/whois_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// This file is part of the happyDomain (R) project.
|
||||
// Copyright (c) 2020-2025 happyDomain
|
||||
// Authors: Pierre-Olivier Mercier, et al.
|
||||
//
|
||||
// This program is offered under a commercial and under the AGPL license.
|
||||
// For commercial licensing, contact us at <contact@happydomain.org>.
|
||||
//
|
||||
// For AGPL licensing:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package domaininfo_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
happydns "git.happydns.org/happyDomain/model"
|
||||
"git.happydns.org/happyDomain/pkg/domaininfo"
|
||||
)
|
||||
|
||||
func TestGetDomainWhoisInfo_KnownDomain(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping live WHOIS integration test")
|
||||
}
|
||||
|
||||
info, err := domaininfo.GetDomainWhoisInfo(context.Background(), happydns.Origin("example.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for example.com: %v", err)
|
||||
}
|
||||
if info == nil {
|
||||
t.Fatal("expected non-nil DomainInfo")
|
||||
}
|
||||
if info.Name == "" {
|
||||
t.Error("expected Name to be set")
|
||||
}
|
||||
if len(info.Nameservers) == 0 {
|
||||
t.Error("expected at least one nameserver")
|
||||
}
|
||||
if info.ExpirationDate == nil {
|
||||
t.Error("expected ExpirationDate to be set")
|
||||
}
|
||||
if info.CreationDate == nil {
|
||||
t.Error("expected CreationDate to be set")
|
||||
}
|
||||
if info.Registrar == "" {
|
||||
t.Error("expected Registrar to be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDomainWhoisInfo_NilRegistrarURL(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping live WHOIS integration test")
|
||||
}
|
||||
|
||||
// example.com's registrar referral URL may be empty; the function should
|
||||
// not panic and should return a valid DomainInfo regardless.
|
||||
info, err := domaininfo.GetDomainWhoisInfo(context.Background(), happydns.Origin("example.com"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
// RegistrarURL may be nil or non-nil depending on the WHOIS data; just
|
||||
// confirm the function handled it without panicking.
|
||||
_ = info.RegistrarURL
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue