diff --git a/app/utils/http.ts b/app/utils/http.ts index 2ca64362..eff4ee22 100644 --- a/app/utils/http.ts +++ b/app/utils/http.ts @@ -6,13 +6,17 @@ import intl from 'react-intl-universal'; import { getRootStore } from '@app/stores'; import { trackEvent } from './stat'; -const subErrMsgStr = [ - 'session expired', - 'connection refused', - 'broken pipe', - 'an existing connection was forcibly closed', - 'Token is expired', -]; +export enum HttpResCode { + ErrBadRequest = 40004000, + ErrParam = 40004001, + ErrUnauthorized = 40104000, + ErrSession = 40104001, + ErrForbidden = 40304000, + ErrNotFound = 40404000, + ErrInternalServer = 50004000, + ErrNotImplemented = 50104000, + ErrUnknown = 90004000, +} const service = axios.create({ transformResponse: [ @@ -39,24 +43,20 @@ service.interceptors.request.use(config => { service.interceptors.response.use( (response: any) => { - const { code, message: errMsg } = response.data; - const isConnectReq = /api-nebula\/db\/connect$/.test(response.config?.url); - // if connection refused, login again - if (code === -1 && new RegExp(subErrMsgStr.join('|')).test(errMsg)) { - message.warning(errMsg); - !isConnectReq && getRootStore().global.logout(); - } else if (code === -1 && errMsg) { - message.warning(errMsg); - } - return response.data; + const isExecReq = /api-nebula\/db\/(exec|batchExec)$/.test(response.config?.url); + return isExecReq ? response.data?.data : response.data; }, (error: any) => { - if (error.response && error.response.status) { + if (error.response?.status) { message.error( `${intl.get('common.requestError')}: ${error.response.status} ${ error.response.statusText }`, ); + // relogin + if (error.response.data?.code === HttpResCode.ErrSession) { + getRootStore().global.logout(); + } return error.response; } else { message.error(`${intl.get('common.requestError')}: ${error}`); diff --git a/config/webpack.prod.js b/config/webpack.prod.js index e0f19da4..2fe7b62f 100644 --- a/config/webpack.prod.js +++ b/config/webpack.prod.js @@ -13,7 +13,7 @@ const publicConfig = { path: path.join(__dirname, '../dist/'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[contenthash].js', - publicPath: '/assets/', + publicPath: '/', }, module: { rules: [ diff --git a/server-v2/api/studio/etc/studio-api.yaml b/server-v2/api/studio/etc/studio-api.yaml index 002e4cd9..0d12cbb3 100644 --- a/server-v2/api/studio/etc/studio-api.yaml +++ b/server-v2/api/studio/etc/studio-api.yaml @@ -1,11 +1,11 @@ Name: studio-api Host: 0.0.0.0 -Port: 7002 +Port: 9000 MaxBytes: 1073741824 Debug: Enable: false Auth: AccessSecret: "login_secret" - AccessExpire: 7200 + AccessExpire: 1800 File: UploadDir: "./upload/" \ No newline at end of file diff --git a/server-v2/api/studio/internal/handler/gateway/batchexecngqlhandler.go b/server-v2/api/studio/internal/handler/gateway/batchexecngqlhandler.go new file mode 100644 index 00000000..56338b7f --- /dev/null +++ b/server-v2/api/studio/internal/handler/gateway/batchexecngqlhandler.go @@ -0,0 +1,33 @@ +// Code generated by goctl. DO NOT EDIT. +package gateway + +import ( + "net/http" + + "github.com/vesoft-inc/go-pkg/validator" + "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/ecode" + + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/logic/gateway" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func BatchExecNGQLHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.BatchExecNGQLParams + if err := httpx.Parse(r, &req); err != nil { + err = ecode.WithCode(ecode.ErrParam, err) + svcCtx.ResponseHandler.Handle(w, r, nil, err) + return + } + if err := validator.Struct(req); err != nil { + svcCtx.ResponseHandler.Handle(w, r, nil, err) + return + } + + l := gateway.NewBatchExecNGQLLogic(r.Context(), svcCtx) + data, err := l.BatchExecNGQL(req) + svcCtx.ResponseHandler.Handle(w, r, data, err) + } +} diff --git a/server-v2/api/studio/internal/handler/gateway/disonnecthandler.go b/server-v2/api/studio/internal/handler/gateway/disonnecthandler.go new file mode 100644 index 00000000..ef7418dd --- /dev/null +++ b/server-v2/api/studio/internal/handler/gateway/disonnecthandler.go @@ -0,0 +1,33 @@ +// Code generated by goctl. DO NOT EDIT. +package gateway + +import ( + "net/http" + + "github.com/vesoft-inc/go-pkg/validator" + "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/ecode" + + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/logic/gateway" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" + "github.com/zeromicro/go-zero/rest/httpx" +) + +func DisonnectHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req types.DisconnectDBParams + if err := httpx.Parse(r, &req); err != nil { + err = ecode.WithCode(ecode.ErrParam, err) + svcCtx.ResponseHandler.Handle(w, r, nil, err) + return + } + if err := validator.Struct(req); err != nil { + svcCtx.ResponseHandler.Handle(w, r, nil, err) + return + } + + l := gateway.NewDisonnectLogic(r.Context(), svcCtx) + data, err := l.Disonnect(req) + svcCtx.ResponseHandler.Handle(w, r, data, err) + } +} diff --git a/server-v2/api/studio/internal/handler/routes.go b/server-v2/api/studio/internal/handler/routes.go index f47653c5..c6b5e76d 100644 --- a/server-v2/api/studio/internal/handler/routes.go +++ b/server-v2/api/studio/internal/handler/routes.go @@ -30,6 +30,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/exec", Handler: gateway.ExecNGQLHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/batchExec", + Handler: gateway.BatchExecNGQLHandler(serverCtx), + }, }, rest.WithPrefix("/api-nebula/db"), ) @@ -41,6 +46,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/connect", Handler: gateway.ConnectHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/disconnect", + Handler: gateway.DisonnectHandler(serverCtx), + }, }, rest.WithPrefix("/api-nebula/db"), ) diff --git a/server-v2/api/studio/internal/logic/gateway/batchexecngqllogic.go b/server-v2/api/studio/internal/logic/gateway/batchexecngqllogic.go new file mode 100644 index 00000000..9d9d52f0 --- /dev/null +++ b/server-v2/api/studio/internal/logic/gateway/batchexecngqllogic.go @@ -0,0 +1,29 @@ +package gateway + +import ( + "context" + + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/service" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type BatchExecNGQLLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewBatchExecNGQLLogic(ctx context.Context, svcCtx *svc.ServiceContext) BatchExecNGQLLogic { + return BatchExecNGQLLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *BatchExecNGQLLogic) BatchExecNGQL(req types.BatchExecNGQLParams) (*types.AnyResponse, error) { + return service.NewGatewayService(l.ctx, l.svcCtx).BatchExecNGQL(&req) +} diff --git a/server-v2/api/studio/internal/logic/gateway/disonnectlogic.go b/server-v2/api/studio/internal/logic/gateway/disonnectlogic.go new file mode 100644 index 00000000..a49cd1aa --- /dev/null +++ b/server-v2/api/studio/internal/logic/gateway/disonnectlogic.go @@ -0,0 +1,29 @@ +package gateway + +import ( + "context" + + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/service" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type DisonnectLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewDisonnectLogic(ctx context.Context, svcCtx *svc.ServiceContext) DisonnectLogic { + return DisonnectLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *DisonnectLogic) Disonnect(req types.DisconnectDBParams) (*types.AnyResponse, error) { + return service.NewGatewayService(l.ctx, l.svcCtx).DisconnectDB(&req) +} diff --git a/server-v2/api/studio/internal/logic/gateway/execngqllogic.go b/server-v2/api/studio/internal/logic/gateway/execngqllogic.go index bc5244ee..f16f31b4 100644 --- a/server-v2/api/studio/internal/logic/gateway/execngqllogic.go +++ b/server-v2/api/studio/internal/logic/gateway/execngqllogic.go @@ -25,5 +25,5 @@ func NewExecNGQLLogic(ctx context.Context, svcCtx *svc.ServiceContext) ExecNGQLL } func (l *ExecNGQLLogic) ExecNGQL(req types.ExecNGQLParams) (resp *types.AnyResponse, err error) { - return service.NewGatewayService(l.ctx, l.svcCtx).GetExec(&req) + return service.NewGatewayService(l.ctx, l.svcCtx).ExecNGQL(&req) } diff --git a/server-v2/api/studio/internal/service/gateway.go b/server-v2/api/studio/internal/service/gateway.go index 87ee65cb..fdbc6218 100644 --- a/server-v2/api/studio/internal/service/gateway.go +++ b/server-v2/api/studio/internal/service/gateway.go @@ -2,14 +2,12 @@ package service import ( "context" - "encoding/base64" - "fmt" "strings" "github.com/vesoft-inc/nebula-http-gateway/ccore/nebula/gateway/dao" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" - "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/auth" + "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/base" "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/ecode" "github.com/zeromicro/go-zero/core/logx" @@ -19,8 +17,10 @@ var _ GatewayService = (*gatewayService)(nil) type ( GatewayService interface { - GetExec(request *types.ExecNGQLParams) (*types.AnyResponse, error) + ExecNGQL(request *types.ExecNGQLParams) (*types.AnyResponse, error) + BatchExecNGQL(request *types.BatchExecNGQLParams) (*types.AnyResponse, error) ConnectDB(request *types.ConnectDBParams) (*types.ConnectDBResult, error) + DisconnectDB(request *types.DisconnectDBParams) (*types.AnyResponse, error) } gatewayService struct { @@ -38,50 +38,57 @@ func NewGatewayService(ctx context.Context, svcCtx *svc.ServiceContext) GatewayS } } -func (s *gatewayService) GetExec(request *types.ExecNGQLParams) (*types.AnyResponse, error) { - return &types.AnyResponse{ - Data: map[string]any{ - "message": "ok", - }, +func (s *gatewayService) ConnectDB(request *types.ConnectDBParams) (*types.ConnectDBResult, error) { + return &types.ConnectDBResult{ + Version: string(request.NebulaVersion), }, nil } -func (s *gatewayService) ConnectDB(request *types.ConnectDBParams) (*types.ConnectDBResult, error) { - fmt.Println("======request999", request) - tokenSplit := strings.Split(request.Authorization, " ") - if len(tokenSplit) != 2 { - return nil, ecode.WithCode(ecode.ErrParam, nil, "invalid authorization") +func (s *gatewayService) DisconnectDB(request *types.DisconnectDBParams) (*types.AnyResponse, error) { + if request.NSID != "" { + dao.Disconnect(request.NSID) } + return nil, nil +} - decode, err := base64.StdEncoding.DecodeString(tokenSplit[1]) +func (s *gatewayService) ExecNGQL(request *types.ExecNGQLParams) (*types.AnyResponse, error) { + execute, _, err := dao.Execute(request.NSID, request.Gql, request.ParamList) if err != nil { - return nil, ecode.WithCode(ecode.ErrParam, err) + return nil, ecode.WithCode(ecode.ErrInternalServer, err, "exec failed") } + return &types.AnyResponse{Data: execute}, nil +} - loginInfo := strings.Split(string(decode), ":") - if len(loginInfo) < 2 { - return nil, ecode.WithCode(ecode.ErrParam, nil, "len of account is less than two") +func (s *gatewayService) BatchExecNGQL(request *types.BatchExecNGQLParams) (*types.AnyResponse, error) { + data := make([]map[string]interface{}, 0) + + NSID := request.NSID + gqls := request.Gqls + paramList := request.ParamList + + for _, gql := range gqls { + execute, _, err := dao.Execute(NSID, gql, make([]string, 0)) + gqlRes := map[string]interface{}{"gql": gql, "data": execute} + if err != nil { + gqlRes["message"] = err.Error() + gqlRes["code"] = base.Error + } else { + gqlRes["code"] = base.Success + } + data = append(data, gqlRes) } - username, password := loginInfo[0], loginInfo[1] - clientInfo, err := dao.Connect(request.Address, request.Port, username, password) - - tokenString, err := auth.CreateToken( - &auth.AuthData{ - NebulaAddress: request.Address, - Username: username, - ClientID: clientInfo.ClientID, - }, - &s.svcCtx.Config, - ) - - fmt.Println("=====tokenString", tokenString) - - if err != nil { - return nil, ecode.WithInternalServer(err, "connect db failed") + if len(paramList) > 0 { + execute, _, err := dao.Execute(NSID, "", paramList) + gqlRes := map[string]interface{}{"gql": strings.Join(paramList, "; "), "data": execute} + if err != nil { + gqlRes["message"] = err.Error() + gqlRes["code"] = base.Error + } else { + gqlRes["code"] = base.Success + } + data = append(data, gqlRes) } - return &types.ConnectDBResult{ - Version: string(clientInfo.NebulaVersion), - }, nil + return &types.AnyResponse{Data: data}, nil } diff --git a/server-v2/api/studio/internal/types/types.go b/server-v2/api/studio/internal/types/types.go index 373f0ca5..ee697dba 100644 --- a/server-v2/api/studio/internal/types/types.go +++ b/server-v2/api/studio/internal/types/types.go @@ -7,12 +7,20 @@ type GetHealth struct { type ExecNGQLParams struct { Gql string `json:"gql"` - ParamList []string `json:"paramList"` + ParamList []string `json:"paramList,optional"` + NSID string `form:"NSID"` +} + +type BatchExecNGQLParams struct { + Gqls []string `json:"gqls"` + ParamList []string `json:"paramList,optional"` + NSID string `form:"NSID"` } type ConnectDBParams struct { Address string `json:"address"` Port int `json:"port"` + NebulaVersion string `form:"nebulaVersion,optional"` Authorization string `header:"Authorization"` } @@ -20,6 +28,10 @@ type ConnectDBResult struct { Version string `json:"version"` } +type DisconnectDBParams struct { + NSID string `form:"NSID,optional"` +} + type AnyResponse struct { Data interface{} `json:"data"` } diff --git a/server-v2/api/studio/pkg/auth/authorize.go b/server-v2/api/studio/pkg/auth/authorize.go index b75c27f9..6cc6bced 100644 --- a/server-v2/api/studio/pkg/auth/authorize.go +++ b/server-v2/api/studio/pkg/auth/authorize.go @@ -2,6 +2,7 @@ package auth import ( "encoding/base64" + "errors" "fmt" "net/http" "strings" @@ -9,8 +10,12 @@ import ( "github.com/golang-jwt/jwt/v4" "github.com/vesoft-inc/nebula-http-gateway/ccore/nebula/gateway/dao" + "github.com/vesoft-inc/nebula-http-gateway/ccore/nebula/gateway/pool" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/config" + "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/types" + "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/ecode" + "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/utils" "github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/rest/httpx" ) @@ -19,18 +24,21 @@ type ( CtxKeyUserInfo struct{} AuthData struct { - NebulaAddress string `json:"nebulaAddress"` - Username string `json:"username"` - ClientID string `json:"clientID"` + Address string `json:"address"` + Port int `json:"port"` + Username string `json:"username"` } authClaims struct { *AuthData - jwt.StandardClaims + jwt.RegisteredClaims } ) -var globalConfig = new(config.Config) +var ( + tokenName = "explorer_token" + nsidName = "explorer_nsid" +) func CreateToken(authData *AuthData, config *config.Config) (string, error) { now := time.Now() @@ -39,117 +47,158 @@ func CreateToken(authData *AuthData, config *config.Config) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, authClaims{ AuthData: authData, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: expiresAt, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: &jwt.NumericDate{Time: time.Unix(expiresAt, 0)}, + // ExpiresAt: expiresAt, }, }) return token.SignedString([]byte(config.Auth.AccessSecret)) } -func AuthMiddlewareWithConfig(config *config.Config) rest.Middleware { - return func(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // login handler - if strings.HasSuffix(r.URL.Path, "/connect") { - fmt.Println("=====global middleware", r.URL.Path) - var req types.ConnectDBParams - err := httpx.Parse(r, &req) - if err != nil { - fmt.Println("=====req3333", req) - } - fmt.Println("=====err", err) - fmt.Println("=====req.Address", req.Address) - fmt.Println("=====req.Port", req.Port) - fmt.Println("=====req.Authorization", req.Authorization) - } - c1 := http.Cookie{ - Name: "access_token", - Value: "12333", - Path: "/", - HttpOnly: true, - MaxAge: 3600, - } - - var req1 types.ConnectDBParams - err1 := httpx.Parse(r, &req1) - fmt.Println("=====err1=====", err1) +func Decode(tokenString, secret string) (*AuthData, error) { + auth := authClaims{} + token, err := jwt.ParseWithClaims(tokenString, &auth, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret), nil + }) - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Set-Cookie", c1.String()) - next(w, r) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, errors.New("that's not even a token") + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { + return nil, errors.New("token is expired") + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { + return nil, errors.New("token not active yet") + } else { + return nil, errors.New("couldn't handle this token") + } } } -} -func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // login handler - if strings.HasSuffix(r.URL.Path, "/connect") { - fmt.Println("=====global middleware", r.URL.Path) - } - c1 := http.Cookie{ - Name: "access_token", - Value: "12333", - Path: "/", - HttpOnly: true, - MaxAge: 3600, - } - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Set-Cookie", c1.String()) - next(w, r) + if _, ok := token.Claims.(*authClaims); !ok || !token.Valid { + return nil, errors.New("couldn't handle this token") } + + return auth.AuthData, nil } -func GenerateLoginToken(address string, port int, authorization string, config *config.Config) (string, error) { - tokenSplit := strings.Split(authorization, " ") +func parseConnectDBParams(params *types.ConnectDBParams, config *config.Config) (string, *pool.ClientInfo, error) { + tokenSplit := strings.Split(params.Authorization, " ") if len(tokenSplit) != 2 { - return "", fmt.Errorf("invalid authorization") + return "", nil, ecode.WithCode(ecode.ErrParam, nil, "invalid authorization") } decode, err := base64.StdEncoding.DecodeString(tokenSplit[1]) if err != nil { - return "", err + return "", nil, ecode.WithCode(ecode.ErrParam, err) } loginInfo := strings.Split(string(decode), ":") if len(loginInfo) < 2 { - return "", fmt.Errorf("len of account is less than two") + return "", nil, ecode.WithCode(ecode.ErrParam, nil, "len of account is less than two") } username, password := loginInfo[0], loginInfo[1] - clientInfo, err := dao.Connect(address, port, username, password) - + clientInfo, err := dao.Connect(params.Address, params.Port, username, password) if err != nil { - return "", err + return "", nil, ecode.WithCode(ecode.ErrInternalServer, err) } - return CreateToken( + tokenString, err := CreateToken( &AuthData{ - NebulaAddress: address, - Username: username, - ClientID: clientInfo.ClientID, + Address: params.Address, + Port: params.Port, + Username: username, }, config, ) + return tokenString, clientInfo, err } -func (d *AuthData) Decode(tokenString, secret string) error { - token, err := jwt.ParseWithClaims(tokenString, &authClaims{ - AuthData: d, - }, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(secret), nil - }) - if err != nil { - return err - } +func AuthMiddlewareWithCtx(svcCtx *svc.ServiceContext) rest.Middleware { + return func(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + configAuth := svcCtx.Config.Auth - if _, ok := token.Claims.(*authClaims); !ok || !token.Valid { - return fmt.Errorf("jwt parse not valid") - } + // login handler + if strings.HasSuffix(r.URL.Path, "/connect") { + var req types.ConnectDBParams + rClone := utils.CopyHttpRequest(r) + err := httpx.Parse(rClone, &req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + tokenString, clientInfo, err := parseConnectDBParams(&req, &svcCtx.Config) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + token := http.Cookie{ + Name: tokenName, + Value: tokenString, + Path: "/", + HttpOnly: true, + MaxAge: int(configAuth.AccessExpire), + } + NSID := http.Cookie{ + Name: nsidName, + Value: clientInfo.ClientID, + Path: "/", + HttpOnly: true, + MaxAge: int(configAuth.AccessExpire), + } + + utils.AddQueryParams(r, map[string]string{"nebulaVersion": string(clientInfo.NebulaVersion)}) - return nil + w.Header().Set("Set-Cookie", token.String()) + w.Header().Add("Set-Cookie", NSID.String()) + + next(w, r) + return + } + + NSIDCookie, NSIDErr := r.Cookie(nsidName) + if NSIDErr == nil { + // Add NSID to request query + utils.AddQueryParams(r, map[string]string{"NSID": NSIDCookie.Value}) + } + + if strings.HasSuffix(r.URL.Path, "/disconnect") { + w.Header().Set("Set-Cookie", utils.DisabledCookie(tokenName).String()) + w.Header().Add("Set-Cookie", utils.DisabledCookie(nsidName).String()) + next(w, r) + return + } + + tokenCookie, tokenErr := r.Cookie(tokenName) + if NSIDErr != nil || tokenErr != nil { + if NSIDErr != nil { + svcCtx.ResponseHandler.Handle(w, r, nil, ecode.WithSessionMessage(NSIDErr)) + return + } + } + + auth, authErr := Decode(tokenCookie.Value, configAuth.AccessSecret) + if authErr != nil { + svcCtx.ResponseHandler.Handle(w, r, nil, ecode.WithSessionMessage(authErr)) + return + } + + // Add address|port|username to request query + utils.AddQueryParams(r, map[string]string{ + "address": auth.Address, + "port": fmt.Sprintf("%d", auth.Port), + "username": auth.Username, + }) + + next(w, r) + } + } } diff --git a/server-v2/api/studio/pkg/base/types.go b/server-v2/api/studio/pkg/base/types.go index a1319637..de47fbb6 100644 --- a/server-v2/api/studio/pkg/base/types.go +++ b/server-v2/api/studio/pkg/base/types.go @@ -2,11 +2,9 @@ package base type StatusCode int -type Result any +type Result interface{} const ( Error StatusCode = -1 Success StatusCode = 0 - // TODO: need to del it - AuthorizationError StatusCode = 401 ) diff --git a/server-v2/api/studio/pkg/ecode/codes.go b/server-v2/api/studio/pkg/ecode/codes.go index 7b8770e4..37feb4ca 100644 --- a/server-v2/api/studio/pkg/ecode/codes.go +++ b/server-v2/api/studio/pkg/ecode/codes.go @@ -1,6 +1,7 @@ package ecode import ( + "fmt" "net/http" ) @@ -46,6 +47,11 @@ func WithUnauthorized(err error, formatWithArgs ...interface{}) error { return WithCode(ErrUnauthorized, err, formatWithArgs...) } +func WithSessionMessage(err error, formatWithArgs ...interface{}) error { + ErrSessionWithMessage := newErrCode(CCUnauthorized, PlatformCode, 1, fmt.Sprintf("ErrSession::%s", err.Error())) + return WithCode(ErrSessionWithMessage, err, formatWithArgs...) +} + func WithForbidden(err error, formatWithArgs ...interface{}) error { return WithCode(ErrForbidden, err, formatWithArgs...) } diff --git a/server-v2/api/studio/pkg/utils/http.go b/server-v2/api/studio/pkg/utils/http.go index f81d283f..0ad3b8cb 100644 --- a/server-v2/api/studio/pkg/utils/http.go +++ b/server-v2/api/studio/pkg/utils/http.go @@ -1,128 +1,56 @@ package utils +import ( + "bytes" + "io" + "io/ioutil" + "net/http" +) + const MIMEOctetStream = "application/octet-stream" -// GetMIME returns the content-type of a file extension -func GetMIME(extension string) (mime string) { - if len(extension) == 0 { - return mime +func CopyHttpRequest(r *http.Request) *http.Request { + reqCopy := new(http.Request) + + if r == nil { + return reqCopy } - if extension[0] == '.' { - mime = mimeExtensions[extension[1:]] - } else { - mime = mimeExtensions[extension] + + *reqCopy = *r + + if r.Body != nil { + defer r.Body.Close() + + // Buffer body data + var bodyBuffer bytes.Buffer + newBodyBuffer := new(bytes.Buffer) + + io.Copy(&bodyBuffer, r.Body) + *newBodyBuffer = bodyBuffer + + // Create new ReadClosers so we can split output + r.Body = ioutil.NopCloser(&bodyBuffer) + reqCopy.Body = ioutil.NopCloser(newBodyBuffer) } - if len(mime) == 0 { - return MIMEOctetStream + + return reqCopy +} + +func DisabledCookie(name string) *http.Cookie { + return &http.Cookie{ + Name: name, + Value: "", + Path: "/", + HttpOnly: true, + MaxAge: -1, } - return mime } -// MIME types were copied from https://github.com/nginx/nginx/blob/master/conf/mime.types -var mimeExtensions = map[string]string{ - "html": "text/html", - "htm": "text/html", - "shtml": "text/html", - "css": "text/css", - "gif": "image/gif", - "jpeg": "image/jpeg", - "jpg": "image/jpeg", - "xml": "application/xml", - "js": "application/javascript", - "atom": "application/atom+xml", - "rss": "application/rss+xml", - "mml": "text/mathml", - "txt": "text/plain", - "jad": "text/vnd.sun.j2me.app-descriptor", - "wml": "text/vnd.wap.wml", - "htc": "text/x-component", - "png": "image/png", - "svg": "image/svg+xml", - "svgz": "image/svg+xml", - "tif": "image/tiff", - "tiff": "image/tiff", - "wbmp": "image/vnd.wap.wbmp", - "webp": "image/webp", - "ico": "image/x-icon", - "jng": "image/x-jng", - "bmp": "image/x-ms-bmp", - "woff": "font/woff", - "woff2": "font/woff2", - "jar": "application/java-archive", - "war": "application/java-archive", - "ear": "application/java-archive", - "json": "application/json", - "hqx": "application/mac-binhex40", - "doc": "application/msword", - "pdf": "application/pdf", - "ps": "application/postscript", - "eps": "application/postscript", - "ai": "application/postscript", - "rtf": "application/rtf", - "m3u8": "application/vnd.apple.mpegurl", - "kml": "application/vnd.google-earth.kml+xml", - "kmz": "application/vnd.google-earth.kmz", - "xls": "application/vnd.ms-excel", - "eot": "application/vnd.ms-fontobject", - "ppt": "application/vnd.ms-powerpoint", - "odg": "application/vnd.oasis.opendocument.graphics", - "odp": "application/vnd.oasis.opendocument.presentation", - "ods": "application/vnd.oasis.opendocument.spreadsheet", - "odt": "application/vnd.oasis.opendocument.text", - "wmlc": "application/vnd.wap.wmlc", - "7z": "application/x-7z-compressed", - "cco": "application/x-cocoa", - "jardiff": "application/x-java-archive-diff", - "jnlp": "application/x-java-jnlp-file", - "run": "application/x-makeself", - "pl": "application/x-perl", - "pm": "application/x-perl", - "prc": "application/x-pilot", - "pdb": "application/x-pilot", - "rar": "application/x-rar-compressed", - "rpm": "application/x-redhat-package-manager", - "sea": "application/x-sea", - "swf": "application/x-shockwave-flash", - "sit": "application/x-stuffit", - "tcl": "application/x-tcl", - "tk": "application/x-tcl", - "der": "application/x-x509-ca-cert", - "pem": "application/x-x509-ca-cert", - "crt": "application/x-x509-ca-cert", - "xpi": "application/x-xpinstall", - "xhtml": "application/xhtml+xml", - "xspf": "application/xspf+xml", - "zip": "application/zip", - "bin": "application/octet-stream", - "exe": "application/octet-stream", - "dll": "application/octet-stream", - "deb": "application/octet-stream", - "dmg": "application/octet-stream", - "iso": "application/octet-stream", - "img": "application/octet-stream", - "msi": "application/octet-stream", - "msp": "application/octet-stream", - "msm": "application/octet-stream", - "mid": "audio/midi", - "midi": "audio/midi", - "kar": "audio/midi", - "mp3": "audio/mpeg", - "ogg": "audio/ogg", - "m4a": "audio/x-m4a", - "ra": "audio/x-realaudio", - "3gpp": "video/3gpp", - "3gp": "video/3gpp", - "ts": "video/mp2t", - "mp4": "video/mp4", - "mpeg": "video/mpeg", - "mpg": "video/mpeg", - "mov": "video/quicktime", - "webm": "video/webm", - "flv": "video/x-flv", - "m4v": "video/x-m4v", - "mng": "video/x-mng", - "asx": "video/x-ms-asf", - "asf": "video/x-ms-asf", - "wmv": "video/x-ms-wmv", - "avi": "video/x-msvideo", +// dynamicly add query params to the request +func AddQueryParams(r *http.Request, params map[string]string) { + query := r.URL.Query() + for k, v := range params { + query.Set(k, v) + } + r.URL, _ = r.URL.Parse(r.URL.Path + "?" + query.Encode()) } diff --git a/server-v2/api/studio/pkg/utils/route.go b/server-v2/api/studio/pkg/utils/route.go deleted file mode 100644 index 13ad7ef6..00000000 --- a/server-v2/api/studio/pkg/utils/route.go +++ /dev/null @@ -1,58 +0,0 @@ -package utils - -import ( - "embed" - "net/http" - "path" - "path/filepath" - "strconv" - "strings" - - "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" - "github.com/zeromicro/go-zero/core/logx" - "github.com/zeromicro/go-zero/rest" -) - -var embedAssetsFolder = "assets" - -func RegisterHandlers(engine *rest.Server, serverCtx *svc.ServiceContext, fs embed.FS) { - dirlevel := []string{"/:1", ":2", ":3", ":4", ":5", ":6", ":7", ":8"} - for i := 1; i < len(dirlevel); i++ { - pathname := strings.Join(dirlevel[:i], "/") - //最后生成 /asset - engine.AddRoute( - rest.Route{ - Method: http.MethodGet, - Path: pathname, - Handler: filehandler(fs), - }) - logx.Infof("register route %s", pathname) - } -} - -func filehandler(fs embed.FS) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - fileType := filepath.Ext(req.URL.Path) - // subpath default: index.html - if len(fileType) == 0 { - htmlFileData, _ := fs.ReadFile(path.Join(embedAssetsFolder, "index.html")) - w.Write(htmlFileData) - return - } - - // assets file: a/b/c.(js|css|png) - data, err := fs.ReadFile(path.Join(embedAssetsFolder, req.URL.Path)) - if err != nil { - logx.Errorf("open resource error %s", err.Error()) - http.NotFound(w, req) - return - } - - // set assets response header - w.Header().Set("Content-Type", GetMIME(fileType)+"; charset=utf-8") - w.Header().Set("Content-Length", strconv.Itoa(len(data))) - w.Header().Set("Cache-Control", "public, max-age=7200") - - w.Write(data) - } -} diff --git a/server-v2/api/studio/restapi/gateway.api b/server-v2/api/studio/restapi/gateway.api index 006dab1d..baee6de6 100644 --- a/server-v2/api/studio/restapi/gateway.api +++ b/server-v2/api/studio/restapi/gateway.api @@ -3,17 +3,26 @@ syntax = "v1" type ( ExecNGQLParams { Gql string `json:"gql"` - ParamList []string `json:"paramList"` + ParamList []string `json:"paramList,optional"` + NSID string `form:"NSID"` + } + BatchExecNGQLParams { + Gqls []string `json:"gqls"` + ParamList []string `json:"paramList,optional"` + NSID string `form:"NSID"` } ConnectDBParams { Address string `json:"address"` Port int `json:"port"` + NebulaVersion string `form:"nebulaVersion,optional"` Authorization string `header:"Authorization"` } ConnectDBResult { Version string `json:"version"` } - + DisconnectDBParams { + NSID string `form:"NSID,optional"` + } AnyResponse { Data interface{} `json:"data"` } @@ -28,6 +37,10 @@ service studio-api { @doc "Exec NGQL" @handler ExecNGQL post /exec(ExecNGQLParams) returns (AnyResponse) + + @doc "BatchExec NGQL" + @handler BatchExecNGQL + post /batchExec(BatchExecNGQLParams) returns (AnyResponse) } @server( @@ -39,4 +52,8 @@ service studio-api { @doc "Connect DB" @handler Connect post /connect(ConnectDBParams) returns (ConnectDBResult) + + @doc "Disonnect DB" + @handler Disonnect + post /disconnect(DisconnectDBParams) returns (AnyResponse) } \ No newline at end of file diff --git a/server-v2/api/studio/studio.go b/server-v2/api/studio/studio.go index 101e5787..55a1258e 100644 --- a/server-v2/api/studio/studio.go +++ b/server-v2/api/studio/studio.go @@ -4,12 +4,13 @@ import ( "embed" "flag" "fmt" + "net/http" + "github.com/vesoft-inc/go-pkg/middleware" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/config" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/handler" "github.com/vesoft-inc/nebula-studio/server/api/studio/internal/svc" "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/auth" - "github.com/vesoft-inc/nebula-studio/server/api/studio/pkg/utils" "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/rest" @@ -27,15 +28,16 @@ func main() { conf.MustLoad(*configFile, &c, conf.UseEnv()) svcCtx := svc.NewServiceContext(c) - server := rest.MustNewServer(c.RestConf) + server := rest.MustNewServer(c.RestConf, rest.WithNotFoundHandler(middleware.NewAssetsHandler(middleware.AssetsConfig{ + Root: "assets", + Filesystem: http.FS(embedAssets), + SPA: true, + }))) + defer server.Stop() // global middleware - // server.Use(auth.AuthMiddleware) - server.Use(auth.AuthMiddlewareWithConfig(&c)) - - // static assets handlers - utils.RegisterHandlers(server, svcCtx, embedAssets) + server.Use(auth.AuthMiddlewareWithCtx(svcCtx)) // api handlers handler.RegisterHandlers(server, svcCtx) diff --git a/server-v2/go.mod b/server-v2/go.mod index cf55c8de..1bafbe9b 100644 --- a/server-v2/go.mod +++ b/server-v2/go.mod @@ -1,9 +1,9 @@ module github.com/vesoft-inc/nebula-studio/server -go 1.18 +go 1.17 require ( - github.com/vesoft-inc/go-pkg v0.0.0-20220415101753-97328ddf3e40 + github.com/vesoft-inc/go-pkg v0.0.0-20220511092334-a180a9379d8d github.com/zeromicro/go-zero v1.3.3 ) @@ -16,6 +16,7 @@ require ( ) require ( + github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/go-logr/logr v1.2.2 // indirect @@ -36,6 +37,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.30.0 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/spaolacci/murmur3 v1.1.0 // indirect go.opentelemetry.io/otel v1.3.0 // indirect go.opentelemetry.io/otel/exporters/jaeger v1.3.0 // indirect diff --git a/server-v2/go.sum b/server-v2/go.sum index 94fe5128..f6c28bf6 100644 --- a/server-v2/go.sum +++ b/server-v2/go.sum @@ -58,6 +58,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGn github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ= +github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -330,6 +332,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -360,6 +363,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM= github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -383,10 +388,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/vesoft-inc/go-pkg v0.0.0-20220415101753-97328ddf3e40 h1:vBoIYZyRgxHFDaCc/4NfkH/rVjYNnDYqg8cMczWb7vU= -github.com/vesoft-inc/go-pkg v0.0.0-20220415101753-97328ddf3e40/go.mod h1:HCAXRhF2io+nPLQnl+RQ6XyVcp1Xdv6NgslXRBBCiEU= +github.com/vesoft-inc/go-pkg v0.0.0-20220511092334-a180a9379d8d h1:Q/eVc0H8CuQNmviD43vAxtJKyB3aFd9R8Z7pOdxokoA= +github.com/vesoft-inc/go-pkg v0.0.0-20220511092334-a180a9379d8d/go.mod h1:HCAXRhF2io+nPLQnl+RQ6XyVcp1Xdv6NgslXRBBCiEU= github.com/vesoft-inc/nebula-http-gateway/ccore v0.0.0-20220413113447-a3f4c56287d8 h1:iL92Uk6hAe4vUBK/L99wf5295HYOtnD4plctVA5xek0= github.com/vesoft-inc/nebula-http-gateway/ccore v0.0.0-20220413113447-a3f4c56287d8/go.mod h1:sFEvE+cY4TgwqWx6H6msOqAUzRhsEHHKaaMgIZENHuQ= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -399,8 +405,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -github.com/zeromicro/go-zero v1.3.2 h1:2HcmceZDEGwZWvofCG+0GXyh+Gtz/wKCW4Fq8Mb7KIg= -github.com/zeromicro/go-zero v1.3.2/go.mod h1:DEj3Fwj1Ui1ltsgf6YqwTL9nD4+tYzIRX0c1pWtQo1E= github.com/zeromicro/go-zero v1.3.3 h1:6qv9PcfqfB1tMgp1FJlP1LfWSZ4XD+FwojvA2h5LL2k= github.com/zeromicro/go-zero v1.3.3/go.mod h1:LwuYc2V04ZHhWPWGJYQ+kJ5DT4QSkeaZGqXiQcpkfks= go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= @@ -522,7 +526,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -598,8 +601,6 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/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-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -726,8 +727,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I= -google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731 h1:nquqdM9+ps0JZcIiI70+tqoaIFS5Ql4ZuK8UXnz3HfE= google.golang.org/genproto v0.0.0-20220422154200-b37d22cd5731/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -746,8 +745,6 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -763,11 +760,11 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -776,6 +773,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=