package main import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "log" "net/http" "sync" "github.com/zalando/go-keyring" ) var simklJwt SimklJWT // simklKeyringService is the OS keyring service name all AniTrack secrets // live under (same wallet the 99designs/keyring build used). const simklKeyringService = "AniTrack" func simklRingSet(key string, data []byte) error { if err := keyring.Set(simklKeyringService, key, string(data)); err != nil { log.Printf("simkl: save %s failed: %s", key, err) return err } return nil } var simklCtxShutdown, simklCancel = context.WithCancel(context.Background()) func (a *App) CheckIfSimklLoggedIn() bool { if (SimklJWT{} == simklJwt) { tokenType, tokenTypeErr := keyring.Get(simklKeyringService, "SimklTokenType") accessToken, accessTokenErr := keyring.Get(simklKeyringService, "SimklAccessToken") scope, scopeErr := keyring.Get(simklKeyringService, "SimklScope") if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken) == 0 { return false } else { simklJwt.TokenType = tokenType simklJwt.AccessToken = accessToken simklJwt.Scope = scope return true } } else { return true } } func (a *App) SimklLogin() { if !a.CheckIfSimklLoggedIn() { tokenType, tokenTypeErr := keyring.Get(simklKeyringService, "SimklTokenType") accessToken, accessTokenErr := keyring.Get(simklKeyringService, "SimklAccessToken") scope, scopeErr := keyring.Get(simklKeyringService, "SimklScope") if (tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil) || len(accessToken) == 0 { getSimklCodeUrl := "https://simkl.com/oauth/authorize?response_type=code&client_id=" + Environment.SIMKL_CLIENT_ID + "&redirect_uri=" + Environment.SIMKL_CALLBACK_URI if err := a.app.Browser.OpenURL(getSimklCodeUrl); err != nil { log.Printf("simkl: failed to open browser: %s", err) return } serverDone := &sync.WaitGroup{} serverDone.Add(1) a.handleSimklCallback(serverDone) serverDone.Wait() } else { simklJwt.TokenType = tokenType simklJwt.AccessToken = accessToken simklJwt.Scope = scope } } } func (a *App) handleSimklCallback(wg *sync.WaitGroup) { mux := http.NewServeMux() srv := &http.Server{Addr: ":6734", Handler: mux} mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { select { case <-simklCtxShutdown.Done(): fmt.Println("Shutting down...") return default: } content := r.FormValue("code") if content != "" { simklJwt = getSimklAuthorizationToken(content) _ = simklRingSet("SimklTokenType", []byte(simklJwt.TokenType)) _ = simklRingSet("SimklAccessToken", []byte(simklJwt.AccessToken)) _ = simklRingSet("SimklScope", []byte(simklJwt.Scope)) a.app.Dialog.Info(). SetTitle("Simkl Authorization"). SetMessage("It is now safe to close your browser tab"). Show() fmt.Println("Shutting down...") simklCancel() if err := srv.Shutdown(context.Background()); err != nil { log.Println("server.Shutdown:", err) } } else { _, err := fmt.Fprintf(w, "Getting code failed.") if err != nil { return } } }) go func() { defer wg.Done() if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Printf("Server error: %s\n", err) } fmt.Println("Shutting down...") }() } func getSimklAuthorizationToken(content string) SimklJWT { data := struct { GrantType string `json:"grant_type"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` RedirectURI string `json:"redirect_uri"` Code string `json:"code"` }{ GrantType: "authorization_code", ClientID: Environment.SIMKL_CLIENT_ID, ClientSecret: Environment.SIMKL_CLIENT_SECRET, RedirectURI: Environment.SIMKL_CALLBACK_URI, Code: content, } jsonData, err := json.Marshal(data) if err != nil { log.Printf("Failed to marshal data: %s\n", err) return SimklJWT{} } response, err := http.NewRequest("POST", "https://api.simkl.com/oauth/token", bytes.NewBuffer(jsonData)) if err != nil { log.Printf("Failed at response, %s\n", err) } response.Header.Add("Content-Type", "application/json") client := &http.Client{} res, resErr := client.Do(response) if resErr != nil { log.Printf("Failed at res, %s\n", err) } defer res.Body.Close() returnedBody, err := io.ReadAll(res.Body) if err != nil { log.Printf("Could not read returned body, %s\n.", err) } var post SimklJWT err = json.Unmarshal(returnedBody, &post) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } return post } func (a *App) GetSimklLoggedInUser() SimklUser { a.SimklLogin() client := &http.Client{} req, _ := http.NewRequest("POST", "https://api.simkl.com/users/settings", nil) req.Header.Add("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+simklJwt.AccessToken) req.Header.Add("simkl-api-key", Environment.SIMKL_CLIENT_ID) response, err := client.Do(req) if err != nil { log.Printf("Failed at request, %s\n", err) return SimklUser{} } defer response.Body.Close() respBody, _ := io.ReadAll(response.Body) var errCheck struct { Error string `json:"error"` Message string `json:"message"` } err = json.Unmarshal(respBody, &errCheck) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } if errCheck.Error != "" { a.LogoutSimkl() return SimklUser{} } var user SimklUser err = json.Unmarshal(respBody, &user) if err != nil { log.Printf("Failed at unmarshal, %s\n", err) } return user } func (a *App) LogoutSimkl() string { if (SimklJWT{} != simklJwt) { tokenTypeErr := keyring.Delete(simklKeyringService, "SimklTokenType") accessTokenErr := keyring.Delete(simklKeyringService, "SimklAccessToken") scopeErr := keyring.Delete(simklKeyringService, "SimklScope") if tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil { log.Printf("simkl: logout cleanup failed (type=%v access=%v scope=%v)", tokenTypeErr, accessTokenErr, scopeErr) } simklJwt = SimklJWT{} } return "Simkl Logged Out Successfully" }