package httpserver import ( "context" "log" "net/http" "path/filepath" "text/template" "git.netflux.io/rob/elon-eats-my-tweets/config" "github.com/labstack/echo/v4" "golang.org/x/oauth2" ) type handler struct { oauth2Config *oauth2.Config } func newHandler(cfg config.Config) *handler { return &handler{ &oauth2.Config{ ClientID: cfg.Twitter.ClientID, ClientSecret: cfg.Twitter.ClientSecret, RedirectURL: cfg.Twitter.CallbackURL, Scopes: []string{"tweet.read", "tweet.write", "users.read", "offline.access"}, Endpoint: oauth2.Endpoint{ AuthURL: "https://twitter.com/i/oauth2/authorize", TokenURL: "https://api.twitter.com/2/oauth2/token", }, }, } } func (h *handler) getIndex(c echo.Context) error { return c.Render(http.StatusOK, "index", nil) } func (h *handler) getLogin(c echo.Context) error { url := h.oauth2Config.AuthCodeURL( // TODO: implement state and code_challenge tokens "state", oauth2.SetAuthURLParam("code_challenge", "challenge"), oauth2.SetAuthURLParam("code_challenge_method", "plain"), ) return c.Redirect(http.StatusTemporaryRedirect, url) } func (h *handler) getCallback(c echo.Context) error { code := c.QueryParam("code") if code == "" { return echo.NewHTTPError(http.StatusBadRequest, "empty code") } _, err := h.oauth2Config.Exchange(context.Background(), code, oauth2.SetAuthURLParam("code_verifier", "challenge")) if err != nil { log.Printf("error exchanging code: %v", err) return echo.NewHTTPError(http.StatusInternalServerError, "error exchanging code") } return c.String(http.StatusOK, "ok") } func Start(cfg config.Config) error { e := echo.New() e.Renderer = &Template{ templates: template.Must(template.ParseGlob(filepath.Join(cfg.PublicPath, "views/", "*.html"))), } h := newHandler(cfg) e.GET("/", h.getIndex) e.GET("/login", h.getLogin) e.GET("/callback", h.getCallback) return e.Start(":8000") }