package auth import ( "encoding/json" "io/ioutil" "log" "net/http" "github.com/alexedwards/scs/v2" "golang.org/x/oauth2" ) var ( profileURL = "https://www.googleapis.com/oauth2/v3/userinfo" ) // Handler authentication handler // handles, login, oauth and registration type Handler interface { Login(w http.ResponseWriter, r *http.Request) OauthCallback(w http.ResponseWriter, r *http.Request) } type authHandler struct { service Servicer ses *scs.SessionManager oclient *oauth2.Config } // NewHandler create new instance of handler // Servicer, session, and oauth client required func NewHandler(service Servicer, session *scs.SessionManager, oclient *oauth2.Config) Handler { return &authHandler{ service, session, oclient, } } func (h *authHandler) Login(w http.ResponseWriter, r *http.Request) { stateValue, err := h.service.GenerateStateToken() if err != nil { return } h.ses.Put(r.Context(), "state", stateValue) url := h.oclient.AuthCodeURL(stateValue, oauth2.AccessTypeOnline) http.Redirect(w, r, url, 302) } func (h *authHandler) OauthCallback(w http.ResponseWriter, r *http.Request) { // this needs to be random stateFromSession := h.ses.GetString(r.Context(), "state") stateFromCallback := r.FormValue("state") // prevent arbitrary authorization exchanges if stateFromCallback != stateFromSession { http.Error(w, "state value miss match bad data", 400) return } stateValid, err := h.service.ValidateStateToken(stateFromCallback, stateFromSession) if err != nil { http.Error(w, "error validating", 400) return } if !stateValid { http.Error(w, "invalid tokens", 400) return } // valid and same as state code := r.FormValue("code") token, err := h.oclient.Exchange(r.Context(), code) if err != nil { http.Error(w, err.Error(), 400) return } log.Printf("returned token %v", token) // google profile client := h.oclient.Client(r.Context(), token) resp, err := client.Get(profileURL) if err != nil { http.Error(w, err.Error(), 400) return } defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { http.Error(w, err.Error(), 400) return } gp := &GoogleAuthProfile{} err = json.Unmarshal(data, &gp) profileID, newProfile, err := h.service.LoginOrRegisterSessionID(token, gp) if err != nil { http.Error(w, err.Error(), 400) return } h.ses.Put(r.Context(), "profid", profileID) // send to registration if newProfile == true { http.Redirect(w, r, "/account/register", 302) return } http.Redirect(w, r, "/", 302) }