diff --git a/src/api.go b/src/api.go index 82dbfac..412fe3a 100644 --- a/src/api.go +++ b/src/api.go @@ -95,6 +95,19 @@ func initAPIs(targetMux *http.ServeMux) { authRouter.HandleFunc("/api/cert/checkDefault", handleDefaultCertCheck) authRouter.HandleFunc("/api/cert/delete", handleCertRemove) + //SSO and Oauth + authRouter.HandleFunc("/api/sso/status", ssoHandler.HandleSSOStatus) + authRouter.HandleFunc("/api/sso/start", ssoHandler.HandleStartSSOPortal) + authRouter.HandleFunc("/api/sso/stop", ssoHandler.HandleStopSSOPortal) + authRouter.HandleFunc("/api/sso/setPort", ssoHandler.HandlePortChange) + authRouter.HandleFunc("/api/sso/setAuthURL", ssoHandler.HandleSetAuthURL) + //authRouter.HandleFunc("/api/sso/registerApp", ssoHandler.HandleRegisterApp) + + authRouter.HandleFunc("/api/sso/user/list", ssoHandler.HandleListUser) + authRouter.HandleFunc("/api/sso/user/add", ssoHandler.HandleAddUser) + authRouter.HandleFunc("/api/sso/user/edit", ssoHandler.HandleEditUser) + authRouter.HandleFunc("/api/sso/user/remove", ssoHandler.HandleRemoveUser) + //Redirection config authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules) authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule) diff --git a/src/go.mod b/src/go.mod index 57b4f13..7625fb4 100644 --- a/src/go.mod +++ b/src/go.mod @@ -8,6 +8,7 @@ require ( github.com/boltdb/bolt v1.3.1 github.com/docker/docker v27.0.0+incompatible github.com/go-acme/lego/v4 v4.16.1 + github.com/go-oauth2/oauth2 v3.9.2+incompatible github.com/go-ping/ping v1.1.0 github.com/google/uuid v1.6.0 github.com/gorilla/sessions v1.2.2 @@ -20,6 +21,19 @@ require ( golang.org/x/text v0.15.0 ) +require ( + github.com/go-session/session v3.1.2+incompatible // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect + github.com/tidwall/buntdb v1.1.2 // indirect + github.com/tidwall/gjson v1.12.1 // indirect + github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect + github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect +) + require ( cloud.google.com/go/compute v1.25.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -82,10 +96,11 @@ require ( github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-oauth2/oauth2/v4 v4.5.2 github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/gofrs/uuid v4.4.0+incompatible // indirect + github.com/gofrs/uuid v4.4.0+incompatible github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -95,7 +110,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.2 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect - github.com/gorilla/csrf v1.7.2 // indirect + github.com/gorilla/csrf v1.7.2 github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect @@ -161,6 +176,7 @@ require ( github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect github.com/vultr/govultr/v2 v2.17.2 // indirect + github.com/xlzd/gotp v0.1.0 github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f // indirect github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 // indirect go.opencensus.io v0.24.0 // indirect diff --git a/src/go.sum b/src/go.sum index 0e2546d..b965060 100644 --- a/src/go.sum +++ b/src/go.sum @@ -67,12 +67,14 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -181,6 +183,7 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/exoscale/egoscale v0.102.3 h1:DYqN2ipoLKpiFoprRGQkp2av/Ze7sUYYlGhi1N62tfY= github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0VQas/UEGU5c= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -193,6 +196,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -215,6 +219,10 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/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-oauth2/oauth2 v3.9.2+incompatible h1:A8gSjq4110EgZDVk4ZtcpusynU2Fto9eM6sXvxL+EOs= +github.com/go-oauth2/oauth2 v3.9.2+incompatible/go.mod h1:GGcZ+i513KxN4yS7zBYfmwo3P+cyGvCS675uCNmWv/g= +github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ= +github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= @@ -228,6 +236,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg= +github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= @@ -240,11 +250,12 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= @@ -303,6 +314,7 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -316,6 +328,7 @@ github.com/googleapis/gax-go/v2 v2.12.2 h1:mhN09QQW1jEWeMF74zGR81R30z4VJzjZsfkUh github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/gophercloud/gophercloud v1.0.0 h1:9nTGx0jizmHxDobe4mck89FyQHVyA3CaXLIUSGJjP9k= github.com/gophercloud/gophercloud v1.0.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= @@ -327,6 +340,7 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= @@ -373,6 +387,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df h1:MZf03xP9WdakyXhOWuAD5uPK3wHh96wCsqe3hCMKh8E= github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/infobloxopen/infoblox-go-client v1.1.1 h1:728A6LbLjptj/7kZjHyIxQnm768PWHfGFm0HH8FnbtU= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= @@ -392,13 +407,18 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00= github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -500,6 +520,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= @@ -531,11 +552,14 @@ github.com/nzdjb/go-metaname v1.0.0 h1:sNASlZC1RM3nSudtBTE1a3ZVTDyTpjqI5WXRPrdZ9 github.com/nzdjb/go-metaname v1.0.0/go.mod h1:0GR0LshZax1Lz4VrOrfNSE4dGvTp7HGjiemdczXT2H4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= @@ -600,7 +624,10 @@ github.com/sacloud/packages-go v0.0.9 h1:GbinkBLC/eirFhHpLjoDW6JV7+95Rnd2d8RWj7A github.com/sacloud/packages-go v0.0.9/go.mod h1:k+EEUMF2LlncjbNIJNOqLyZ9wjTESPIWIk1OA7x9j2Q= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22 h1:wJrcTdddKOI8TFxs8cemnhKP2EmKy3yfUKHj3ZdfzYo= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.22/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -612,6 +639,7 @@ github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHei github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= @@ -655,6 +683,28 @@ github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz2 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ= +github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/buntdb v1.1.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE= +github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= +github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= +github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= +github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo= +github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= +github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip/v6 v6.23.0 h1:PsTdjortrEZ8IFFifEryzjVjOy9SgK4ahlnhKBBIQgA= github.com/transip/gotransip/v6 v6.23.0/go.mod h1:nzv9eN2tdsUrm5nG5ZX6AugYIU4qgsMwIn2c0EZLk8c= @@ -665,16 +715,29 @@ github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxW github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a h1:w4PK5/N9kq8PfNxBv8a5t1bqlYRrVT7XzT7iTPTtiPk= github.com/ultradns/ultradns-go-sdk v1.6.1-20231103022937-8589b6a/go.mod h1:Xwz7o+ExFtxR/i0aJDnTXuiccQJlOxDgNe6FsZC4TzQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= +github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f h1:cG+ehPRJSlqljSufLf1KXeXpUd1dLNjnzA18mZcB/O0= github.com/yandex-cloud/go-genproto v0.0.0-20220805142335-27b56ddae16f/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997 h1:2wzke3JH7OtN20WsNDZx2VH/TCmsbqtDEbXzjF+i05E= github.com/yandex-cloud/go-sdk v0.0.0-20220805164847-cf028e604997/go.mod h1:2CHKs/YGbCcNn/BPaCkEBwKz/FNCELi+MLILjR9RaTA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -723,6 +786,7 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= @@ -771,6 +835,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -838,6 +903,7 @@ golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -856,6 +922,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -939,6 +1006,7 @@ 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1002,6 +1070,8 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ns1/ns1-go.v2 v2.7.13 h1:r07CLALg18f/L1KIK1ZJdbirBV349UtYT1rDWGjnaTk= gopkg.in/ns1/ns1-go.v2 v2.7.13/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/oauth2.v3 v3.12.0 h1:yOffAPoolH/i2JxwmC+pgtnY3362iPahsDpLXfDFvNg= +gopkg.in/oauth2.v3 v3.12.0/go.mod h1:XEYgKqWX095YiPT+Aw5y3tCn+7/FMnlTFKrupgSiJ3I= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1015,6 +1085,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/main.go b/src/main.go index 6c64296..f4c4528 100644 --- a/src/main.go +++ b/src/main.go @@ -16,6 +16,7 @@ import ( "imuslab.com/zoraxy/mod/access" "imuslab.com/zoraxy/mod/acme" "imuslab.com/zoraxy/mod/auth" + "imuslab.com/zoraxy/mod/auth/sso" "imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/dockerux" "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" @@ -95,6 +96,7 @@ var ( staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs forwardProxy *forwardproxy.Handler //HTTP Forward proxy, basically VPN for web browser loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing + ssoHandler *sso.SSOHandler //Single Sign On handler //Helper modules EmailSender *email.Sender //Email sender that handle email sending diff --git a/src/mod/auth/sso/app.go b/src/mod/auth/sso/app.go new file mode 100644 index 0000000..a1e574d --- /dev/null +++ b/src/mod/auth/sso/app.go @@ -0,0 +1,34 @@ +package sso + +/* + app.go + + This file contains the app structure and app management + functions for the SSO module. + +*/ + +// RegisteredUpstreamApp is a structure that contains the information of an +// upstream app that is registered with the SSO server +type RegisteredUpstreamApp struct { + ID string + Secret string + Domain []string + Scopes []string + SessionDuration int //in seconds, default to 1 hour +} + +// RegisterUpstreamApp registers an upstream app with the SSO server +func (s *SSOHandler) ListRegisteredApps() []*RegisteredUpstreamApp { + apps := make([]*RegisteredUpstreamApp, 0) + for _, app := range s.Apps { + apps = append(apps, &app) + } + return apps +} + +// RegisterUpstreamApp registers an upstream app with the SSO server +func (s *SSOHandler) GetAppByID(appID string) (*RegisteredUpstreamApp, bool) { + app, ok := s.Apps[appID] + return &app, ok +} diff --git a/src/mod/auth/sso/handlers.go b/src/mod/auth/sso/handlers.go new file mode 100644 index 0000000..4e165bd --- /dev/null +++ b/src/mod/auth/sso/handlers.go @@ -0,0 +1,240 @@ +package sso + +/* + handlers.go + + This file contains the handlers for the SSO module. + If you are looking for handlers for SSO user management, + please refer to userHandlers.go. +*/ + +import ( + "encoding/json" + "net/http" + "strings" + + "github.com/gofrs/uuid" + "imuslab.com/zoraxy/mod/utils" +) + +// HandleSSOStatus handle the request to get the status of the SSO portal server +func (s *SSOHandler) HandleSSOStatus(w http.ResponseWriter, r *http.Request) { + type SSOStatus struct { + Enabled bool + SSOInterceptEnabled bool + ListeningPort int + AuthURL string + } + + status := SSOStatus{ + Enabled: s.ssoPortalServer != nil, + //SSOInterceptEnabled: s.ssoInterceptEnabled, + ListeningPort: s.Config.PortalServerPort, + AuthURL: s.Config.AuthURL, + } + + js, _ := json.Marshal(status) + utils.SendJSONResponse(w, string(js)) +} + +// HandleStartSSOPortal handle the request to start the SSO portal server +func (s *SSOHandler) HandleStartSSOPortal(w http.ResponseWriter, r *http.Request) { + err := s.StartSSOPortal() + if err != nil { + s.Log("Failed to start SSO portal server", err) + utils.SendErrorResponse(w, "failed to start SSO portal server") + return + } + //Write current state to database + err = s.Config.Database.Write("sso_conf", "enabled", true) + if err != nil { + utils.SendErrorResponse(w, "failed to update SSO state") + return + } + utils.SendOK(w) +} + +// HandleStopSSOPortal handle the request to stop the SSO portal server +func (s *SSOHandler) HandleStopSSOPortal(w http.ResponseWriter, r *http.Request) { + err := s.ssoPortalServer.Close() + if err != nil { + s.Log("Failed to stop SSO portal server", err) + utils.SendErrorResponse(w, "failed to stop SSO portal server") + return + } + s.ssoPortalServer = nil + + //Write current state to database + err = s.Config.Database.Write("sso_conf", "enabled", false) + if err != nil { + utils.SendErrorResponse(w, "failed to update SSO state") + return + } + + //Clear the cookie store and restart the server + err = s.RestartSSOServer() + if err != nil { + utils.SendErrorResponse(w, "failed to restart SSO server") + return + } + utils.SendOK(w) +} + +// HandlePortChange handle the request to change the SSO portal server port +func (s *SSOHandler) HandlePortChange(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + //Return the current port + js, _ := json.Marshal(s.Config.PortalServerPort) + utils.SendJSONResponse(w, string(js)) + return + } + + port, err := utils.PostInt(r, "port") + if err != nil { + utils.SendErrorResponse(w, "invalid port given") + return + } + + s.Config.PortalServerPort = port + + //Write to the database + err = s.Config.Database.Write("sso_conf", "port", port) + if err != nil { + utils.SendErrorResponse(w, "failed to update port") + return + } + + //Clear the cookie store and restart the server + err = s.RestartSSOServer() + if err != nil { + utils.SendErrorResponse(w, "failed to restart SSO server") + return + } + utils.SendOK(w) +} + +// HandleSetAuthURL handle the request to change the SSO auth URL +// This is the URL that the SSO portal server will redirect to for authentication +// e.g. auth.yourdomain.com +func (s *SSOHandler) HandleSetAuthURL(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet { + //Return the current auth URL + js, _ := json.Marshal(s.Config.AuthURL) + utils.SendJSONResponse(w, string(js)) + return + } + + //Get the auth URL + authURL, err := utils.PostPara(r, "auth_url") + if err != nil { + utils.SendErrorResponse(w, "invalid auth URL given") + return + } + + s.Config.AuthURL = authURL + + //Write to the database + err = s.Config.Database.Write("sso_conf", "authurl", authURL) + if err != nil { + utils.SendErrorResponse(w, "failed to update auth URL") + return + } + + //Clear the cookie store and restart the server + err = s.RestartSSOServer() + if err != nil { + utils.SendErrorResponse(w, "failed to restart SSO server") + return + } + utils.SendOK(w) +} + +// HandleRegisterApp handle the request to register a new app to the SSO portal +func (s *SSOHandler) HandleRegisterApp(w http.ResponseWriter, r *http.Request) { + appName, err := utils.PostPara(r, "app_name") + if err != nil { + utils.SendErrorResponse(w, "invalid app name given") + return + } + + id, err := utils.PostPara(r, "app_id") + if err != nil { + //If id is not given, use the app name with a random UUID + newID, err := uuid.NewV4() + if err != nil { + utils.SendErrorResponse(w, "failed to generate new app ID") + return + } + id = strings.ReplaceAll(appName, " ", "") + "-" + newID.String() + } + + //Check if the given appid is already in use + if _, ok := s.Apps[id]; ok { + utils.SendErrorResponse(w, "app ID already in use") + return + } + + /* + Process the app domain + An app can have multiple domains, separated by commas + Usually the app domain is the proxy rule that points to the app + For example, if the app is hosted at app.yourdomain.com, the app domain is app.yourdomain.com + */ + appDomain, err := utils.PostPara(r, "app_domain") + if err != nil { + utils.SendErrorResponse(w, "invalid app URL given") + return + } + + appURLs := strings.Split(appDomain, ",") + //Remove padding and trailing spaces in each URL + for i := range appURLs { + appURLs[i] = strings.TrimSpace(appURLs[i]) + } + + //Create a new app entry + thisAppEntry := RegisteredUpstreamApp{ + ID: id, + Secret: "", + Domain: appURLs, + Scopes: []string{}, + SessionDuration: 3600, + } + + js, _ := json.Marshal(thisAppEntry) + + //Create a new app in the database + err = s.Config.Database.Write("sso_apps", appName, string(js)) + if err != nil { + utils.SendErrorResponse(w, "failed to create new app") + return + } + + //Also add the app to runtime config + s.Apps[appName] = thisAppEntry + + utils.SendOK(w) +} + +// HandleAppRemove handle the request to remove an app from the SSO portal +func (s *SSOHandler) HandleAppRemove(w http.ResponseWriter, r *http.Request) { + appID, err := utils.PostPara(r, "app_id") + if err != nil { + utils.SendErrorResponse(w, "invalid app ID given") + return + } + + //Check if the app actually exists + if _, ok := s.Apps[appID]; !ok { + utils.SendErrorResponse(w, "app not found") + return + } + delete(s.Apps, appID) + + //Also remove it from the database + err = s.Config.Database.Delete("sso_apps", appID) + if err != nil { + s.Log("Failed to remove app from database", err) + } + +} diff --git a/src/mod/auth/sso/oauth2.go b/src/mod/auth/sso/oauth2.go new file mode 100644 index 0000000..d519fb6 --- /dev/null +++ b/src/mod/auth/sso/oauth2.go @@ -0,0 +1,295 @@ +package sso + +import ( + "context" + _ "embed" + "encoding/json" + "log" + "net/http" + "net/url" + "time" + + "github.com/go-oauth2/oauth2/v4/errors" + "github.com/go-oauth2/oauth2/v4/generates" + "github.com/go-oauth2/oauth2/v4/manage" + "github.com/go-oauth2/oauth2/v4/models" + "github.com/go-oauth2/oauth2/v4/server" + "github.com/go-oauth2/oauth2/v4/store" + "github.com/go-session/session" + "imuslab.com/zoraxy/mod/utils" +) + +const ( + SSO_SESSION_NAME = "ZoraxySSO" +) + +type OAuth2Server struct { + srv *server.Server //oAuth server instance + config *SSOConfig + parent *SSOHandler +} + +//go:embed static/auth.html +var authHtml []byte + +//go:embed static/login.html +var loginHtml []byte + +// NewOAuth2Server creates a new OAuth2 server instance +func NewOAuth2Server(config *SSOConfig, parent *SSOHandler) (*OAuth2Server, error) { + manager := manage.NewDefaultManager() + manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg) + // token store + manager.MustTokenStorage(store.NewFileTokenStore("./conf/sso.db")) + // generate jwt access token + manager.MapAccessGenerate(generates.NewAccessGenerate()) + + //Load the information of registered app within the OAuth2 server + clientStore := store.NewClientStore() + clientStore.Set("myapp", &models.Client{ + ID: "myapp", + Secret: "verysecurepassword", + Domain: "localhost:9094", + }) + //TODO: LOAD THIS DYNAMICALLY FROM DATABASE + manager.MapClientStorage(clientStore) + + thisServer := OAuth2Server{ + config: config, + parent: parent, + } + + //Create a new oauth server + srv := server.NewServer(server.NewConfig(), manager) + srv.SetPasswordAuthorizationHandler(thisServer.PasswordAuthorizationHandler) + srv.SetUserAuthorizationHandler(thisServer.UserAuthorizeHandler) + srv.SetInternalErrorHandler(func(err error) (re *errors.Response) { + log.Println("Internal Error:", err.Error()) + return + }) + srv.SetResponseErrorHandler(func(re *errors.Response) { + log.Println("Response Error:", re.Error.Error()) + }) + + //Set the access scope handler + srv.SetAuthorizeScopeHandler(thisServer.AuthorizationScopeHandler) + //Set the access token expiration handler based on requesting domain / hostname + srv.SetAccessTokenExpHandler(thisServer.ExpireHandler) + thisServer.srv = srv + return &thisServer, nil +} + +// Password handler, validate if the given username and password are correct +func (oas *OAuth2Server) PasswordAuthorizationHandler(ctx context.Context, clientID, username, password string) (userID string, err error) { + //TODO: LOAD THIS DYNAMICALLY FROM DATABASE + if username == "test" && password == "test" { + userID = "test" + } + return +} + +// User Authorization Handler, handle auth request from user +func (oas *OAuth2Server) UserAuthorizeHandler(w http.ResponseWriter, r *http.Request) (userID string, err error) { + store, err := session.Start(r.Context(), w, r) + if err != nil { + return + } + + uid, ok := store.Get(SSO_SESSION_NAME) + if !ok { + if r.Form == nil { + r.ParseForm() + } + + store.Set("ReturnUri", r.Form) + store.Save() + + w.Header().Set("Location", "/oauth2/login") + w.WriteHeader(http.StatusFound) + return + } + + userID = uid.(string) + store.Delete(SSO_SESSION_NAME) + store.Save() + return +} + +// AccessTokenExpHandler, set the SSO session length default value +func (oas *OAuth2Server) ExpireHandler(w http.ResponseWriter, r *http.Request) (exp time.Duration, err error) { + requestHostname := r.Host + if requestHostname == "" { + //Use default value + return time.Hour, nil + } + + //Get the Registered App Config from parent + appConfig, ok := oas.parent.Apps[requestHostname] + if !ok { + //Use default value + return time.Hour, nil + } + + //Use the app's session length + return time.Second * time.Duration(appConfig.SessionDuration), nil +} + +// AuthorizationScopeHandler, handle the scope of the request +func (oas *OAuth2Server) AuthorizationScopeHandler(w http.ResponseWriter, r *http.Request) (scope string, err error) { + //Get the scope from post or GEt request + if r.Form == nil { + if err := r.ParseForm(); err != nil { + return "none", err + } + } + + //Get the hostname of the request + requestHostname := r.Host + if requestHostname == "" { + //No rule set. Use default + return "none", nil + } + + //Get the Registered App Config from parent + appConfig, ok := oas.parent.Apps[requestHostname] + if !ok { + //No rule set. Use default + return "none", nil + } + + //Check if the scope is set in the request + if v, ok := r.Form["scope"]; ok { + //Check if the requested scope is in the appConfig scope + if utils.StringInArray(appConfig.Scopes, v[0]) { + return v[0], nil + } + return "none", nil + } + + return "none", nil +} + +/* SSO Web Server Toggle Functions */ +func (oas *OAuth2Server) RegisterOauthEndpoints(primaryMux *http.ServeMux) { + primaryMux.HandleFunc("/oauth2/login", oas.loginHandler) + primaryMux.HandleFunc("/oauth2/auth", oas.authHandler) + + primaryMux.HandleFunc("/oauth2/authorize", func(w http.ResponseWriter, r *http.Request) { + store, err := session.Start(r.Context(), w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var form url.Values + if v, ok := store.Get("ReturnUri"); ok { + form = v.(url.Values) + } + r.Form = form + + store.Delete("ReturnUri") + store.Save() + + err = oas.srv.HandleAuthorizeRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + }) + + primaryMux.HandleFunc("/oauth2/token", func(w http.ResponseWriter, r *http.Request) { + err := oas.srv.HandleTokenRequest(w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) + + primaryMux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + token, err := oas.srv.ValidationBearerToken(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + data := map[string]interface{}{ + "expires_in": int64(time.Until(token.GetAccessCreateAt().Add(token.GetAccessExpiresIn())).Seconds()), + "client_id": token.GetClientID(), + "user_id": token.GetUserID(), + } + e := json.NewEncoder(w) + e.SetIndent("", " ") + e.Encode(data) + }) +} + +func (oas *OAuth2Server) loginHandler(w http.ResponseWriter, r *http.Request) { + store, err := session.Start(r.Context(), w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if r.Method == "POST" { + if r.Form == nil { + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + //Load username and password from form post + username, err := utils.PostPara(r, "username") + if err != nil { + w.Write([]byte("invalid username or password")) + return + } + + password, err := utils.PostPara(r, "password") + if err != nil { + w.Write([]byte("invalid username or password")) + return + } + + //Validate the user + if !oas.parent.ValidateUsernameAndPassword(username, password) { + //Wrong password + w.Write([]byte("invalid username or password")) + return + } + + store.Set(SSO_SESSION_NAME, r.Form.Get("username")) + store.Save() + + w.Header().Set("Location", "/oauth2/auth") + w.WriteHeader(http.StatusFound) + return + } else if r.Method == "GET" { + //Check if the user is logged in + if _, ok := store.Get(SSO_SESSION_NAME); ok { + w.Header().Set("Location", "/oauth2/auth") + w.WriteHeader(http.StatusFound) + return + } + } + //User not logged in. Show login page + w.Write(loginHtml) +} + +func (oas *OAuth2Server) authHandler(w http.ResponseWriter, r *http.Request) { + store, err := session.Start(context.TODO(), w, r) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if _, ok := store.Get(SSO_SESSION_NAME); !ok { + w.Header().Set("Location", "/oauth2/login") + w.WriteHeader(http.StatusFound) + return + } + //User logged in. Check if this user have previously authorized the app + + //TODO: Check if the user have previously authorized the app + + //User have not authorized the app. Show the authorization page + w.Write(authHtml) +} diff --git a/src/mod/auth/sso/oauth_test.go b/src/mod/auth/sso/oauth_test.go new file mode 100644 index 0000000..2e16460 --- /dev/null +++ b/src/mod/auth/sso/oauth_test.go @@ -0,0 +1 @@ +package sso diff --git a/src/mod/auth/sso/server.go b/src/mod/auth/sso/server.go new file mode 100644 index 0000000..915bda3 --- /dev/null +++ b/src/mod/auth/sso/server.go @@ -0,0 +1,123 @@ +package sso + +import ( + "context" + "net/http" + "strconv" + "time" + + "imuslab.com/zoraxy/mod/utils" +) + +/* + server.go + + This is the web server for the SSO portal. It contains the + HTTP server and the handlers for the SSO portal. + + If you are looking for handlers that changes the settings + of the SSO portale or user management, please refer to + handlers.go. + +*/ + +func (h *SSOHandler) InitSSOPortal(portalServerPort int) { + //Create a new web server for the SSO portal + pmux := http.NewServeMux() + fs := http.FileServer(http.FS(staticFiles)) + pmux.Handle("/", fs) + + //Register API endpoint for the SSO portal + pmux.HandleFunc("/sso/login", h.HandleLogin) + + //Register OAuth2 endpoints + h.Oauth2Server.RegisterOauthEndpoints(pmux) + + h.ssoPortalMux = pmux +} + +// StartSSOPortal start the SSO portal server +// This function will block the main thread, call it in a goroutine +func (h *SSOHandler) StartSSOPortal() error { + h.ssoPortalServer = &http.Server{ + Addr: ":" + strconv.Itoa(h.Config.PortalServerPort), + Handler: h.ssoPortalMux, + } + err := h.ssoPortalServer.ListenAndServe() + if err != nil { + h.Log("Failed to start SSO portal server", err) + } + return err +} + +// StopSSOPortal stop the SSO portal server +func (h *SSOHandler) StopSSOPortal() error { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := h.ssoPortalServer.Shutdown(ctx) + if err != nil { + h.Log("Failed to stop SSO portal server", err) + return err + } + return nil +} + +// StartSSOPortal start the SSO portal server +func (h *SSOHandler) RestartSSOServer() error { + err := h.StopSSOPortal() + if err != nil { + return err + } + go h.StartSSOPortal() + return nil +} + +func (h *SSOHandler) IsRunning() bool { + return h.ssoPortalServer != nil +} + +// HandleLogin handle the login request +func (h *SSOHandler) HandleLogin(w http.ResponseWriter, r *http.Request) { + //Handle the login request + username, err := utils.PostPara(r, "username") + if err != nil { + utils.SendErrorResponse(w, "invalid username or password") + return + } + + password, err := utils.PostPara(r, "password") + if err != nil { + utils.SendErrorResponse(w, "invalid username or password") + return + } + + rememberMe, err := utils.PostBool(r, "remember_me") + if err != nil { + rememberMe = false + } + + //Check if the user exists + userEntry, err := h.GetSSOUser(username) + if err != nil { + utils.SendErrorResponse(w, "user not found") + return + } + + //Check if the password is correct + if !userEntry.VerifyPassword(password) { + utils.SendErrorResponse(w, "incorrect password") + return + } + + //Create a new session for the user + session, _ := h.cookieStore.Get(r, "Zoraxy-SSO") + session.Values["username"] = username + if rememberMe { + session.Options.MaxAge = 86400 * 15 //15 days + } else { + session.Options.MaxAge = 3600 //1 hour + } + session.Save(r, w) //Save the session + + utils.SendOK(w) +} diff --git a/src/mod/auth/sso/sso.go b/src/mod/auth/sso/sso.go new file mode 100644 index 0000000..4be5f35 --- /dev/null +++ b/src/mod/auth/sso/sso.go @@ -0,0 +1,158 @@ +package sso + +import ( + "embed" + "net/http" + + "github.com/gorilla/sessions" + "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/info/logger" +) + +/* + sso.go + + This file contains the main SSO handler and the SSO configuration + structure. It also contains the main SSO handler functions. + + SSO web interface are stored in the static folder, which is embedded + into the binary. +*/ + +//go:embed static/* +var staticFiles embed.FS //Static files for the SSO portal + +type SSOConfig struct { + SystemUUID string //System UUID, should be passed in from main scope + AuthURL string //Authentication subdomain URL, e.g. auth.example.com + PortalServerPort int //SSO portal server port + Database *database.Database //System master key-value database + Logger *logger.Logger +} + +// SSOHandler is the main SSO handler structure +type SSOHandler struct { + cookieStore *sessions.CookieStore + ssoPortalServer *http.Server + ssoPortalMux *http.ServeMux + Oauth2Server *OAuth2Server + Config *SSOConfig + Apps map[string]RegisteredUpstreamApp +} + +// Create a new Zoraxy SSO handler +func NewSSOHandler(config *SSOConfig) (*SSOHandler, error) { + //Create a cookie store for the SSO handler + cookieStore := sessions.NewCookieStore([]byte(config.SystemUUID)) + cookieStore.Options = &sessions.Options{ + Path: "", + Domain: "", + MaxAge: 0, + Secure: false, + HttpOnly: false, + SameSite: 0, + } + + config.Database.NewTable("sso_users") //For storing user information + config.Database.NewTable("sso_conf") //For storing SSO configuration + config.Database.NewTable("sso_apps") //For storing registered apps + + //Create the SSO Handler + thisHandler := SSOHandler{ + cookieStore: cookieStore, + Config: config, + } + + //Read the app info from database + thisHandler.Apps = make(map[string]RegisteredUpstreamApp) + + //Create an oauth2 server + oauth2Server, err := NewOAuth2Server(config, &thisHandler) + if err != nil { + return nil, err + } + + //Register endpoints + thisHandler.Oauth2Server = oauth2Server + thisHandler.InitSSOPortal(config.PortalServerPort) + + return &thisHandler, nil +} + +func (h *SSOHandler) RestorePreviousRunningState() { + //Load the previous SSO state + ssoEnabled := false + ssoPort := 5488 + ssoAuthURL := "" + h.Config.Database.Read("sso_conf", "enabled", &ssoEnabled) + h.Config.Database.Read("sso_conf", "port", &ssoPort) + h.Config.Database.Read("sso_conf", "authurl", &ssoAuthURL) + + if ssoAuthURL == "" { + //Cannot enable SSO without auth URL + ssoEnabled = false + } + + h.Config.PortalServerPort = ssoPort + h.Config.AuthURL = ssoAuthURL + + if ssoEnabled { + go h.StartSSOPortal() + } +} + +// ServeForwardAuth handle the SSO request in interception mode +// Suppose to be called in dynamicproxy. +// Return true if the request is allowed to pass, false if the request is blocked and shall not be further processed +func (h *SSOHandler) ServeForwardAuth(w http.ResponseWriter, r *http.Request) bool { + //Get the current uri for appending to the auth subdomain + originalRequestURL := r.RequestURI + + redirectAuthURL := h.Config.AuthURL + if redirectAuthURL == "" || !h.IsRunning() { + //Redirect not set or auth server is offlined + w.Write([]byte("SSO auth URL not set or SSO server offline.")) + //TODO: Use better looking template if exists + return false + } + + //Check if the user have the cookie "Zoraxy-SSO" set + session, err := h.cookieStore.Get(r, "Zoraxy-SSO") + if err != nil { + //Redirect to auth subdomain + http.Redirect(w, r, redirectAuthURL+"/sso/login?m=new&t="+originalRequestURL, http.StatusFound) + return false + } + + //Check if the user is logged in + if session.Values["username"] != true { + //Redirect to auth subdomain + http.Redirect(w, r, redirectAuthURL+"/sso/login?m=expired&t="+originalRequestURL, http.StatusFound) + return false + } + + //Check if the current request subdomain is allowed + userName := session.Values["username"].(string) + user, err := h.GetSSOUser(userName) + if err != nil { + //User might have been removed from SSO. Redirect to auth subdomain + http.Redirect(w, r, redirectAuthURL, http.StatusFound) + return false + } + + //Check if the user have access to the current subdomain + if !user.Subdomains[r.Host].AllowAccess { + //User is not allowed to access the current subdomain. Sent 403 + http.Error(w, "Forbidden", http.StatusForbidden) + //TODO: Use better looking template if exists + return false + } + + //User is logged in, continue to the next handler + return true +} + +// Log a message with the SSO module tag +func (h *SSOHandler) Log(message string, err error) { + h.Config.Logger.PrintAndLog("SSO", message, err) +} diff --git a/src/mod/auth/sso/static/auth.html b/src/mod/auth/sso/static/auth.html new file mode 100644 index 0000000..9c74b65 --- /dev/null +++ b/src/mod/auth/sso/static/auth.html @@ -0,0 +1,33 @@ + + +
+ +Create and manage accounts with Zoraxy!
+A centralized authentication system for all your subdomains
+ +127.0.0.1:5488
+