diff --git a/cmd/login.go b/cmd/login.go index 8f266be..3c153fd 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -1,10 +1,14 @@ package cmd import ( + "bufio" "fmt" + "os" + "strings" - "github.com/mathismqn/godeez/internal/auth" + "github.com/mathismqn/godeez/internal/deezer" "github.com/spf13/cobra" + "golang.org/x/term" ) func newLoginCmd() *cobra.Command { @@ -13,16 +17,16 @@ func newLoginCmd() *cobra.Command { Short: "Log in to Deezer with your email and password", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if err := auth.CheckGatewayEnv(); err != nil { + if err := deezer.CheckGatewayEnv(); err != nil { return err } - email, password, err := auth.PromptCredentials() + email, password, err := promptCredentials() if err != nil { return err } - _, username, err := auth.Login(cmd.Context(), email, password) + _, username, err := deezer.Login(cmd.Context(), email, password) if err != nil { return err } @@ -33,3 +37,21 @@ func newLoginCmd() *cobra.Command { }, } } + +func promptCredentials() (string, string, error) { + fmt.Print("Email: ") + line, err := bufio.NewReader(os.Stdin).ReadString('\n') + if err != nil { + return "", "", err + } + email := strings.TrimSpace(line) + + fmt.Print("Password: ") + passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd())) + fmt.Println() + if err != nil { + return "", "", err + } + + return email, string(passwordBytes), nil +} diff --git a/cmd/logout.go b/cmd/logout.go index e3829b6..f3de877 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/mathismqn/godeez/internal/auth" + "github.com/mathismqn/godeez/internal/deezer" "github.com/spf13/cobra" ) @@ -13,7 +13,7 @@ func newLogoutCmd() *cobra.Command { Short: "Remove stored Deezer credentials", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - if err := auth.Clear(); err != nil { + if err := deezer.ClearCredentials(); err != nil { return err } diff --git a/internal/auth/prompt.go b/internal/auth/prompt.go deleted file mode 100644 index 72e4dc1..0000000 --- a/internal/auth/prompt.go +++ /dev/null @@ -1,28 +0,0 @@ -package auth - -import ( - "bufio" - "fmt" - "os" - "strings" - - "golang.org/x/term" -) - -func PromptCredentials() (string, string, error) { - fmt.Print("Email: ") - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return "", "", err - } - email := strings.TrimSpace(line) - - fmt.Print("Password: ") - passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Println() - if err != nil { - return "", "", err - } - - return email, string(passwordBytes), nil -} diff --git a/internal/crypto/blowfish.go b/internal/deezer/blowfish.go similarity index 92% rename from internal/crypto/blowfish.go rename to internal/deezer/blowfish.go index e0d3088..5f6eb86 100644 --- a/internal/crypto/blowfish.go +++ b/internal/deezer/blowfish.go @@ -1,4 +1,4 @@ -package crypto +package deezer import ( "crypto/cipher" @@ -13,7 +13,7 @@ var ( blowfishSecretKey = []byte("g4el58wc0zvf9na1") ) -func GetBlowfishKey(trackID string) []byte { +func BlowfishKey(trackID string) []byte { hash := md5.Sum([]byte(trackID)) hashHex := hex.EncodeToString(hash[:]) diff --git a/internal/deezer/client.go b/internal/deezer/client.go index b316896..484e1e2 100644 --- a/internal/deezer/client.go +++ b/internal/deezer/client.go @@ -8,17 +8,14 @@ import ( "io" "net/http" "strings" - - "github.com/mathismqn/godeez/internal/auth" - "github.com/mathismqn/godeez/internal/config" ) type Client struct { Session *Session } -func NewClient(ctx context.Context, appConfig *config.Config) (*Client, error) { - session, err := resolveSession(ctx, appConfig) +func NewClient(ctx context.Context, arlCookie string) (*Client, error) { + session, err := resolveSession(ctx, arlCookie) if err != nil { return nil, fmt.Errorf("failed to authenticate: %w", err) } @@ -28,14 +25,14 @@ func NewClient(ctx context.Context, appConfig *config.Config) (*Client, error) { }, nil } -func resolveSession(ctx context.Context, appConfig *config.Config) (*Session, error) { - if appConfig.ARLCookie != "" { - return Authenticate(ctx, appConfig.ARLCookie) +func resolveSession(ctx context.Context, arlCookie string) (*Session, error) { + if arlCookie != "" { + return authenticate(ctx, arlCookie) } var session *Session validate := func(ctx context.Context, arl string) error { - s, err := Authenticate(ctx, arl) + s, err := authenticate(ctx, arl) if err != nil { return err } @@ -44,13 +41,13 @@ func resolveSession(ctx context.Context, appConfig *config.Config) (*Session, er return nil } - arl, err := auth.Resolve(ctx, validate) + arl, err := resolveARL(ctx, validate) if err != nil { return nil, err } if session == nil { - session, err = Authenticate(ctx, arl) + session, err = authenticate(ctx, arl) if err != nil { return nil, err } diff --git a/internal/auth/storage.go b/internal/deezer/credentials.go similarity index 88% rename from internal/auth/storage.go rename to internal/deezer/credentials.go index 627d7b2..b8ff7a4 100644 --- a/internal/auth/storage.go +++ b/internal/deezer/credentials.go @@ -1,4 +1,4 @@ -package auth +package deezer import ( "encoding/json" @@ -19,7 +19,7 @@ type Credentials struct { ARL string `json:"arl,omitempty"` } -func Load() (*Credentials, error) { +func LoadCredentials() (*Credentials, error) { secret, err := keyring.Get(keyringService, keyringUser) if err != nil { if errors.Is(err, keyring.ErrNotFound) { @@ -36,7 +36,7 @@ func Load() (*Credentials, error) { return &creds, nil } -func Save(creds *Credentials) error { +func SaveCredentials(creds *Credentials) error { data, err := json.Marshal(creds) if err != nil { return err @@ -49,7 +49,7 @@ func Save(creds *Credentials) error { return nil } -func Clear() error { +func ClearCredentials() error { if err := keyring.Delete(keyringService, keyringUser); err != nil { if errors.Is(err, keyring.ErrNotFound) { return nil diff --git a/internal/crypto/ecb.go b/internal/deezer/ecb.go similarity index 82% rename from internal/crypto/ecb.go rename to internal/deezer/ecb.go index 14c994c..921f63c 100644 --- a/internal/crypto/ecb.go +++ b/internal/deezer/ecb.go @@ -1,4 +1,4 @@ -package crypto +package deezer import ( "crypto/aes" @@ -6,7 +6,7 @@ import ( "fmt" ) -func ZeroPad(data []byte) []byte { +func zeroPad(data []byte) []byte { bs := aes.BlockSize padded := make([]byte, len(data)+(bs-len(data)%bs)%bs) copy(padded, data) @@ -14,11 +14,11 @@ func ZeroPad(data []byte) []byte { return padded } -func EncryptECB(key, data []byte) ([]byte, error) { +func ecbEncrypt(key, data []byte) ([]byte, error) { return ecbTransform(key, data, (cipher.Block).Encrypt) } -func DecryptECB(key, data []byte) ([]byte, error) { +func ecbDecrypt(key, data []byte) ([]byte, error) { return ecbTransform(key, data, (cipher.Block).Decrypt) } diff --git a/internal/auth/mobile.go b/internal/deezer/gateway.go similarity index 94% rename from internal/auth/mobile.go rename to internal/deezer/gateway.go index 0e86f81..73f5ddd 100644 --- a/internal/auth/mobile.go +++ b/internal/deezer/gateway.go @@ -1,4 +1,4 @@ -package auth +package deezer import ( "bytes" @@ -14,8 +14,6 @@ import ( "os" "strings" "time" - - "github.com/mathismqn/godeez/internal/crypto" ) const ( @@ -72,7 +70,7 @@ func newMobileClient() (*mobileClient, error) { }, nil } -func (m *mobileClient) Login(ctx context.Context, email, password string) (*Credentials, string, error) { +func (m *mobileClient) login(ctx context.Context, email, password string) (*Credentials, string, error) { token, tokenKey, userKey, err := m.authenticate(ctx) if err != nil { return nil, "", err @@ -117,7 +115,7 @@ func (m *mobileClient) authenticate(ctx context.Context) (string, string, string return "", "", "", err } - decrypted, err := crypto.DecryptECB(m.gwKey, encrypted) + decrypted, err := ecbDecrypt(m.gwKey, encrypted) if err != nil { return "", "", "", err } @@ -130,7 +128,7 @@ func (m *mobileClient) authenticate(ctx context.Context) (string, string, string } func (m *mobileClient) checkToken(ctx context.Context, token, tokenKey string) error { - encrypted, err := crypto.EncryptECB([]byte(tokenKey), []byte(token)) + encrypted, err := ecbEncrypt([]byte(tokenKey), []byte(token)) if err != nil { return err } @@ -156,7 +154,7 @@ func (m *mobileClient) checkToken(ctx context.Context, token, tokenKey string) e } func (m *mobileClient) userAuth(ctx context.Context, email, password, userKey string) (string, string, error) { - encryptedPassword, err := crypto.EncryptECB([]byte(userKey), crypto.ZeroPad([]byte(password))) + encryptedPassword, err := ecbEncrypt([]byte(userKey), zeroPad([]byte(password))) if err != nil { return "", "", err } diff --git a/internal/auth/auth.go b/internal/deezer/login.go similarity index 75% rename from internal/auth/auth.go rename to internal/deezer/login.go index 88ac6e2..4b7fecd 100644 --- a/internal/auth/auth.go +++ b/internal/deezer/login.go @@ -1,12 +1,12 @@ -package auth +package deezer import ( "context" "fmt" ) -func Resolve(ctx context.Context, validate func(ctx context.Context, arl string) error) (string, error) { - creds, err := Load() +func resolveARL(ctx context.Context, validate func(ctx context.Context, arl string) error) (string, error) { + creds, err := LoadCredentials() if err != nil { return "", err } @@ -35,12 +35,12 @@ func Login(ctx context.Context, email, password string) (string, string, error) return "", "", err } - creds, username, err := client.Login(ctx, email, password) + creds, username, err := client.login(ctx, email, password) if err != nil { return "", "", err } - if err := Save(creds); err != nil { + if err := SaveCredentials(creds); err != nil { return "", "", err } diff --git a/internal/deezer/session.go b/internal/deezer/session.go index adaee54..e00c86e 100644 --- a/internal/deezer/session.go +++ b/internal/deezer/session.go @@ -17,7 +17,7 @@ type Session struct { Premium bool } -func Authenticate(ctx context.Context, arlCookie string) (*Session, error) { +func authenticate(ctx context.Context, arlCookie string) (*Session, error) { jar, err := cookiejar.New(nil) if err != nil { return nil, err diff --git a/internal/downloader/client.go b/internal/downloader/client.go index d6620a7..7be72a5 100644 --- a/internal/downloader/client.go +++ b/internal/downloader/client.go @@ -12,7 +12,6 @@ import ( "time" "github.com/mathismqn/godeez/internal/config" - "github.com/mathismqn/godeez/internal/crypto" "github.com/mathismqn/godeez/internal/deezer" "github.com/mathismqn/godeez/internal/fileutil" "github.com/mathismqn/godeez/internal/store" @@ -55,7 +54,7 @@ func (c *Client) Run(ctx context.Context, opts Options, id string) error { func (c *Client) initDeezerClient(ctx context.Context, opts Options) error { var err error - c.deezerClient, err = deezer.NewClient(ctx, c.appConfig) + c.deezerClient, err = deezer.NewClient(ctx, c.appConfig.ARLCookie) if err != nil { return err } @@ -155,7 +154,7 @@ func (c *Client) downloadTrack(ctx context.Context, resource deezer.Resource, tr fileName := track.Filename(c.kind, mediaFormat) outputPath := path.Join(outputDir, fileName) - key := crypto.GetBlowfishKey(track.ID) + key := deezer.BlowfishKey(track.ID) if err := c.streamToFile(dlCtx, stream, outputPath, key); err != nil { fileutil.DeleteFile(outputPath) return downloadResult{err: fmt.Errorf("failed to stream to file: %w", err)} @@ -213,7 +212,7 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP } if chunk%3 == 0 && totalRead == chunkSize { - buffer, err = crypto.DecryptBlowfish(buffer, key) + buffer, err = deezer.DecryptBlowfish(buffer, key) if err != nil { return err }