diff --git a/.github/workflows/import-console-api-dev.yml b/.github/workflows/import-console-api-dev.yml index 58f7045..0a1b772 100644 --- a/.github/workflows/import-console-api-dev.yml +++ b/.github/workflows/import-console-api-dev.yml @@ -7,6 +7,10 @@ on: description: "specify a case or skip to run all tests" default: "" required: false + pull_request: + branches: + - master + jobs: import-console-api-test-dev: diff --git a/pkg/login/login.go b/pkg/login/login.go index 18460e4..f124c73 100644 --- a/pkg/login/login.go +++ b/pkg/login/login.go @@ -1,9 +1,16 @@ package tidbcloudlogin import ( + "bytes" "context" + "encoding/json" "errors" + "fmt" + "io" "net/http" + "net/http/cookiejar" + "net/url" + "strings" "sync" "github.com/tidbcloud/serverless-test/pkg/coreportalapi" @@ -17,29 +24,31 @@ type WebApiLoginContext struct { Auth0ClientSecret string UserEmail string BearerToken string + HTTPClient *http.Client + CookieHeader string mu sync.Mutex } -func (ctx *WebApiLoginContext) LoginAndGetToken(c context.Context) (string, error) { +func (ctx *WebApiLoginContext) Login(c context.Context) (error) { ctx.mu.Lock() defer ctx.mu.Unlock() if ctx.Host == "" { - return "", errors.New("login context is not complete: empty host") + return errors.New("login context is not complete: empty host") } if ctx.Auth0Domain == "" { - return "", errors.New("login context is not complete: empty auth0 domain") + return errors.New("login context is not complete: empty auth0 domain") } if ctx.Auth0ClientID == "" { - return "", errors.New("login context is not complete: empty auth0 client id") + return errors.New("login context is not complete: empty auth0 client id") } if ctx.Auth0ClientSecret == "" { - return "", errors.New("login context is not complete: empty auth0 client secret") + return errors.New("login context is not complete: empty auth0 client secret") } if ctx.UserEmail == "" { - return "", errors.New("login context is not complete: empty user email") + return errors.New("login context is not complete: empty user email") } var err error @@ -52,10 +61,76 @@ func (ctx *WebApiLoginContext) LoginAndGetToken(c context.Context) (string, erro ctx.UserEmail, ) if err != nil { - return "", err + return err + } + + if ctx.HTTPClient == nil { + jar, err := cookiejar.New(nil) + if err != nil { + return fmt.Errorf("failed to init cookie jar: %w", err) + } + ctx.HTTPClient = &http.Client{Jar: jar} + } + + if err := ctx.loginConfirm(c, ctx.BearerToken); err != nil { + return err + } + ctx.HTTPClient.Transport = util.NewBearerTransport(ctx.BearerToken) + + return nil +} + +type loginConfirmReq struct { + IDToken string `json:"id_token"` +} + +func (ctx *WebApiLoginContext) loginConfirm(c context.Context, idToken string) error { + loginConfirmURL := (&url.URL{ + Scheme: "https", + Host: ctx.Host, + Path: "/login_confirm", + }).String() + + bodyBytes, err := json.Marshal(loginConfirmReq{IDToken: idToken}) + if err != nil { + return fmt.Errorf("failed to marshal login_confirm payload: %w", err) + } + + req, err := http.NewRequestWithContext(c, http.MethodPost, loginConfirmURL, bytes.NewReader(bodyBytes)) + if err != nil { + return fmt.Errorf("failed to create login_confirm request: %w", err) + } + req.Header.Set("Content-Type", "application/json") + + resp, err := ctx.HTTPClient.Do(req) + if err != nil { + return fmt.Errorf("login_confirm request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + b, _ := io.ReadAll(resp.Body) + return fmt.Errorf("login_confirm failed: status=%d body=%s", resp.StatusCode, strings.TrimSpace(string(b))) + } + + if ctx.HTTPClient.Jar == nil { + return errors.New("login_confirm succeeded but http client has no cookie jar") + } + + u, _ := url.Parse(loginConfirmURL) + cookies := ctx.HTTPClient.Jar.Cookies(u) + if len(cookies) == 0 { + return errors.New("login_confirm succeeded but no cookies were set") + } + + parts := make([]string, 0, len(cookies)) + for _, ck := range cookies { + parts = append(parts, ck.Name+"="+ck.Value) + } + ctx.CookieHeader = strings.Join(parts, "; ") - return ctx.BearerToken, nil + return nil } // getToken requests a token from tidbcloud test api `POST /api/v1/test-token`. diff --git a/sceneTest/webImport/init_test.go b/sceneTest/webImport/init_test.go index 1374653..092bbc3 100644 --- a/sceneTest/webImport/init_test.go +++ b/sceneTest/webImport/init_test.go @@ -7,7 +7,6 @@ import ( "errors" "flag" "fmt" - "net/http" "os" "strings" "testing" @@ -85,14 +84,12 @@ func NewImportConsoleClient(cfg *config.Config) (*consoleimportapi.APIClient, er UserEmail: cfg.UserEmail, } - token, err := loginCtx.LoginAndGetToken(context.Background()) + err := loginCtx.Login(context.Background()) if err != nil { return nil, fmt.Errorf("failed to login and get token: %w", err) } - httpClient := &http.Client{ - Transport: util.NewBearerTransport(token), - } + httpClient := loginCtx.HTTPClient importCfg := consoleimportapi.NewConfiguration() importCfg.HTTPClient = httpClient