feat: add unified authorization middleware
This commit is contained in:
@@ -31,7 +31,14 @@ func main() {
|
||||
config.New(ServiceKey)
|
||||
impl.NewImpl()
|
||||
|
||||
srv := allserver.New()
|
||||
srv, err := allserver.New(
|
||||
config.Spec.Authorization.Key,
|
||||
config.Spec.Authorization.Expire,
|
||||
config.Spec.Authorization.Anonymous,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := service.Expose(srv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -45,7 +52,7 @@ func main() {
|
||||
_ = srv.Stop(ctx)
|
||||
}()
|
||||
|
||||
if err := srv.Start(config.Spec.Server.GRPC.Addr, config.Spec.Server.HTTP.Addr, config.Spec.DynamicRPC.Allow); err != nil && err != http.ErrServerClosed {
|
||||
if err := srv.Start(config.Spec.Server.GRPC.Addr, config.Spec.Server.HTTP.Addr); err != nil && err != http.ErrServerClosed {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,6 @@ Server:
|
||||
BindIP: 0.0.0.0
|
||||
Port: 12001
|
||||
|
||||
# Dynamic JSON-to-protobuf unary RPC endpoint: POST /rpc/{module}/{service}/{method}
|
||||
# An empty allow list denies every dynamic call. Use "*" to expose all
|
||||
# reflection-visible unary methods, or list full service/method names.
|
||||
DynamicRPC:
|
||||
Allow:
|
||||
- "*"
|
||||
|
||||
Databases:
|
||||
Driver: postgres
|
||||
@@ -22,6 +16,32 @@ Databases:
|
||||
Cache: redis://null:Weidong1214@127.0.0.1:6379/1
|
||||
SecretKey: CHANGE_ME
|
||||
|
||||
# Authorization contains the HS256 signing key and maximum token lifetime in
|
||||
# seconds. Clients send the raw JWT in the Authorization header (no Bearer).
|
||||
Authorization:
|
||||
Key: CHANGE_ME_32_BYTE_JWT_SECRET_KEY
|
||||
Expire: 86400
|
||||
Anonymous:
|
||||
- /passport.Login/Pwd
|
||||
- /passport.Login/Code
|
||||
- /passport.Login/Quick
|
||||
- /passport.Register/Pwd
|
||||
- /passport.Register/Code
|
||||
- /passport.Forget/Verify
|
||||
- /passport.Forget/Reset
|
||||
- /passport.Verify/Request
|
||||
- /passport.Verify/JumioCallback
|
||||
- /market.Agency/Login
|
||||
- /mall.Staff/Login
|
||||
- /rest/fts/ping
|
||||
- /rest/fts/config
|
||||
- /rest/logs/ping
|
||||
- /rest/mgt/ping
|
||||
- /rest/mgt/session
|
||||
- /rest/mgt/login
|
||||
- /rest/mgt/refresh
|
||||
- /rest/mgt/reset
|
||||
|
||||
# Enabled services. Use "all" to enable every registered service.
|
||||
Services:
|
||||
- ads
|
||||
|
||||
18
all/go.mod
18
all/go.mod
@@ -52,7 +52,6 @@ require golang.org/x/net v0.57.0 // indirect
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.4 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
@@ -72,16 +71,6 @@ require (
|
||||
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||
github.com/gin-contrib/cors v1.7.7 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.9 // indirect
|
||||
github.com/go-openapi/swag/conv v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/pools v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
|
||||
github.com/go-pay/crypto v0.0.1 // indirect
|
||||
github.com/go-pay/errgroup v0.0.3 // indirect
|
||||
github.com/go-pay/gopay v1.5.122 // indirect
|
||||
@@ -95,7 +84,7 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
@@ -128,9 +117,6 @@ require (
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
github.com/swaggo/gin-swagger v1.6.1 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.155 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.155 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
@@ -146,11 +132,9 @@ require (
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
golang.org/x/arch v0.30.0 // indirect
|
||||
golang.org/x/crypto v0.54.0 // indirect
|
||||
golang.org/x/mod v0.38.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20260807164820-c8921c73eeea
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260807164820-c8921c73eeea // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260807164820-c8921c73eeea // indirect
|
||||
|
||||
43
all/go.sum
43
all/go.sum
@@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||
@@ -94,8 +92,6 @@ github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd
|
||||
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
|
||||
github.com/gin-contrib/cors v1.7.7 h1:Oh9joP463x7Mw72vhvJ61YQm8ODh9b04YR7vsOErD0Q=
|
||||
github.com/gin-contrib/cors v1.7.7/go.mod h1:K5tW0RkzJtWSiOdikXloy8VEZlgdVNpHNw8FpjUPNrE=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sessions v1.1.0 h1:00mhHfNEGF5sP2fwxa98aRqj1FOJdL6IkR86n2hOiBo=
|
||||
github.com/gin-contrib/sessions v1.1.0/go.mod h1:TyYZDIs6qCQg2SOoYPgMT9pAkmZceVNEJMcv5qbIy60=
|
||||
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
|
||||
@@ -106,33 +102,6 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
|
||||
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
|
||||
github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
|
||||
github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
|
||||
github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
|
||||
github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
|
||||
github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
|
||||
github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY=
|
||||
github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes=
|
||||
github.com/go-pay/errgroup v0.0.3 h1:DB4s8e8oWYDyETKQ1y1riMJ7y29zE1uIsMCSjEOFSbU=
|
||||
@@ -295,12 +264,6 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
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=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
|
||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.155 h1:142i5n5u0TcSyJM8YrQNPq1owhD7cXMvvWM45+BcrK0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.155/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.155 h1:MbrQONH8U74mKsa9aRhEnTYNhJBQfnGHQKMN8OEp9aE=
|
||||
@@ -381,8 +344,6 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
|
||||
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -476,8 +437,6 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -487,8 +446,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20260807164820-c8921c73eeea h1:EoNao6AiKb+bftjR8vFOxqtY1A4zLnvnfea+FBQicBo=
|
||||
google.golang.org/genproto v0.0.0-20260807164820-c8921c73eeea/go.mod h1:MVqdGj2rFowpxM1nWJoGZmKI3TGThZsWENFz9uc4l1g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260807164820-c8921c73eeea h1:Jifw/kjs/r3B0uszvls/m3c3tmZs2YHGM9C+rvxP9gY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260807164820-c8921c73eeea/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260807164820-c8921c73eeea h1:kVhQEPTpKQahD5+JSBTfBB19wcgQTTjAIn45MBqnyHk=
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ftsService "bsm/full/module/base/fts/service"
|
||||
mgtService "bsm/full/module/base/mgt/service"
|
||||
@@ -11,24 +12,26 @@ import (
|
||||
senderService "bsm/full/module/base/sender/service"
|
||||
walletService "bsm/full/module/finance/wallet/service"
|
||||
"git.apinb.com/bsm-sdk/core/conf"
|
||||
"git.apinb.com/bsm-sdk/core/env"
|
||||
"git.apinb.com/bsm-sdk/core/printer"
|
||||
coreVars "git.apinb.com/bsm-sdk/core/vars"
|
||||
)
|
||||
|
||||
type SrvConfig struct {
|
||||
conf.Base `yaml:",inline"`
|
||||
Server ServerConfig `yaml:"Server"`
|
||||
DynamicRPC DynamicRPCConfig `yaml:"DynamicRPC"`
|
||||
Databases *conf.DBConf `yaml:"Databases"`
|
||||
MicroService *conf.MicroServiceConf `yaml:"MicroService"`
|
||||
Rpc map[string]conf.RpcConf `yaml:"Rpc"`
|
||||
Apm *conf.ApmConf `yaml:"APM"`
|
||||
Etcd *conf.EtcdConf `yaml:"Etcd"`
|
||||
Services []string `yaml:"Services"`
|
||||
Fts *ftsService.Config `yaml:"Fts"`
|
||||
Mgt *mgtService.Config `yaml:"Mgt"`
|
||||
Passport *passportService.Config `yaml:"Passport"`
|
||||
Sender *senderService.Config `yaml:"Sender"`
|
||||
Wallet *walletService.Config `yaml:"Wallet"`
|
||||
conf.Base `yaml:",inline"`
|
||||
Server ServerConfig `yaml:"Server"`
|
||||
Authorization AuthorizationConfig `yaml:"Authorization"`
|
||||
Databases *conf.DBConf `yaml:"Databases"`
|
||||
MicroService *conf.MicroServiceConf `yaml:"MicroService"`
|
||||
Rpc map[string]conf.RpcConf `yaml:"Rpc"`
|
||||
Apm *conf.ApmConf `yaml:"APM"`
|
||||
Etcd *conf.EtcdConf `yaml:"Etcd"`
|
||||
Services []string `yaml:"Services"`
|
||||
Fts *ftsService.Config `yaml:"Fts"`
|
||||
Mgt *mgtService.Config `yaml:"Mgt"`
|
||||
Passport *passportService.Config `yaml:"Passport"`
|
||||
Sender *senderService.Config `yaml:"Sender"`
|
||||
Wallet *walletService.Config `yaml:"Wallet"`
|
||||
}
|
||||
|
||||
type ListenerConfig struct {
|
||||
@@ -42,8 +45,10 @@ type ServerConfig struct {
|
||||
HTTP ListenerConfig `yaml:"HTTP"`
|
||||
}
|
||||
|
||||
type DynamicRPCConfig struct {
|
||||
Allow []string `yaml:"Allow"`
|
||||
type AuthorizationConfig struct {
|
||||
Key string `yaml:"Key"`
|
||||
Expire int64 `yaml:"Expire"`
|
||||
Anonymous []string `yaml:"Anonymous"`
|
||||
}
|
||||
|
||||
var Spec SrvConfig
|
||||
@@ -55,6 +60,18 @@ func New(serviceKey string) {
|
||||
if Spec.Server.GRPC.Addr == Spec.Server.HTTP.Addr {
|
||||
panic("gRPC and HTTP listeners must use different addresses")
|
||||
}
|
||||
if strings.TrimSpace(Spec.Authorization.Key) == "" {
|
||||
panic("Authorization.Key must not be empty")
|
||||
}
|
||||
keyLength := len(Spec.Authorization.Key)
|
||||
if keyLength != 16 && keyLength != 24 && keyLength != 32 {
|
||||
panic("Authorization.Key must contain 16, 24, or 32 bytes")
|
||||
}
|
||||
if Spec.Authorization.Expire <= 0 {
|
||||
panic("Authorization.Expire must be greater than zero")
|
||||
}
|
||||
env.NewEnv().JwtSecretKey = Spec.Authorization.Key
|
||||
coreVars.JwtExpire = time.Duration(Spec.Authorization.Expire) * time.Second
|
||||
// Keep the embedded base address meaningful for module configurations that
|
||||
// still consume it, while all itself uses the two explicit listeners.
|
||||
Spec.BindIP, Spec.Port, Spec.Addr = Spec.Server.HTTP.BindIP, Spec.Server.HTTP.Port, Spec.Server.HTTP.Addr
|
||||
|
||||
@@ -23,8 +23,12 @@ func TestAllDevConfig(t *testing.T) {
|
||||
if cfg.Server.GRPC.Port == "" || cfg.Server.HTTP.Port == "" || cfg.Server.GRPC.Port == cfg.Server.HTTP.Port {
|
||||
t.Fatal("separate gRPC and HTTP ports are required")
|
||||
}
|
||||
if len(cfg.DynamicRPC.Allow) == 0 {
|
||||
t.Fatal("dynamic RPC allow list must be explicit")
|
||||
if cfg.Authorization.Key == "" || cfg.Authorization.Expire <= 0 {
|
||||
t.Fatal("authorization key and expiration are required")
|
||||
}
|
||||
keyLength := len(cfg.Authorization.Key)
|
||||
if keyLength != 16 && keyLength != 24 && keyLength != 32 {
|
||||
t.Fatal("authorization key must be compatible with the JWT issuer")
|
||||
}
|
||||
if cfg.Fts == nil || cfg.Mgt == nil || cfg.Passport == nil || cfg.Sender == nil || cfg.Wallet == nil {
|
||||
t.Fatal("service-specific configuration is incomplete")
|
||||
|
||||
123
all/internal/server/authorization.go
Normal file
123
all/internal/server/authorization.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type authorization struct {
|
||||
key []byte
|
||||
expire time.Duration
|
||||
anonymous map[string]struct{}
|
||||
}
|
||||
|
||||
func newAuthorization(key string, expireSeconds int64, anonymous []string) (*authorization, error) {
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
return nil, errors.New("authorization key must not be empty")
|
||||
}
|
||||
if expireSeconds <= 0 {
|
||||
return nil, errors.New("authorization expiration must be greater than zero")
|
||||
}
|
||||
allowed := make(map[string]struct{}, len(anonymous))
|
||||
for _, item := range anonymous {
|
||||
if item = normalizePath(item); item != "" {
|
||||
allowed[item] = struct{}{}
|
||||
}
|
||||
}
|
||||
return &authorization{key: []byte(key), expire: time.Duration(expireSeconds) * time.Second, anonymous: allowed}, nil
|
||||
}
|
||||
|
||||
func (a *authorization) unaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
if !a.isAnonymous(info.FullMethod) {
|
||||
values := metadata.ValueFromIncomingContext(ctx, "authorization")
|
||||
if len(values) == 0 {
|
||||
return nil, status.Error(codes.Unauthenticated, "authorization is required")
|
||||
}
|
||||
if err := a.validate(values[0]); err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, err.Error())
|
||||
}
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
func (a *authorization) httpMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath := canonicalHTTPPath(r.URL.Path)
|
||||
if !a.isAnonymous(requestPath) {
|
||||
if err := a.validate(r.Header.Get("Authorization")); err != nil {
|
||||
writeHTTPAuthorizationError(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (a *authorization) validate(raw string) error {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return errors.New("authorization is required")
|
||||
}
|
||||
claims := &jwt.RegisteredClaims{}
|
||||
tokenValue, err := jwt.ParseWithClaims(raw, claims, func(tokenValue *jwt.Token) (any, error) {
|
||||
if tokenValue.Method != jwt.SigningMethodHS256 {
|
||||
return nil, fmt.Errorf("unexpected signing method: %s", tokenValue.Method.Alg())
|
||||
}
|
||||
return a.key, nil
|
||||
}, jwt.WithExpirationRequired(), jwt.WithIssuedAt(), jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
||||
if err != nil || !tokenValue.Valid {
|
||||
return errors.New("authorization is invalid or expired")
|
||||
}
|
||||
if claims.IssuedAt == nil {
|
||||
return errors.New("authorization issued-at is required")
|
||||
}
|
||||
now := time.Now()
|
||||
if claims.IssuedAt.Time.After(now) || now.Sub(claims.IssuedAt.Time) > a.expire {
|
||||
return errors.New("authorization is expired")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *authorization) isAnonymous(requestPath string) bool {
|
||||
requestPath = normalizePath(requestPath)
|
||||
if strings.HasPrefix(requestPath, "/grpc.reflection.") || strings.HasPrefix(requestPath, "/grpc.health.") {
|
||||
return true
|
||||
}
|
||||
_, ok := a.anonymous[requestPath]
|
||||
return ok
|
||||
}
|
||||
|
||||
func canonicalHTTPPath(requestPath string) string {
|
||||
normalized := normalizePath(requestPath)
|
||||
parts := strings.Split(strings.TrimPrefix(normalized, "/"), "/")
|
||||
if len(parts) == 4 && parts[0] == "rpc" {
|
||||
return "/" + parts[1] + "." + parts[2] + "/" + parts[3]
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func normalizePath(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return ""
|
||||
}
|
||||
return path.Clean("/" + strings.TrimPrefix(value, "/"))
|
||||
}
|
||||
|
||||
func writeHTTPAuthorizationError(w http.ResponseWriter, err error) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprintf(w, `{"code":%d,"message":%q}`, codes.Unauthenticated, err.Error())
|
||||
}
|
||||
100
all/internal/server/authorization_test.go
Normal file
100
all/internal/server/authorization_test.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const testAuthorizationKey = "0123456789abcdef0123456789abcdef"
|
||||
|
||||
func TestHTTPAuthorization(t *testing.T) {
|
||||
auth, err := newAuthorization(testAuthorizationKey, 3600, []string{"/passport.Login/Pwd", "/rest/fts/ping"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
|
||||
handler := auth.httpMiddleware(next)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
token string
|
||||
want int
|
||||
}{
|
||||
{name: "grpc gateway anonymous", path: "/passport.Login/Pwd", want: http.StatusNoContent},
|
||||
{name: "dynamic rpc anonymous", path: "/rpc/passport/Login/Pwd", want: http.StatusNoContent},
|
||||
{name: "rest anonymous", path: "/rest/fts/ping", want: http.StatusNoContent},
|
||||
{name: "missing token", path: "/passport.Account/Get", want: http.StatusOK},
|
||||
{name: "valid raw token", path: "/passport.Account/Get", token: signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusNoContent},
|
||||
{name: "bearer rejected", path: "/passport.Account/Get", token: "Bearer " + signedToken(t, time.Now(), time.Now().Add(time.Hour)), want: http.StatusOK},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, test.path, nil)
|
||||
if test.token != "" {
|
||||
request.Header.Set("Authorization", test.token)
|
||||
}
|
||||
response := httptest.NewRecorder()
|
||||
handler.ServeHTTP(response, request)
|
||||
if response.Code != test.want {
|
||||
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
||||
}
|
||||
if test.want == http.StatusOK && !strings.Contains(response.Body.String(), `"code":16`) {
|
||||
t.Fatalf("unexpected authorization error: %s", response.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthorizationRejectsTokenOlderThanConfiguredLifetime(t *testing.T) {
|
||||
auth, err := newAuthorization(testAuthorizationKey, 60, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := auth.validate(signedToken(t, time.Now().Add(-2*time.Minute), time.Now().Add(time.Hour))); err == nil {
|
||||
t.Fatal("expected token older than configured lifetime to be rejected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGRPCAuthorization(t *testing.T) {
|
||||
auth, err := newAuthorization(testAuthorizationKey, 3600, []string{"/passport.Login/Pwd"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
handler := func(context.Context, any) (any, error) { return "ok", nil }
|
||||
|
||||
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Login/Pwd"}, handler); err != nil {
|
||||
t.Fatalf("anonymous method failed: %v", err)
|
||||
}
|
||||
if _, err := auth.unaryInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); status.Code(err) != codes.Unauthenticated {
|
||||
t.Fatalf("expected unauthenticated, got %v", err)
|
||||
}
|
||||
ctx := metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorization", signedToken(t, time.Now(), time.Now().Add(time.Hour))))
|
||||
if _, err := auth.unaryInterceptor(ctx, nil, &grpc.UnaryServerInfo{FullMethod: "/passport.Account/Get"}, handler); err != nil {
|
||||
t.Fatalf("valid token failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func signedToken(t *testing.T, issuedAt, expiresAt time.Time) string {
|
||||
t.Helper()
|
||||
claims := jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(issuedAt),
|
||||
NotBefore: jwt.NewNumericDate(issuedAt),
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
}
|
||||
value, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(testAuthorizationKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -30,7 +30,6 @@ const maxDynamicRPCBody = 4 << 20
|
||||
|
||||
type dynamicGateway struct {
|
||||
conn *grpc.ClientConn
|
||||
allow map[string]struct{}
|
||||
mu sync.RWMutex
|
||||
cache map[string]protoreflect.MethodDescriptor
|
||||
}
|
||||
@@ -42,18 +41,12 @@ type dynamicRPCResponse struct {
|
||||
Details []json.RawMessage `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func newDynamicGateway(grpcAddr string, allow []string) (*dynamicGateway, error) {
|
||||
func newDynamicGateway(grpcAddr string) (*dynamicGateway, error) {
|
||||
conn, err := grpc.NewClient(reflectionTarget(grpcAddr), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create dynamic gRPC client: %w", err)
|
||||
}
|
||||
allowed := make(map[string]struct{}, len(allow))
|
||||
for _, item := range allow {
|
||||
if item = strings.TrimSpace(item); item != "" {
|
||||
allowed[item] = struct{}{}
|
||||
}
|
||||
}
|
||||
return &dynamicGateway{conn: conn, allow: allowed, cache: make(map[string]protoreflect.MethodDescriptor)}, nil
|
||||
return &dynamicGateway{conn: conn, cache: make(map[string]protoreflect.MethodDescriptor)}, nil
|
||||
}
|
||||
|
||||
func reflectionTarget(addr string) string {
|
||||
@@ -78,12 +71,6 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
serviceName := moduleName + "." + serviceShortName
|
||||
fullMethod := serviceName + "." + methodName
|
||||
if !g.isAllowed(serviceName, fullMethod) {
|
||||
writeDynamicError(c, status.Error(codes.PermissionDenied, "dynamic RPC method is not allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
descriptor, err := g.resolveMethod(c.Request.Context(), serviceName, methodName)
|
||||
if err != nil {
|
||||
writeDynamicError(c, err)
|
||||
@@ -125,15 +112,6 @@ func (g *dynamicGateway) handle(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, dynamicRPCResponse{Code: int32(codes.OK), Message: codes.OK.String(), Data: data})
|
||||
}
|
||||
|
||||
func (g *dynamicGateway) isAllowed(serviceName, fullMethod string) bool {
|
||||
for _, key := range []string{"*", serviceName, fullMethod} {
|
||||
if _, ok := g.allow[key]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *dynamicGateway) resolveMethod(ctx context.Context, serviceName, methodName string) (protoreflect.MethodDescriptor, error) {
|
||||
cacheKey := serviceName + "." + methodName
|
||||
g.mu.RLock()
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/health"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/metadata"
|
||||
@@ -33,7 +32,7 @@ func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) {
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
gateway, err := newDynamicGateway(listener.Addr().String(), []string{"grpc.health.v1.Health.Check"})
|
||||
gateway, err := newDynamicGateway(listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -61,26 +60,6 @@ func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) {
|
||||
gateway := &dynamicGateway{allow: map[string]struct{}{}}
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
engine.POST("/rpc/:module/:service/:method", gateway.handle)
|
||||
request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1/Health/Check", strings.NewReader(`{}`))
|
||||
response := httptest.NewRecorder()
|
||||
engine.ServeHTTP(response, request)
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("dynamic RPC errors must use HTTP 200, got %d", response.Code)
|
||||
}
|
||||
var payload dynamicRPCResponse
|
||||
if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if payload.Code != int32(codes.PermissionDenied) {
|
||||
t.Fatalf("expected permission denied, got %s", response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutgoingMetadataFiltersHeaders(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
request.Header.Set("Authorization", "Bearer token")
|
||||
|
||||
@@ -21,10 +21,15 @@ type Server struct {
|
||||
HTTP *gin.Engine
|
||||
http *http.Server
|
||||
dynamic *dynamicGateway
|
||||
auth *authorization
|
||||
}
|
||||
|
||||
func New() *Server {
|
||||
grpcServer := grpc.NewServer()
|
||||
func New(key string, expireSeconds int64, anonymous []string) (*Server, error) {
|
||||
auth, err := newAuthorization(key, expireSeconds, anonymous)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
grpcServer := grpc.NewServer(grpc.UnaryInterceptor(auth.unaryInterceptor))
|
||||
reflection.Register(grpcServer)
|
||||
engine := gin.New()
|
||||
engine.Use(gin.Logger(), gin.Recovery())
|
||||
@@ -32,10 +37,11 @@ func New() *Server {
|
||||
GRPC: grpcServer,
|
||||
Gateway: gwRuntime.NewServeMux(),
|
||||
HTTP: engine,
|
||||
}
|
||||
auth: auth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error {
|
||||
func (s *Server) Start(grpcAddr, httpAddr string) error {
|
||||
grpcListener, err := net.Listen("tcp", grpcAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen gRPC on %s: %w", grpcAddr, err)
|
||||
@@ -46,7 +52,7 @@ func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error {
|
||||
return fmt.Errorf("listen HTTP on %s: %w", httpAddr, err)
|
||||
}
|
||||
|
||||
s.dynamic, err = newDynamicGateway(grpcAddr, allow)
|
||||
s.dynamic, err = newDynamicGateway(grpcAddr)
|
||||
if err != nil {
|
||||
_ = grpcListener.Close()
|
||||
_ = httpListener.Close()
|
||||
@@ -65,7 +71,7 @@ func (s *Server) Start(grpcAddr, httpAddr string, allow []string) error {
|
||||
})
|
||||
s.http = &http.Server{
|
||||
Addr: httpAddr,
|
||||
Handler: handler,
|
||||
Handler: s.auth.httpMiddleware(handler),
|
||||
ReadHeaderTimeout: 10 * time.Second,
|
||||
IdleTimeout: 120 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
|
||||
@@ -9,7 +9,10 @@ import (
|
||||
)
|
||||
|
||||
func TestHTTPRouterIsAvailable(t *testing.T) {
|
||||
srv := New()
|
||||
srv, err := New("0123456789abcdef0123456789abcdef", 3600, []string{"/healthz"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv.HTTP.GET("/healthz", func(c *gin.Context) { c.Status(http.StatusNoContent) })
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/healthz", nil)
|
||||
|
||||
Reference in New Issue
Block a user