package chipmunk import ( "bytes" "encoding/json" "io" "io/ioutil" "log" "math/rand" "net/http" "strconv" "time" "github.com/gorilla/sessions" "golang.org/x/oauth2" "golang.org/x/oauth2/google" ) var store *sessions.CookieStore var ( oauthConf = &oauth2.Config{ ClientID: "", ClientSecret: "", RedirectURL: "http://127.0.0.1:8080/api/v0/oauth_cb/", Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, Endpoint: google.Endpoint, } oauthStateString = strconv.Itoa(rand.Int()) ) var Version string = "dev" var start time.Time var users []user var categories []category type failure struct { Success bool `json:"success"` Error string `json:"error"` } func NewFailure(msg string) *failure { return &failure{ Success: false, Error: msg, } } type Server struct { clientID string clientSecret string cookieSecret string db *DB } func init() { log.SetFlags(log.Ltime) start = time.Now() } func NewServer(sm *http.ServeMux, clientId, clientSecret, cookieSecret, dbhost, dbname, static string) (*Server, error) { db, err := NewDB(dbhost, dbname) if err != nil { return nil, err } db.db.SetMaxOpenConns(32) server := &Server{ clientID: clientId, clientSecret: clientSecret, cookieSecret: cookieSecret, db: db, } addRoutes(sm, server, static) return server, nil } func (s *Server) fakeSetup(w http.ResponseWriter, r *http.Request) { //u := userInfo{ // Email: "derekmcquay@gmail.com", //} //addUser(u) } //func (s *Server) tranx(w http.ResponseWriter, r *http.Request) { // //TODO add back in oauth // //w.Header().Set("Content-Type", "application/json") // //session, err := store.Get(r, "creds") // //if err != nil { // // http.Error(w, err.Error(), http.StatusInternalServerError) // // return // //} // //if loggedIn := session.Values["authenticated"]; loggedIn != true { // // http.Redirect(w, r, "/", http.StatusTemporaryRedirect) // // return // //} // switch r.Method { // default: // b, _ := json.Marshal(NewFailure("Allowed method: POST and GET")) // http.Error(w, string(b), http.StatusBadRequest) // return // case "GET": // u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session // if err != nil { // b, _ := json.Marshal(NewFailure(err.Error())) // http.Error(w, string(b), http.StatusInternalServerError) // return // } // json.NewEncoder(w).Encode(users[u].txs) // if err != nil { // http.Error(w, err.Error(), http.StatusInternalServerError) // return // } // case "POST": // u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session // if err != nil { // b, _ := json.Marshal(NewFailure(err.Error())) // http.Error(w, string(b), http.StatusInternalServerError) // return // } // // t := tranx{} // err = json.NewDecoder(r.Body).Decode(&t) // if err != nil { // http.Error(w, err.Error(), http.StatusBadRequest) // return // } // defer r.Body.Close() // // users[u].txs = append(users[u].txs, // tranx{ // Cost: t.Cost, // Store: t.Store, // Info: t.Info, // Month: t.Month, // }, // ) // } //} // //func (s *Server) costPerMonth(w http.ResponseWriter, r *http.Request) { // //TODO add back in oauth // //w.Header().Set("Content-Type", "application/json") // //session, err := store.Get(r, "creds") // //if err != nil { // // http.Error(w, err.Error(), http.StatusInternalServerError) // // return // //} // //if loggedIn := session.Values["authenticated"]; loggedIn != true { // // http.Redirect(w, r, "/", http.StatusTemporaryRedirect) // // return // //} // switch r.Method { // default: // b, _ := json.Marshal(NewFailure("Allowed method: GET")) // http.Error(w, string(b), http.StatusBadRequest) // return // case "GET": // u, err := getUser("derekmcquay@gmail.com") //TODO will grab this from session // if err != nil { // b, _ := json.Marshal(NewFailure(err.Error())) // http.Error(w, string(b), http.StatusInternalServerError) // return // } // monthCost := make(map[time.Month]float32) // for _, t := range users[u].txs { // c, ok := monthCost[t.Month] // if !ok { // monthCost[t.Month] = t.Cost // continue // } // monthCost[t.Month] = t.Cost + c // } // err = json.NewEncoder(w).Encode(monthCost) // if err != nil { // http.Error(w, err.Error(), http.StatusInternalServerError) // return // } // } //} func (s *Server) login(w http.ResponseWriter, r *http.Request) { session, _ := store.Get(r, "creds") if loggedIn := session.Values["authenticated"]; loggedIn == true { http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect) return } oauthConf.ClientID = s.clientID oauthConf.ClientSecret = s.clientSecret url := oauthConf.AuthCodeURL(oauthStateString, oauth2.AccessTypeOnline) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func (s *Server) oauthCallback(w http.ResponseWriter, r *http.Request) { state := r.FormValue("state") if state != oauthStateString { log.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } code := r.FormValue("code") token, err := oauthConf.Exchange(oauth2.NoContext, code) if err != nil { log.Printf("oauthConf.Exchange() failed with '%s'\n", err) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } oauthClient := oauthConf.Client(oauth2.NoContext, token) email, err := oauthClient.Get("https://www.googleapis.com/oauth2/v3/userinfo") if err != nil { log.Printf("failed with getting userinfo: '%s'\n", err) http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } defer email.Body.Close() data, _ := ioutil.ReadAll(email.Body) u := userInfo{} err = json.Unmarshal(data, &u) if err != nil { b, _ := json.Marshal(NewFailure(err.Error())) http.Error(w, string(b), http.StatusInternalServerError) return } if authorizedEmail(u.Email) { session, err := store.Get(r, "creds") if err != nil { if !session.IsNew { http.Error(w, err.Error(), http.StatusInternalServerError) return } } session.Values["authenticated"] = true session.Values["uname"] = u.Email if err := session.Save(r, w); err != nil { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) } // TODO add psql user here //addUser(u) http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect) return } b, _ := json.Marshal(NewFailure("Not a authorized user")) http.Error(w, string(b), http.StatusForbidden) return } func (s *Server) auth(w http.ResponseWriter, r *http.Request) { output := struct { Auth bool `json:"auth"` }{ Auth: false, } w.Header().Set("Content-Type", "application/json") session, err := store.Get(r, "creds") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if loggedIn := session.Values["authenticated"]; loggedIn == true { output.Auth = true json.NewEncoder(w).Encode(output) return } b, _ := json.Marshal(output) http.Error(w, string(b), http.StatusUnauthorized) } func (s *Server) logout(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "creds") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } delete(session.Values, "authenticated") delete(session.Values, "uname") session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } func (s *Server) serverInfo(w http.ResponseWriter, r *http.Request) { output := struct { Version string `json:"version"` Start string `json:"start"` Uptime string `json:"uptime"` }{ Version: Version, Start: start.Format("2006-01-02 15:04:05"), Uptime: time.Since(start).String(), } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(output) } func (s *Server) plist(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, "creds") if err != nil { if session.IsNew { http.Redirect(w, r, "/", http.StatusSeeOther) return } http.Error(w, err.Error(), http.StatusInternalServerError) return } if loggedIn := session.Values["authenticated"]; loggedIn != true { http.Redirect(w, r, "/", http.StatusTemporaryRedirect) return } data, err := Asset("static/list.html") if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } req := bytes.NewReader(data) io.Copy(w, req) } func (s *Server) health(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") io.WriteString(w, `{"alive": true}`) }