refactor: fold auth and crypto into deezer domain
This commit is contained in:
+26
-4
@@ -1,10 +1,14 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/auth"
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newLoginCmd() *cobra.Command {
|
func newLoginCmd() *cobra.Command {
|
||||||
@@ -13,16 +17,16 @@ func newLoginCmd() *cobra.Command {
|
|||||||
Short: "Log in to Deezer with your email and password",
|
Short: "Log in to Deezer with your email and password",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := auth.CheckGatewayEnv(); err != nil {
|
if err := deezer.CheckGatewayEnv(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
email, password, err := auth.PromptCredentials()
|
email, password, err := promptCredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, username, err := auth.Login(cmd.Context(), email, password)
|
_, username, err := deezer.Login(cmd.Context(), email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/auth"
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ func newLogoutCmd() *cobra.Command {
|
|||||||
Short: "Remove stored Deezer credentials",
|
Short: "Remove stored Deezer credentials",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := auth.Clear(); err != nil {
|
if err := deezer.ClearCredentials(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package crypto
|
package deezer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
@@ -13,7 +13,7 @@ var (
|
|||||||
blowfishSecretKey = []byte("g4el58wc0zvf9na1")
|
blowfishSecretKey = []byte("g4el58wc0zvf9na1")
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetBlowfishKey(trackID string) []byte {
|
func BlowfishKey(trackID string) []byte {
|
||||||
hash := md5.Sum([]byte(trackID))
|
hash := md5.Sum([]byte(trackID))
|
||||||
hashHex := hex.EncodeToString(hash[:])
|
hashHex := hex.EncodeToString(hash[:])
|
||||||
|
|
||||||
@@ -8,17 +8,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/auth"
|
|
||||||
"github.com/mathismqn/godeez/internal/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
Session *Session
|
Session *Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, appConfig *config.Config) (*Client, error) {
|
func NewClient(ctx context.Context, arlCookie string) (*Client, error) {
|
||||||
session, err := resolveSession(ctx, appConfig)
|
session, err := resolveSession(ctx, arlCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
||||||
}
|
}
|
||||||
@@ -28,14 +25,14 @@ func NewClient(ctx context.Context, appConfig *config.Config) (*Client, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveSession(ctx context.Context, appConfig *config.Config) (*Session, error) {
|
func resolveSession(ctx context.Context, arlCookie string) (*Session, error) {
|
||||||
if appConfig.ARLCookie != "" {
|
if arlCookie != "" {
|
||||||
return Authenticate(ctx, appConfig.ARLCookie)
|
return authenticate(ctx, arlCookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
var session *Session
|
var session *Session
|
||||||
validate := func(ctx context.Context, arl string) error {
|
validate := func(ctx context.Context, arl string) error {
|
||||||
s, err := Authenticate(ctx, arl)
|
s, err := authenticate(ctx, arl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -44,13 +41,13 @@ func resolveSession(ctx context.Context, appConfig *config.Config) (*Session, er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
arl, err := auth.Resolve(ctx, validate)
|
arl, err := resolveARL(ctx, validate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if session == nil {
|
if session == nil {
|
||||||
session, err = Authenticate(ctx, arl)
|
session, err = authenticate(ctx, arl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package auth
|
package deezer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -19,7 +19,7 @@ type Credentials struct {
|
|||||||
ARL string `json:"arl,omitempty"`
|
ARL string `json:"arl,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() (*Credentials, error) {
|
func LoadCredentials() (*Credentials, error) {
|
||||||
secret, err := keyring.Get(keyringService, keyringUser)
|
secret, err := keyring.Get(keyringService, keyringUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, keyring.ErrNotFound) {
|
if errors.Is(err, keyring.ErrNotFound) {
|
||||||
@@ -36,7 +36,7 @@ func Load() (*Credentials, error) {
|
|||||||
return &creds, nil
|
return &creds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Save(creds *Credentials) error {
|
func SaveCredentials(creds *Credentials) error {
|
||||||
data, err := json.Marshal(creds)
|
data, err := json.Marshal(creds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -49,7 +49,7 @@ func Save(creds *Credentials) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Clear() error {
|
func ClearCredentials() error {
|
||||||
if err := keyring.Delete(keyringService, keyringUser); err != nil {
|
if err := keyring.Delete(keyringService, keyringUser); err != nil {
|
||||||
if errors.Is(err, keyring.ErrNotFound) {
|
if errors.Is(err, keyring.ErrNotFound) {
|
||||||
return nil
|
return nil
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package crypto
|
package deezer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ZeroPad(data []byte) []byte {
|
func zeroPad(data []byte) []byte {
|
||||||
bs := aes.BlockSize
|
bs := aes.BlockSize
|
||||||
padded := make([]byte, len(data)+(bs-len(data)%bs)%bs)
|
padded := make([]byte, len(data)+(bs-len(data)%bs)%bs)
|
||||||
copy(padded, data)
|
copy(padded, data)
|
||||||
@@ -14,11 +14,11 @@ func ZeroPad(data []byte) []byte {
|
|||||||
return padded
|
return padded
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncryptECB(key, data []byte) ([]byte, error) {
|
func ecbEncrypt(key, data []byte) ([]byte, error) {
|
||||||
return ecbTransform(key, data, (cipher.Block).Encrypt)
|
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)
|
return ecbTransform(key, data, (cipher.Block).Decrypt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package auth
|
package deezer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -14,8 +14,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -72,7 +70,7 @@ func newMobileClient() (*mobileClient, error) {
|
|||||||
}, nil
|
}, 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)
|
token, tokenKey, userKey, err := m.authenticate(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -117,7 +115,7 @@ func (m *mobileClient) authenticate(ctx context.Context) (string, string, string
|
|||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypted, err := crypto.DecryptECB(m.gwKey, encrypted)
|
decrypted, err := ecbDecrypt(m.gwKey, encrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
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 {
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
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 {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package auth
|
package deezer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Resolve(ctx context.Context, validate func(ctx context.Context, arl string) error) (string, error) {
|
func resolveARL(ctx context.Context, validate func(ctx context.Context, arl string) error) (string, error) {
|
||||||
creds, err := Load()
|
creds, err := LoadCredentials()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -35,12 +35,12 @@ func Login(ctx context.Context, email, password string) (string, string, error)
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
creds, username, err := client.Login(ctx, email, password)
|
creds, username, err := client.login(ctx, email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Save(creds); err != nil {
|
if err := SaveCredentials(creds); err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ type Session struct {
|
|||||||
Premium bool
|
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)
|
jar, err := cookiejar.New(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/config"
|
"github.com/mathismqn/godeez/internal/config"
|
||||||
"github.com/mathismqn/godeez/internal/crypto"
|
|
||||||
"github.com/mathismqn/godeez/internal/deezer"
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
"github.com/mathismqn/godeez/internal/fileutil"
|
"github.com/mathismqn/godeez/internal/fileutil"
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
"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 {
|
func (c *Client) initDeezerClient(ctx context.Context, opts Options) error {
|
||||||
var err error
|
var err error
|
||||||
c.deezerClient, err = deezer.NewClient(ctx, c.appConfig)
|
c.deezerClient, err = deezer.NewClient(ctx, c.appConfig.ARLCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -155,7 +154,7 @@ func (c *Client) downloadTrack(ctx context.Context, resource deezer.Resource, tr
|
|||||||
fileName := track.Filename(c.kind, mediaFormat)
|
fileName := track.Filename(c.kind, mediaFormat)
|
||||||
outputPath := path.Join(outputDir, fileName)
|
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 {
|
if err := c.streamToFile(dlCtx, stream, outputPath, key); err != nil {
|
||||||
fileutil.DeleteFile(outputPath)
|
fileutil.DeleteFile(outputPath)
|
||||||
return downloadResult{err: fmt.Errorf("failed to stream to file: %w", err)}
|
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 {
|
if chunk%3 == 0 && totalRead == chunkSize {
|
||||||
buffer, err = crypto.DecryptBlowfish(buffer, key)
|
buffer, err = deezer.DecryptBlowfish(buffer, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user