Skip to content

Commit

Permalink
Refactor API interfaces (#185)
Browse files Browse the repository at this point in the history
- Move logging to command level.
- Simplify and unify interfaces for `appstore` package.
- Move sinf replication from "Download" API to "ReplicateSinf" API.
- Handle retries and command specific logic in the command level.
- Rename "Info" API to "AccountInfo".
  • Loading branch information
majd authored May 27, 2023
1 parent cf926d7 commit 95c3aa6
Show file tree
Hide file tree
Showing 74 changed files with 1,788 additions and 3,106 deletions.
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
with:
go-version: '1.19.3'
cache: true
- run: go generate github.com/majd/ipatool/...
- run: go test -v github.com/majd/ipatool/...
build:
name: Build
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ jobs:
with:
go-version: '1.19.3'
cache: true
- run: go generate github.com/majd/ipatool/...
- run: go test -v github.com/majd/ipatool/...
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.AppleDouble
.LSOverride
.vscode/
.idea/
.idea/
**/*_mock.go
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,10 @@ The tool can be compiled using the Go toolchain.
$ go build -o ipatool
```

Unit tests can be executed with the following command.
Unit tests can be executed with the following commands.

```shell
$ go generate github.com/majd/ipatool/...
$ go test -v github.com/majd/ipatool/...
```

Expand Down
128 changes: 88 additions & 40 deletions cmd/auth.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package cmd

import (
"bufio"
"errors"
"fmt"
"github.com/99designs/keyring"
"github.com/avast/retry-go"
"github.com/majd/ipatool/pkg/appstore"
"github.com/majd/ipatool/pkg/log"
"github.com/pkg/errors"
"github.com/majd/ipatool/pkg/util"
"github.com/spf13/cobra"
"golang.org/x/term"
"os"
"strings"
"time"
)

var keychainPassphrase string

func authCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "auth",
Expand All @@ -28,6 +33,19 @@ func authCmd() *cobra.Command {
}

func loginCmd() *cobra.Command {
promptForAuthCode := func() (string, error) {
reader := bufio.NewReader(os.Stdin)
authCode, err := reader.ReadString('\n')
if err != nil {
return "", err
}

authCode = strings.Trim(authCode, "\n")
authCode = strings.Trim(authCode, "\r")

return authCode, nil
}

var email string
var password string
var authCode string
Expand All @@ -36,29 +54,72 @@ func loginCmd() *cobra.Command {
Use: "login",
Short: "Login to the App Store",
RunE: func(cmd *cobra.Command, args []string) error {
store, err := newAppStore(cmd, keychainPassphrase)
if err != nil {
return errors.Wrap(err, "failed to create appstore client")
interactive := cmd.Context().Value("interactive").(bool)

if password == "" && !interactive {
return errors.New("password is required when not running in interactive mode; use the \"--password\" flag")
}

logger := cmd.Context().Value("logger").(log.Logger)
out, err := store.Login(email, password, authCode)
if err != nil {
if err == appstore.ErrAuthCodeRequired {
logger.Log().Msg("2FA code is required; run the command again and supply a code using the `--auth-code` flag")
return nil
}
if password == "" && interactive {
dependencies.Logger.Log().Msg("enter password:")

return err
bytes, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = string(bytes)
}

logger.Log().
Str("name", out.Name).
Str("email", out.Email).
Bool("success", true).
Send()
var lastErr error

return nil
return retry.Do(func() error {
if errors.Is(lastErr, appstore.ErrAuthCodeRequired) && interactive {
dependencies.Logger.Log().Msg("enter 2FA code:")

var err error
authCode, err = promptForAuthCode()
if err != nil {
return fmt.Errorf("failed to read auth code: %w", err)
}
}

dependencies.Logger.Verbose().
Str("password", password).
Str("email", email).
Str("authCode", util.IfEmpty(authCode, "<nil>")).
Msg("logging in")

output, err := dependencies.AppStore.Login(appstore.LoginInput{
Email: email,
Password: password,
AuthCode: authCode,
})
if err != nil {
if errors.Is(err, appstore.ErrAuthCodeRequired) && !interactive {
dependencies.Logger.Log().Msg("2FA code is required; run the command again and supply a code using the `--auth-code` flag")
return nil
}

return err
}

dependencies.Logger.Log().
Str("name", output.Account.Name).
Str("email", output.Account.Email).
Bool("success", true).
Send()

return nil
},
retry.LastErrorOnly(true),
retry.DelayType(retry.FixedDelay),
retry.Delay(time.Millisecond),
retry.Attempts(2),
retry.RetryIf(func(err error) bool {
lastErr = err
return errors.Is(err, appstore.ErrAuthCodeRequired)
}),
)
},
}

Expand All @@ -76,20 +137,14 @@ func infoCmd() *cobra.Command {
Use: "info",
Short: "Show current account info",
RunE: func(cmd *cobra.Command, args []string) error {
appstore, err := newAppStore(cmd, keychainPassphrase)
if err != nil {
return errors.Wrap(err, "failed to create appstore client")
}

out, err := appstore.Info()
output, err := dependencies.AppStore.AccountInfo()
if err != nil {
return err
}

logger := cmd.Context().Value("logger").(log.Logger)
logger.Log().
Str("name", out.Name).
Str("email", out.Email).
dependencies.Logger.Log().
Str("name", output.Account.Name).
Str("email", output.Account.Email).
Bool("success", true).
Send()

Expand All @@ -103,19 +158,12 @@ func revokeCmd() *cobra.Command {
Use: "revoke",
Short: "Revoke your App Store credentials",
RunE: func(cmd *cobra.Command, args []string) error {
appstore, err := newAppStore(cmd, keychainPassphrase)
if err != nil {
return errors.Wrap(err, "failed to create appstore client")
}

err = appstore.Revoke()
err := dependencies.AppStore.Revoke()
if err != nil {
return err
}

logger := cmd.Context().Value("logger").(log.Logger)
logger.Log().Bool("success", true).Send()

dependencies.Logger.Log().Bool("success", true).Send()
return nil
},
}
Expand Down
Loading

0 comments on commit 95c3aa6

Please sign in to comment.