package auth import ( "encoding/json" "io/ioutil" "net/http" "github.com/alexedwards/scs/v2" "golang.org/x/oauth2" "git.ofmax.li/iserv/internal/goog" ) // 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 { svc Servicer ses *scs.SessionManager } // TODO migrate to Goog // NewHandler create new instance of handler // Servicer, session, and oauth client required func NewHandler(service Servicer, session *scs.SessionManager) Handler { return &authHandler{ service, session, } } func (h *authHandler) Login(w http.ResponseWriter, r *http.Request) { stateValue, err := h.svc.GenerateStateToken() if err != nil { return } h.ses.Put(r.Context(), "state", stateValue) url := h.svc.Goog().Config().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.svc.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.svc.Goog().Config().Exchange(r.Context(), code) if err != nil { http.Error(w, err.Error(), 400) return } // google profile client := h.svc.Goog().UserClient(r.Context(), token) resp, err := client.Get(goog.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 := &goog.GoogleProfile{} err = json.Unmarshal(data, &gp) profileID, newProfile, err := h.svc.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, "/u/register", 302) return } http.Redirect(w, r, "/", 302) return }