nicolasparada / blog

Personal blog
https://nicolasparada.netlify.com/
ISC License
0 stars 1 forks source link

Building a Messenger App: OAuth #7

Open nicolasparada opened 6 years ago

nicolasparada commented 6 years ago

https://nicolasparada.netlify.com/posts/go-messenger-oauth/

Building a Messenger App: OAuth

nytro04 commented 6 years ago

Good stuff, keep it coming!

nicolasparada commented 6 years ago

@nytro04 Good stuff, keep it coming!

👍

joshvoll commented 6 years ago

how can i test only this part, without the UI

nicolasparada commented 6 years ago

@joshvoll how can i test only this parte, without the UI

Well isn't much of UI. First, I'd make those http handlers methods of a server struct and move db inside the struct. So it isn't global anymore.

type server struct {
  db *sql.DB
}

// handler creates an http.Handler with predefined routing.
func handler(db *sql.DB) http.Handler {
  s := &server{db: db}
  mux := http.NewServeMux()
  mux.HandleFunc("/some_path", s.someHandler)
  return mux
}

// someHandler handles requests on /some_path.
func (s *server) someHandler(w http.ResponseWriter, r *http.Request) {
  // Now you can access the database form s.db.
}

func main() {
  db, _ := sql.Open("postgres", "...")
  h := handler(db)
  http.ListenAndServe(":3000", h)
}

That way you can use a diferent db for testing.

nicolasparada commented 6 years ago

And goind beyond by separating the domain from the http transport layer. Something like:

type Service interface {
  GithubOAuthURL(state string) string
  UpsertGithubUser(ctx context.Context, code string) (User, error)
  Token(userID string) (string, time.Time, error)
}

// service should implement the Service interface.
type service struct {
  origin *url.URL
  db *sql.DB
  githubOAuth *oauth2.Config
  tokenSigner jwt.Signer
}

And make use of the service in the handlers. So the http handlers just decode requests and encode responses.

type handler struct {
  s Service
}

func Handler(s Service) http.Handler {
  h := &handler{s: s}
  r := way.NewRouter()
  r.HandleFunc("GET", "/api/oauth/github", h.startGithubOAuth)
  r.HandleFunc("GET", "/api/oauth/github/callback", h.endGithubOAuth)
  return r
}

func (h *handler) startGithubOAuth(w http.ResponseWriter, r *http.Request) {
  state, _ := gonanoid.Nanoid()
  // TODO: save state.
  http.Redirect(w, r, h.s.GithubOAuthURL(state), http.StatusFound)
}

func (h *handler) endGithubOAuth(w http.ResponseWriter, r *http.Request) {
  q := r.URL.Query()
  // TODO: check state and handle errors.
  ctx := r.Context()
  u, _ := h.s.UpsertGithubUser(ctx, q.Get("code"))
  t, exp, err := h.s.Token(u.ID)
  expiresAt, _ := exp.MarshalText()

  data := make(url.Values)
  data.Set("token", t)
  data.Set("expires_at", string(expiresAt))
  http.Redirect(w, r, "/callback?"+data.Encode(), http.StatusFound)
}

And you can easily test the service.

joshvoll commented 6 years ago

thanks a lot, well done