@pippit-dev/cli 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/auth/auth.go +142 -142
- package/cmd/root.go +1 -3
- package/cmd/short_drama_test.go +1 -3
- package/internal/auth/manager.go +144 -144
- package/internal/common/access_key.go +4 -2
- package/internal/common/client.go +47 -19
- package/internal/common/download_results.go +10 -17
- package/internal/common/runner.go +5 -8
- package/package.json +1 -1
package/cmd/auth/auth.go
CHANGED
|
@@ -1,144 +1,144 @@
|
|
|
1
1
|
package authcmd
|
|
2
2
|
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"io"
|
|
6
|
-
"strings"
|
|
7
|
-
"time"
|
|
8
|
-
|
|
9
|
-
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
10
|
-
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
11
|
-
"github.com/bytedance/sonic"
|
|
12
|
-
"github.com/spf13/cobra"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
type checkResult struct {
|
|
16
|
-
Pending bool `json:"pending"`
|
|
17
|
-
State any `json:"state,omitempty"`
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
21
|
-
cmd := &cobra.Command{
|
|
22
|
-
Use: "auth",
|
|
23
|
-
Short: "Manage Pippit OAuth login state",
|
|
24
|
-
}
|
|
25
|
-
cmd.SetOut(stdout)
|
|
26
|
-
cmd.SetErr(stderr)
|
|
27
|
-
cmd.AddCommand(newLoginCommand(stdout, stderr, runner))
|
|
28
|
-
cmd.AddCommand(newCheckCommand(stdout, stderr, runner))
|
|
29
|
-
cmd.AddCommand(newStatusCommand(stdout, stderr, runner))
|
|
30
|
-
cmd.AddCommand(newLogoutCommand(stdout, stderr, runner))
|
|
31
|
-
return cmd
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func newLoginCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
35
|
-
cmd := &cobra.Command{
|
|
36
|
-
Use: "login",
|
|
37
|
-
Short: "Start an OAuth device login flow",
|
|
38
|
-
Args: cobra.NoArgs,
|
|
39
|
-
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
40
|
-
if runner == nil || runner.AuthAuthorizer == nil {
|
|
41
|
-
return fmt.Errorf("auth manager is required")
|
|
42
|
-
}
|
|
43
|
-
flow, err := runner.AuthAuthorizer.NewLoginFlow(cmd.Context())
|
|
44
|
-
if err != nil {
|
|
45
|
-
return err
|
|
46
|
-
}
|
|
47
|
-
return writeJSON(stdout, flow)
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
cmd.SetOut(stdout)
|
|
51
|
-
cmd.SetErr(stderr)
|
|
52
|
-
return cmd
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
func newCheckCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
56
|
-
var deviceCode string
|
|
57
|
-
cmd := &cobra.Command{
|
|
58
|
-
Use: "check",
|
|
59
|
-
Short: "Check whether an OAuth device login has completed",
|
|
60
|
-
Args: cobra.NoArgs,
|
|
61
|
-
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
62
|
-
deviceCode = strings.TrimSpace(deviceCode)
|
|
63
|
-
if deviceCode == "" {
|
|
64
|
-
return fmt.Errorf("--device-code is required")
|
|
65
|
-
}
|
|
66
|
-
if runner == nil || runner.AuthAuthorizer == nil {
|
|
67
|
-
return fmt.Errorf("auth manager is required")
|
|
68
|
-
}
|
|
69
|
-
state, err := runner.AuthAuthorizer.CheckLogin(cmd.Context(), deviceCode)
|
|
70
|
-
if auth.IsLoginPending(err) {
|
|
71
|
-
return writeJSON(stdout, checkResult{Pending: true})
|
|
72
|
-
}
|
|
73
|
-
if err != nil {
|
|
74
|
-
return err
|
|
75
|
-
}
|
|
76
|
-
v := map[string]any{
|
|
77
|
-
"logged_in": state.LoggedIn,
|
|
78
|
-
"expires_at": state.ExpiresAt.Format(time.RFC3339),
|
|
79
|
-
}
|
|
80
|
-
return writeJSON(stdout, checkResult{State: v})
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
cmd.SetOut(stdout)
|
|
84
|
-
cmd.SetErr(stderr)
|
|
85
|
-
cmd.Flags().StringVar(&deviceCode, "device-code", "", "device code returned by auth login")
|
|
86
|
-
return cmd
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
func newStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
90
|
-
cmd := &cobra.Command{
|
|
91
|
-
Use: "status",
|
|
92
|
-
Short: "Show current OAuth login state",
|
|
93
|
-
Args: cobra.NoArgs,
|
|
94
|
-
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
95
|
-
if runner == nil || runner.AuthAuthorizer == nil {
|
|
96
|
-
return fmt.Errorf("auth manager is required")
|
|
97
|
-
}
|
|
98
|
-
state, err := runner.AuthAuthorizer.State(cmd.Context())
|
|
99
|
-
if err != nil {
|
|
100
|
-
return err
|
|
101
|
-
}
|
|
102
|
-
if !state.LoggedIn {
|
|
103
|
-
return fmt.Errorf("not logged in")
|
|
104
|
-
}
|
|
105
|
-
v := map[string]any{
|
|
106
|
-
"logged_in": state.LoggedIn,
|
|
107
|
-
"expires_at": state.ExpiresAt.Format(time.RFC3339),
|
|
108
|
-
}
|
|
109
|
-
return writeJSON(stdout, v)
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
cmd.SetOut(stdout)
|
|
113
|
-
cmd.SetErr(stderr)
|
|
114
|
-
return cmd
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
func newLogoutCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
118
|
-
cmd := &cobra.Command{
|
|
119
|
-
Use: "logout",
|
|
120
|
-
Short: "Clear local OAuth login state",
|
|
121
|
-
Args: cobra.NoArgs,
|
|
122
|
-
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
123
|
-
if runner == nil || runner.AuthAuthorizer == nil {
|
|
124
|
-
return fmt.Errorf("auth manager is required")
|
|
125
|
-
}
|
|
126
|
-
if err := runner.AuthAuthorizer.Logout(cmd.Context()); err != nil {
|
|
127
|
-
return err
|
|
128
|
-
}
|
|
129
|
-
return writeJSON(stdout, map[string]bool{"logged_out": true})
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
cmd.SetOut(stdout)
|
|
133
|
-
cmd.SetErr(stderr)
|
|
134
|
-
return cmd
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
func writeJSON(w io.Writer, v any) error {
|
|
138
|
-
data, err := sonic.Marshal(v)
|
|
139
|
-
if err != nil {
|
|
140
|
-
return err
|
|
141
|
-
}
|
|
142
|
-
_, err = fmt.Fprintln(w, string(data))
|
|
143
|
-
return err
|
|
144
|
-
}
|
|
3
|
+
//import (
|
|
4
|
+
// "fmt"
|
|
5
|
+
// "io"
|
|
6
|
+
// "strings"
|
|
7
|
+
// "time"
|
|
8
|
+
//
|
|
9
|
+
// "github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
10
|
+
// "github.com/Pippit-dev/pippit-cli/internal/common"
|
|
11
|
+
// "github.com/bytedance/sonic"
|
|
12
|
+
// "github.com/spf13/cobra"
|
|
13
|
+
//)
|
|
14
|
+
//
|
|
15
|
+
//type checkResult struct {
|
|
16
|
+
// Pending bool `json:"pending"`
|
|
17
|
+
// State any `json:"state,omitempty"`
|
|
18
|
+
//}
|
|
19
|
+
//
|
|
20
|
+
//func NewCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
21
|
+
// cmd := &cobra.Command{
|
|
22
|
+
// Use: "auth",
|
|
23
|
+
// Short: "Manage Pippit OAuth login state",
|
|
24
|
+
// }
|
|
25
|
+
// cmd.SetOut(stdout)
|
|
26
|
+
// cmd.SetErr(stderr)
|
|
27
|
+
// cmd.AddCommand(newLoginCommand(stdout, stderr, runner))
|
|
28
|
+
// cmd.AddCommand(newCheckCommand(stdout, stderr, runner))
|
|
29
|
+
// cmd.AddCommand(newStatusCommand(stdout, stderr, runner))
|
|
30
|
+
// cmd.AddCommand(newLogoutCommand(stdout, stderr, runner))
|
|
31
|
+
// return cmd
|
|
32
|
+
//}
|
|
33
|
+
//
|
|
34
|
+
//func newLoginCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
35
|
+
// cmd := &cobra.Command{
|
|
36
|
+
// Use: "login",
|
|
37
|
+
// Short: "Start an OAuth device login flow",
|
|
38
|
+
// Args: cobra.NoArgs,
|
|
39
|
+
// RunE: func(cmd *cobra.Command, _ []string) error {
|
|
40
|
+
// if runner == nil || runner.AuthAuthorizer == nil {
|
|
41
|
+
// return fmt.Errorf("auth manager is required")
|
|
42
|
+
// }
|
|
43
|
+
// flow, err := runner.AuthAuthorizer.NewLoginFlow(cmd.Context())
|
|
44
|
+
// if err != nil {
|
|
45
|
+
// return err
|
|
46
|
+
// }
|
|
47
|
+
// return writeJSON(stdout, flow)
|
|
48
|
+
// },
|
|
49
|
+
// }
|
|
50
|
+
// cmd.SetOut(stdout)
|
|
51
|
+
// cmd.SetErr(stderr)
|
|
52
|
+
// return cmd
|
|
53
|
+
//}
|
|
54
|
+
//
|
|
55
|
+
//func newCheckCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
56
|
+
// var deviceCode string
|
|
57
|
+
// cmd := &cobra.Command{
|
|
58
|
+
// Use: "check",
|
|
59
|
+
// Short: "Check whether an OAuth device login has completed",
|
|
60
|
+
// Args: cobra.NoArgs,
|
|
61
|
+
// RunE: func(cmd *cobra.Command, _ []string) error {
|
|
62
|
+
// deviceCode = strings.TrimSpace(deviceCode)
|
|
63
|
+
// if deviceCode == "" {
|
|
64
|
+
// return fmt.Errorf("--device-code is required")
|
|
65
|
+
// }
|
|
66
|
+
// if runner == nil || runner.AuthAuthorizer == nil {
|
|
67
|
+
// return fmt.Errorf("auth manager is required")
|
|
68
|
+
// }
|
|
69
|
+
// state, err := runner.AuthAuthorizer.CheckLogin(cmd.Context(), deviceCode)
|
|
70
|
+
// if auth.IsLoginPending(err) {
|
|
71
|
+
// return writeJSON(stdout, checkResult{Pending: true})
|
|
72
|
+
// }
|
|
73
|
+
// if err != nil {
|
|
74
|
+
// return err
|
|
75
|
+
// }
|
|
76
|
+
// v := map[string]any{
|
|
77
|
+
// "logged_in": state.LoggedIn,
|
|
78
|
+
// "expires_at": state.ExpiresAt.Format(time.RFC3339),
|
|
79
|
+
// }
|
|
80
|
+
// return writeJSON(stdout, checkResult{State: v})
|
|
81
|
+
// },
|
|
82
|
+
// }
|
|
83
|
+
// cmd.SetOut(stdout)
|
|
84
|
+
// cmd.SetErr(stderr)
|
|
85
|
+
// cmd.Flags().StringVar(&deviceCode, "device-code", "", "device code returned by auth login")
|
|
86
|
+
// return cmd
|
|
87
|
+
//}
|
|
88
|
+
//
|
|
89
|
+
//func newStatusCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
90
|
+
// cmd := &cobra.Command{
|
|
91
|
+
// Use: "status",
|
|
92
|
+
// Short: "Show current OAuth login state",
|
|
93
|
+
// Args: cobra.NoArgs,
|
|
94
|
+
// RunE: func(cmd *cobra.Command, _ []string) error {
|
|
95
|
+
// if runner == nil || runner.AuthAuthorizer == nil {
|
|
96
|
+
// return fmt.Errorf("auth manager is required")
|
|
97
|
+
// }
|
|
98
|
+
// state, err := runner.AuthAuthorizer.State(cmd.Context())
|
|
99
|
+
// if err != nil {
|
|
100
|
+
// return err
|
|
101
|
+
// }
|
|
102
|
+
// if !state.LoggedIn {
|
|
103
|
+
// return fmt.Errorf("not logged in")
|
|
104
|
+
// }
|
|
105
|
+
// v := map[string]any{
|
|
106
|
+
// "logged_in": state.LoggedIn,
|
|
107
|
+
// "expires_at": state.ExpiresAt.Format(time.RFC3339),
|
|
108
|
+
// }
|
|
109
|
+
// return writeJSON(stdout, v)
|
|
110
|
+
// },
|
|
111
|
+
// }
|
|
112
|
+
// cmd.SetOut(stdout)
|
|
113
|
+
// cmd.SetErr(stderr)
|
|
114
|
+
// return cmd
|
|
115
|
+
//}
|
|
116
|
+
//
|
|
117
|
+
//func newLogoutCommand(stdout, stderr io.Writer, runner *common.Runner) *cobra.Command {
|
|
118
|
+
// cmd := &cobra.Command{
|
|
119
|
+
// Use: "logout",
|
|
120
|
+
// Short: "Clear local OAuth login state",
|
|
121
|
+
// Args: cobra.NoArgs,
|
|
122
|
+
// RunE: func(cmd *cobra.Command, _ []string) error {
|
|
123
|
+
// if runner == nil || runner.AuthAuthorizer == nil {
|
|
124
|
+
// return fmt.Errorf("auth manager is required")
|
|
125
|
+
// }
|
|
126
|
+
// if err := runner.AuthAuthorizer.Logout(cmd.Context()); err != nil {
|
|
127
|
+
// return err
|
|
128
|
+
// }
|
|
129
|
+
// return writeJSON(stdout, map[string]bool{"logged_out": true})
|
|
130
|
+
// },
|
|
131
|
+
// }
|
|
132
|
+
// cmd.SetOut(stdout)
|
|
133
|
+
// cmd.SetErr(stderr)
|
|
134
|
+
// return cmd
|
|
135
|
+
//}
|
|
136
|
+
//
|
|
137
|
+
//func writeJSON(w io.Writer, v any) error {
|
|
138
|
+
// data, err := sonic.Marshal(v)
|
|
139
|
+
// if err != nil {
|
|
140
|
+
// return err
|
|
141
|
+
// }
|
|
142
|
+
// _, err = fmt.Fprintln(w, string(data))
|
|
143
|
+
// return err
|
|
144
|
+
//}
|
package/cmd/root.go
CHANGED
|
@@ -7,7 +7,6 @@ import (
|
|
|
7
7
|
// authcmd "github.com/Pippit-dev/pippit-cli/cmd/auth"
|
|
8
8
|
"github.com/Pippit-dev/pippit-cli/cmd/short_drama"
|
|
9
9
|
updatecmd "github.com/Pippit-dev/pippit-cli/cmd/update"
|
|
10
|
-
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
11
10
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
12
11
|
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
13
12
|
"github.com/spf13/cobra"
|
|
@@ -20,9 +19,8 @@ func Execute() error {
|
|
|
20
19
|
|
|
21
20
|
func NewRootCommand(stdout, stderr io.Writer) *cobra.Command {
|
|
22
21
|
cfg := config.Load()
|
|
23
|
-
authManager := auth.NewManager(cfg)
|
|
24
22
|
client := common.NewHTTPClient(cfg.BaseURL, cfg.HTTPTimeout, common.NewAccessKeyAuthorizer(cfg.AccessKey))
|
|
25
|
-
runner := common.NewRunner(cfg, client
|
|
23
|
+
runner := common.NewRunner(cfg, client)
|
|
26
24
|
return newRootCommand(stdout, stderr, runner)
|
|
27
25
|
}
|
|
28
26
|
|
package/cmd/short_drama_test.go
CHANGED
|
@@ -10,7 +10,6 @@ import (
|
|
|
10
10
|
"strings"
|
|
11
11
|
"testing"
|
|
12
12
|
|
|
13
|
-
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
14
13
|
"github.com/Pippit-dev/pippit-cli/internal/common"
|
|
15
14
|
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
16
15
|
"github.com/bytedance/sonic"
|
|
@@ -542,9 +541,8 @@ func newTestRootCommandWithAccessKey(t *testing.T, stdout, stderr io.Writer, bas
|
|
|
542
541
|
cfg := config.Load()
|
|
543
542
|
cfg.BaseURL = baseURL
|
|
544
543
|
cfg.AccessKey = accessKey
|
|
545
|
-
authAuthorizer := auth.NewManager(cfg)
|
|
546
544
|
client := common.NewHTTPClient(cfg.BaseURL, cfg.HTTPTimeout, common.NewAccessKeyAuthorizer(cfg.AccessKey))
|
|
547
|
-
runner := common.NewRunner(cfg, client
|
|
545
|
+
runner := common.NewRunner(cfg, client)
|
|
548
546
|
return newRootCommand(stdout, stderr, runner)
|
|
549
547
|
}
|
|
550
548
|
|
package/internal/auth/manager.go
CHANGED
|
@@ -1,146 +1,146 @@
|
|
|
1
1
|
package auth
|
|
2
2
|
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"errors"
|
|
6
|
-
"fmt"
|
|
7
|
-
"net/http"
|
|
8
|
-
"sync"
|
|
9
|
-
"time"
|
|
10
|
-
|
|
11
|
-
"code.byted.org/passport/auth_client/go/authsdk"
|
|
12
|
-
|
|
13
|
-
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
type Authorizer interface {
|
|
17
|
-
Refresh(ctx context.Context, ensureTTL time.Duration) error
|
|
18
|
-
Inject(ctx context.Context, req *http.Request) error
|
|
19
|
-
NewLoginFlow(ctx context.Context) (*LoginFlow, error)
|
|
20
|
-
CheckLogin(ctx context.Context, deviceCode string) (*State, error)
|
|
21
|
-
State(ctx context.Context) (*State, error)
|
|
22
|
-
Logout(ctx context.Context) error
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type OAuthManager struct {
|
|
26
|
-
cfg *config.Config
|
|
27
|
-
mu sync.Mutex
|
|
28
|
-
client *authsdk.Client
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
type LoginFlow struct {
|
|
32
|
-
DeviceCode string `json:"device_code"`
|
|
33
|
-
UserCode string `json:"user_code"`
|
|
34
|
-
VerificationURI string `json:"verification_uri"`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type State struct {
|
|
38
|
-
LoggedIn bool `json:"logged_in"`
|
|
39
|
-
ExpiresAt time.Time `json:"expires_at,omitempty"`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
func NewManager(cfg *config.Config) *OAuthManager {
|
|
43
|
-
return &OAuthManager{cfg: cfg}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func (m *OAuthManager) NewLoginFlow(ctx context.Context) (*LoginFlow, error) {
|
|
47
|
-
client, err := m.clientInstance()
|
|
48
|
-
if err != nil {
|
|
49
|
-
return nil, err
|
|
50
|
-
}
|
|
51
|
-
flow, err := client.Authenticator().NewLoginFlow(ctx)
|
|
52
|
-
if err != nil {
|
|
53
|
-
return nil, err
|
|
54
|
-
}
|
|
55
|
-
return &LoginFlow{
|
|
56
|
-
DeviceCode: flow.DeviceCode,
|
|
57
|
-
UserCode: flow.UserCode,
|
|
58
|
-
VerificationURI: flow.VerificationURI,
|
|
59
|
-
}, nil
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func (m *OAuthManager) CheckLogin(ctx context.Context, deviceCode string) (*State, error) {
|
|
63
|
-
client, err := m.clientInstance()
|
|
64
|
-
if err != nil {
|
|
65
|
-
return nil, err
|
|
66
|
-
}
|
|
67
|
-
state, err := client.Authenticator().CheckLogin(ctx, deviceCode)
|
|
68
|
-
if err != nil {
|
|
69
|
-
return nil, err
|
|
70
|
-
}
|
|
71
|
-
return authState(state), nil
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
func (m *OAuthManager) State(ctx context.Context) (*State, error) {
|
|
75
|
-
client, err := m.clientInstance()
|
|
76
|
-
if err != nil {
|
|
77
|
-
return nil, err
|
|
78
|
-
}
|
|
79
|
-
state, err := client.Authorizer().State(ctx)
|
|
80
|
-
if err != nil {
|
|
81
|
-
return nil, err
|
|
82
|
-
}
|
|
83
|
-
return authState(state), nil
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func (m *OAuthManager) Refresh(ctx context.Context, ensureTTL time.Duration) error {
|
|
87
|
-
client, err := m.clientInstance()
|
|
88
|
-
if err != nil {
|
|
89
|
-
return err
|
|
90
|
-
}
|
|
91
|
-
_, err = client.Authorizer().Refresh(ctx, ensureTTL)
|
|
92
|
-
return err
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
func (m *OAuthManager) Inject(ctx context.Context, req *http.Request) error {
|
|
96
|
-
client, err := m.clientInstance()
|
|
97
|
-
if err != nil {
|
|
98
|
-
return err
|
|
99
|
-
}
|
|
100
|
-
return client.Authorizer().Inject(ctx, req)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
func (m *OAuthManager) Logout(ctx context.Context) error {
|
|
104
|
-
client, err := m.clientInstance()
|
|
105
|
-
if err != nil {
|
|
106
|
-
return err
|
|
107
|
-
}
|
|
108
|
-
return client.Authorizer().Logout(ctx)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
func IsLoginPending(err error) bool {
|
|
112
|
-
return errors.Is(err, authsdk.ErrLoginPending)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
func (m *OAuthManager) clientInstance() (*authsdk.Client, error) {
|
|
116
|
-
m.mu.Lock()
|
|
117
|
-
defer m.mu.Unlock()
|
|
118
|
-
if m.client != nil {
|
|
119
|
-
return m.client, nil
|
|
120
|
-
}
|
|
121
|
-
if m.cfg == nil || m.cfg.OAuth == nil {
|
|
122
|
-
return nil, errors.New("oauth config is required")
|
|
123
|
-
}
|
|
124
|
-
oauth := m.cfg.OAuth
|
|
125
|
-
client, err := authsdk.NewClient(authsdk.Config{
|
|
126
|
-
ClientKey: oauth.ClientKey,
|
|
127
|
-
BaseURL: oauth.BaseURL,
|
|
128
|
-
StoreServiceName: oauth.StoreServiceName,
|
|
129
|
-
Scopes: oauth.Scopes,
|
|
130
|
-
})
|
|
131
|
-
if err != nil {
|
|
132
|
-
return nil, fmt.Errorf("initialize oauth client: %w", err)
|
|
133
|
-
}
|
|
134
|
-
m.client = client
|
|
135
|
-
return client, nil
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
func authState(state *authsdk.AuthStateView) *State {
|
|
139
|
-
if state == nil {
|
|
140
|
-
return &State{}
|
|
141
|
-
}
|
|
142
|
-
return &State{
|
|
143
|
-
LoggedIn: state.LoggedIn,
|
|
144
|
-
ExpiresAt: state.ExpiresAt,
|
|
145
|
-
}
|
|
146
|
-
}
|
|
3
|
+
//import (
|
|
4
|
+
// "context"
|
|
5
|
+
// "errors"
|
|
6
|
+
// "fmt"
|
|
7
|
+
// "net/http"
|
|
8
|
+
// "sync"
|
|
9
|
+
// "time"
|
|
10
|
+
//
|
|
11
|
+
// "code.byted.org/passport/auth_client/go/authsdk"
|
|
12
|
+
//
|
|
13
|
+
// "github.com/Pippit-dev/pippit-cli/internal/config"
|
|
14
|
+
//)
|
|
15
|
+
//
|
|
16
|
+
//type Authorizer interface {
|
|
17
|
+
// Refresh(ctx context.Context, ensureTTL time.Duration) error
|
|
18
|
+
// Inject(ctx context.Context, req *http.Request) error
|
|
19
|
+
// NewLoginFlow(ctx context.Context) (*LoginFlow, error)
|
|
20
|
+
// CheckLogin(ctx context.Context, deviceCode string) (*State, error)
|
|
21
|
+
// State(ctx context.Context) (*State, error)
|
|
22
|
+
// Logout(ctx context.Context) error
|
|
23
|
+
//}
|
|
24
|
+
//
|
|
25
|
+
//type OAuthManager struct {
|
|
26
|
+
// cfg *config.Config
|
|
27
|
+
// mu sync.Mutex
|
|
28
|
+
// client *authsdk.Client
|
|
29
|
+
//}
|
|
30
|
+
//
|
|
31
|
+
//type LoginFlow struct {
|
|
32
|
+
// DeviceCode string `json:"device_code"`
|
|
33
|
+
// UserCode string `json:"user_code"`
|
|
34
|
+
// VerificationURI string `json:"verification_uri"`
|
|
35
|
+
//}
|
|
36
|
+
//
|
|
37
|
+
//type State struct {
|
|
38
|
+
// LoggedIn bool `json:"logged_in"`
|
|
39
|
+
// ExpiresAt time.Time `json:"expires_at,omitempty"`
|
|
40
|
+
//}
|
|
41
|
+
//
|
|
42
|
+
//func NewManager(cfg *config.Config) *OAuthManager {
|
|
43
|
+
// return &OAuthManager{cfg: cfg}
|
|
44
|
+
//}
|
|
45
|
+
//
|
|
46
|
+
//func (m *OAuthManager) NewLoginFlow(ctx context.Context) (*LoginFlow, error) {
|
|
47
|
+
// client, err := m.clientInstance()
|
|
48
|
+
// if err != nil {
|
|
49
|
+
// return nil, err
|
|
50
|
+
// }
|
|
51
|
+
// flow, err := client.Authenticator().NewLoginFlow(ctx)
|
|
52
|
+
// if err != nil {
|
|
53
|
+
// return nil, err
|
|
54
|
+
// }
|
|
55
|
+
// return &LoginFlow{
|
|
56
|
+
// DeviceCode: flow.DeviceCode,
|
|
57
|
+
// UserCode: flow.UserCode,
|
|
58
|
+
// VerificationURI: flow.VerificationURI,
|
|
59
|
+
// }, nil
|
|
60
|
+
//}
|
|
61
|
+
//
|
|
62
|
+
//func (m *OAuthManager) CheckLogin(ctx context.Context, deviceCode string) (*State, error) {
|
|
63
|
+
// client, err := m.clientInstance()
|
|
64
|
+
// if err != nil {
|
|
65
|
+
// return nil, err
|
|
66
|
+
// }
|
|
67
|
+
// state, err := client.Authenticator().CheckLogin(ctx, deviceCode)
|
|
68
|
+
// if err != nil {
|
|
69
|
+
// return nil, err
|
|
70
|
+
// }
|
|
71
|
+
// return authState(state), nil
|
|
72
|
+
//}
|
|
73
|
+
//
|
|
74
|
+
//func (m *OAuthManager) State(ctx context.Context) (*State, error) {
|
|
75
|
+
// client, err := m.clientInstance()
|
|
76
|
+
// if err != nil {
|
|
77
|
+
// return nil, err
|
|
78
|
+
// }
|
|
79
|
+
// state, err := client.Authorizer().State(ctx)
|
|
80
|
+
// if err != nil {
|
|
81
|
+
// return nil, err
|
|
82
|
+
// }
|
|
83
|
+
// return authState(state), nil
|
|
84
|
+
//}
|
|
85
|
+
//
|
|
86
|
+
//func (m *OAuthManager) Refresh(ctx context.Context, ensureTTL time.Duration) error {
|
|
87
|
+
// client, err := m.clientInstance()
|
|
88
|
+
// if err != nil {
|
|
89
|
+
// return err
|
|
90
|
+
// }
|
|
91
|
+
// _, err = client.Authorizer().Refresh(ctx, ensureTTL)
|
|
92
|
+
// return err
|
|
93
|
+
//}
|
|
94
|
+
//
|
|
95
|
+
//func (m *OAuthManager) Inject(ctx context.Context, req *http.Request) error {
|
|
96
|
+
// client, err := m.clientInstance()
|
|
97
|
+
// if err != nil {
|
|
98
|
+
// return err
|
|
99
|
+
// }
|
|
100
|
+
// return client.Authorizer().Inject(ctx, req)
|
|
101
|
+
//}
|
|
102
|
+
//
|
|
103
|
+
//func (m *OAuthManager) Logout(ctx context.Context) error {
|
|
104
|
+
// client, err := m.clientInstance()
|
|
105
|
+
// if err != nil {
|
|
106
|
+
// return err
|
|
107
|
+
// }
|
|
108
|
+
// return client.Authorizer().Logout(ctx)
|
|
109
|
+
//}
|
|
110
|
+
//
|
|
111
|
+
//func IsLoginPending(err error) bool {
|
|
112
|
+
// return errors.Is(err, authsdk.ErrLoginPending)
|
|
113
|
+
//}
|
|
114
|
+
//
|
|
115
|
+
//func (m *OAuthManager) clientInstance() (*authsdk.Client, error) {
|
|
116
|
+
// m.mu.Lock()
|
|
117
|
+
// defer m.mu.Unlock()
|
|
118
|
+
// if m.client != nil {
|
|
119
|
+
// return m.client, nil
|
|
120
|
+
// }
|
|
121
|
+
// if m.cfg == nil || m.cfg.OAuth == nil {
|
|
122
|
+
// return nil, errors.New("oauth config is required")
|
|
123
|
+
// }
|
|
124
|
+
// oauth := m.cfg.OAuth
|
|
125
|
+
// client, err := authsdk.NewClient(authsdk.Config{
|
|
126
|
+
// ClientKey: oauth.ClientKey,
|
|
127
|
+
// BaseURL: oauth.BaseURL,
|
|
128
|
+
// StoreServiceName: oauth.StoreServiceName,
|
|
129
|
+
// Scopes: oauth.Scopes,
|
|
130
|
+
// })
|
|
131
|
+
// if err != nil {
|
|
132
|
+
// return nil, fmt.Errorf("initialize oauth client: %w", err)
|
|
133
|
+
// }
|
|
134
|
+
// m.client = client
|
|
135
|
+
// return client, nil
|
|
136
|
+
//}
|
|
137
|
+
//
|
|
138
|
+
//func authState(state *authsdk.AuthStateView) *State {
|
|
139
|
+
// if state == nil {
|
|
140
|
+
// return &State{}
|
|
141
|
+
// }
|
|
142
|
+
// return &State{
|
|
143
|
+
// LoggedIn: state.LoggedIn,
|
|
144
|
+
// ExpiresAt: state.ExpiresAt,
|
|
145
|
+
// }
|
|
146
|
+
//}
|
|
@@ -21,9 +21,11 @@ func (a *accessKeyAuthorizer) Inject(ctx context.Context, req *http.Request) err
|
|
|
21
21
|
if err := ctx.Err(); err != nil {
|
|
22
22
|
return err
|
|
23
23
|
}
|
|
24
|
-
if a.accessKey == "" {
|
|
24
|
+
if a.accessKey == "" && req.Method == http.MethodPost {
|
|
25
25
|
return fmt.Errorf("%s is required for authenticated requests", config.EnvXYQAccessKey)
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
if a.accessKey != "" {
|
|
28
|
+
req.Header.Set("Authorization", "Bearer "+a.accessKey)
|
|
29
|
+
}
|
|
28
30
|
return nil
|
|
29
31
|
}
|
|
@@ -7,6 +7,7 @@ import (
|
|
|
7
7
|
"io"
|
|
8
8
|
"net/http"
|
|
9
9
|
"net/url"
|
|
10
|
+
"reflect"
|
|
10
11
|
"strings"
|
|
11
12
|
"time"
|
|
12
13
|
|
|
@@ -40,50 +41,77 @@ func NewHTTPClient(baseURL string, timeout time.Duration, authorizer RequestAuth
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
func (c *httpClient) SendRequest(ctx context.Context, path string, body any, out any) error {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
req, err := c.newJSONRequest(ctx, path, body)
|
|
47
|
-
if err != nil {
|
|
48
|
-
return err
|
|
49
|
-
}
|
|
50
|
-
if err := c.authorizer.Inject(ctx, req); err != nil {
|
|
51
|
-
return fmt.Errorf("inject auth headers: %w", err)
|
|
44
|
+
method := http.MethodPost
|
|
45
|
+
if body == nil {
|
|
46
|
+
method = http.MethodGet
|
|
52
47
|
}
|
|
53
|
-
return c.do(req, out)
|
|
54
|
-
}
|
|
55
48
|
|
|
56
|
-
func (c *httpClient) newJSONRequest(ctx context.Context, path string, body any) (*http.Request, error) {
|
|
57
49
|
reqURL, err := c.resolveURL(path, nil)
|
|
58
50
|
if err != nil {
|
|
59
|
-
return
|
|
51
|
+
return err
|
|
60
52
|
}
|
|
53
|
+
|
|
61
54
|
var reader io.Reader
|
|
62
55
|
if body != nil {
|
|
63
56
|
payload, err := sonic.Marshal(body)
|
|
64
57
|
if err != nil {
|
|
65
|
-
return
|
|
58
|
+
return fmt.Errorf("encode body: %w", err)
|
|
66
59
|
}
|
|
67
60
|
reader = bytes.NewReader(payload)
|
|
68
61
|
}
|
|
69
|
-
|
|
62
|
+
|
|
63
|
+
req, err := http.NewRequestWithContext(ctx, method, reqURL, reader)
|
|
70
64
|
if err != nil {
|
|
71
|
-
return
|
|
65
|
+
return fmt.Errorf("build %s request: %w", method, err)
|
|
72
66
|
}
|
|
73
67
|
if body != nil {
|
|
74
68
|
req.Header.Set("Content-Type", "application/json")
|
|
75
69
|
}
|
|
76
|
-
|
|
70
|
+
|
|
71
|
+
if c.authorizer == nil {
|
|
72
|
+
return fmt.Errorf("authorized request requires authorizer")
|
|
73
|
+
}
|
|
74
|
+
if err := c.authorizer.Inject(ctx, req); err != nil {
|
|
75
|
+
return fmt.Errorf("inject auth headers: %w", err)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
c.injectHeaders(req)
|
|
79
|
+
|
|
80
|
+
// If out is **http.Response, return the raw response for streaming (e.g. file download).
|
|
81
|
+
if out != nil {
|
|
82
|
+
if rv := reflect.ValueOf(out); rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Ptr {
|
|
83
|
+
if rv.Elem().Type().Elem() == reflect.TypeOf(http.Response{}) {
|
|
84
|
+
resp, err := c.httpClient.Do(req)
|
|
85
|
+
if err != nil {
|
|
86
|
+
return fmt.Errorf("%s %s failed: %w", method, reqURL, err)
|
|
87
|
+
}
|
|
88
|
+
if resp.StatusCode >= 400 {
|
|
89
|
+
defer resp.Body.Close()
|
|
90
|
+
return fmt.Errorf("%s %s returned HTTP %d", method, reqURL, resp.StatusCode)
|
|
91
|
+
}
|
|
92
|
+
rv.Elem().Set(reflect.ValueOf(resp))
|
|
93
|
+
return nil
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
req.Header.Set("Accept", "application/json")
|
|
99
|
+
|
|
100
|
+
return c.do(req, out)
|
|
77
101
|
}
|
|
78
102
|
|
|
79
|
-
func (c *httpClient)
|
|
103
|
+
func (c *httpClient) injectHeaders(req *http.Request) {
|
|
80
104
|
for k, values := range c.headers {
|
|
81
105
|
for _, v := range values {
|
|
82
106
|
req.Header.Add(k, v)
|
|
83
107
|
}
|
|
84
108
|
}
|
|
85
|
-
req.Header.Set("
|
|
109
|
+
req.Header.Set("User-Agent", "Pippit-CLI/1.0")
|
|
110
|
+
req.Header.Set("x-use-ppe", "1")
|
|
111
|
+
req.Header.Set("x-tt-env", "ppe_harness_novel_v2")
|
|
112
|
+
}
|
|
86
113
|
|
|
114
|
+
func (c *httpClient) do(req *http.Request, out any) error {
|
|
87
115
|
resp, err := c.httpClient.Do(req)
|
|
88
116
|
if err != nil {
|
|
89
117
|
return fmt.Errorf("%s %s failed: %w", req.Method, req.URL.String(), err)
|
|
@@ -15,6 +15,10 @@ import (
|
|
|
15
15
|
"time"
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
+
// DownloadClient is a minimal interface for downloading files via HTTP GET.
|
|
19
|
+
// It is satisfied by common.Client so that download logic can use the same
|
|
20
|
+
// HTTP infrastructure (headers, auth, timeouts) as API calls.
|
|
21
|
+
|
|
18
22
|
// DownloadResultOptions is the command-facing shape for downloading one result URL.
|
|
19
23
|
type DownloadResultOptions struct {
|
|
20
24
|
URL string `json:"url"`
|
|
@@ -51,7 +55,7 @@ type downloadTaskResult struct {
|
|
|
51
55
|
err error
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
func DownloadResult(ctx context.Context, opts DownloadResultOptions,
|
|
58
|
+
func DownloadResult(ctx context.Context, opts DownloadResultOptions, runner *Runner) (*DownloadResultResponse, error) {
|
|
55
59
|
if err := ctx.Err(); err != nil {
|
|
56
60
|
return nil, err
|
|
57
61
|
}
|
|
@@ -104,7 +108,6 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
|
|
|
104
108
|
workers = len(tasks)
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
client := &http.Client{Timeout: 600 * time.Second}
|
|
108
111
|
taskCh := make(chan downloadTask)
|
|
109
112
|
resultCh := make(chan downloadTaskResult, len(tasks))
|
|
110
113
|
|
|
@@ -116,7 +119,7 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
|
|
|
116
119
|
for task := range taskCh {
|
|
117
120
|
resultCh <- downloadTaskResult{
|
|
118
121
|
filepath: task.filepath,
|
|
119
|
-
err: downloadFileWithRetry(ctx,
|
|
122
|
+
err: downloadFileWithRetry(ctx, runner.Client, task.url, task.filepath),
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
}()
|
|
@@ -167,7 +170,7 @@ func DownloadResult(ctx context.Context, opts DownloadResultOptions, _ *Runner)
|
|
|
167
170
|
return result, nil
|
|
168
171
|
}
|
|
169
172
|
|
|
170
|
-
func downloadFileWithRetry(ctx context.Context, client
|
|
173
|
+
func downloadFileWithRetry(ctx context.Context, client Client, rawURL string, targetPath string) error {
|
|
171
174
|
var lastErr error
|
|
172
175
|
for attempt := 0; attempt <= maxDownloadRetries; attempt++ {
|
|
173
176
|
if attempt > 0 {
|
|
@@ -203,23 +206,13 @@ func isRetryableError(err error) bool {
|
|
|
203
206
|
return false
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
func downloadFile(ctx context.Context, client
|
|
207
|
-
|
|
208
|
-
if err != nil {
|
|
209
|
-
return fmt.Errorf("build download request: %w", err)
|
|
210
|
-
}
|
|
211
|
-
req.Header.Set("User-Agent", "Pippit-CLI/1.0")
|
|
212
|
-
|
|
213
|
-
resp, err := client.Do(req)
|
|
214
|
-
if err != nil {
|
|
209
|
+
func downloadFile(ctx context.Context, client Client, rawURL string, targetPath string) error {
|
|
210
|
+
var resp *http.Response
|
|
211
|
+
if err := client.SendRequest(ctx, rawURL, nil, &resp); err != nil {
|
|
215
212
|
return err
|
|
216
213
|
}
|
|
217
214
|
defer resp.Body.Close()
|
|
218
215
|
|
|
219
|
-
if resp.StatusCode >= 400 {
|
|
220
|
-
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, http.StatusText(resp.StatusCode))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
216
|
tmpPath := targetPath + ".tmp"
|
|
224
217
|
out, err := os.Create(tmpPath)
|
|
225
218
|
if err != nil {
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
package common
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"github.com/Pippit-dev/pippit-cli/internal/auth"
|
|
5
4
|
"github.com/Pippit-dev/pippit-cli/internal/config"
|
|
6
5
|
)
|
|
7
6
|
|
|
8
7
|
// Runner carries runtime dependencies for command execution.
|
|
9
8
|
type Runner struct {
|
|
10
|
-
Config
|
|
11
|
-
Client
|
|
12
|
-
AuthAuthorizer auth.Authorizer
|
|
9
|
+
Config *config.Config
|
|
10
|
+
Client Client
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
func NewRunner(cfg *config.Config, client Client
|
|
13
|
+
func NewRunner(cfg *config.Config, client Client) *Runner {
|
|
16
14
|
return &Runner{
|
|
17
|
-
Config:
|
|
18
|
-
Client:
|
|
19
|
-
AuthAuthorizer: authAuthorizer,
|
|
15
|
+
Config: cfg,
|
|
16
|
+
Client: client,
|
|
20
17
|
}
|
|
21
18
|
}
|