diff options
Diffstat (limited to 'lib/auth.go')
| -rw-r--r-- | lib/auth.go | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/lib/auth.go b/lib/auth.go new file mode 100644 index 0000000..b5bc06a --- /dev/null +++ b/lib/auth.go @@ -0,0 +1,111 @@ +package lib + +import ( + "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os/exec" + "runtime" + "strings" + "yato/config" +) + +func GetNewCodeVerifier() (string, error) { + bytes := make([]byte, 100) + _, err := rand.Read(bytes) + if err != nil { + return "", fmt.Errorf("failed to generate code verifier: %w", err) + } + + encoded := base64.RawURLEncoding.EncodeToString(bytes) + + // Truncate to 128 characters + if len(encoded) > 128 { + encoded = encoded[:128] + } + + return encoded, nil +} + +func GetOAuthURL(codeVerifier string) string { + return fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&code_challenge=%s&code_challenge_method=plain", + config.MALOAuthBaseURL, config.MALClientID, config.MALRedirectURI, codeVerifier) +} + +func OpenBrowser(url string) error { + var err error + + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform") + } + + return err +} + +func ExchangeToken(code, codeVerifier string) (*config.MyAnimeListConfig, error) { + data := url.Values{} + data.Set("grant_type", "authorization_code") + data.Set("client_id", config.MALClientID) + data.Set("client_secret", config.MALClientSecret) + data.Set("redirect_uri", config.MALRedirectURI) + data.Set("code", code) + + if codeVerifier != "" { + data.Set("code_verifier", codeVerifier) + } + + // POST request to MAL API + req, err := http.NewRequest("POST", "https://myanimelist.net/v1/oauth2/token", strings.NewReader(data.Encode())) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %w", err) + } + + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + var malConfig config.MyAnimeListConfig + var tokenResponse struct { + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + } + + if err := json.Unmarshal(body, &tokenResponse); err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + malConfig.TokenType = tokenResponse.TokenType + malConfig.ExpiresIn = tokenResponse.ExpiresIn + malConfig.AccessToken = tokenResponse.AccessToken + malConfig.RefreshToken = tokenResponse.RefreshToken + + return &malConfig, nil +} |
