2016-08-21 22:31:37 -07:00
|
|
|
package chipmunk
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2016-08-23 11:11:25 -07:00
|
|
|
"math/rand"
|
2016-08-21 22:31:37 -07:00
|
|
|
"net/http"
|
2016-08-23 11:11:25 -07:00
|
|
|
"strconv"
|
2016-08-21 22:31:37 -07:00
|
|
|
"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,
|
|
|
|
}
|
2016-08-23 11:11:25 -07:00
|
|
|
oauthStateString = strconv.Itoa(rand.Int())
|
2016-08-21 22:31:37 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
var Version string = "dev"
|
|
|
|
var start time.Time
|
2016-08-22 15:17:25 -07:00
|
|
|
var users []user
|
2017-01-20 14:22:01 -08:00
|
|
|
var categories []category
|
2016-08-22 13:03:23 -07:00
|
|
|
|
2016-08-21 22:31:37 -07:00
|
|
|
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 {
|
2017-02-04 19:48:13 -08:00
|
|
|
clientID string
|
|
|
|
clientSecret string
|
|
|
|
cookieSecret string
|
|
|
|
db *DB
|
2016-08-21 22:31:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
log.SetFlags(log.Ltime)
|
|
|
|
start = time.Now()
|
|
|
|
}
|
|
|
|
|
2017-02-04 19:48:13 -08:00
|
|
|
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)
|
|
|
|
|
2016-08-21 22:31:37 -07:00
|
|
|
server := &Server{
|
2017-02-04 19:48:13 -08:00
|
|
|
clientID: clientId,
|
|
|
|
clientSecret: clientSecret,
|
|
|
|
cookieSecret: cookieSecret,
|
|
|
|
db: db,
|
2016-08-21 22:31:37 -07:00
|
|
|
}
|
|
|
|
addRoutes(sm, server, static)
|
2017-02-04 19:48:13 -08:00
|
|
|
return server, nil
|
2016-08-21 22:31:37 -07:00
|
|
|
}
|
|
|
|
|
2016-08-22 15:17:25 -07:00
|
|
|
func (s *Server) fakeSetup(w http.ResponseWriter, r *http.Request) {
|
2017-02-04 23:24:38 -08:00
|
|
|
//u := userInfo{
|
|
|
|
// Email: "derekmcquay@gmail.com",
|
|
|
|
//}
|
|
|
|
//addUser(u)
|
2016-08-22 15:17:25 -07:00
|
|
|
}
|
|
|
|
|
2017-02-04 19:48:13 -08:00
|
|
|
//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) listUsers(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":
|
|
|
|
// err := json.NewEncoder(w).Encode(users)
|
|
|
|
// if err != nil {
|
|
|
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//}
|
|
|
|
//
|
2016-08-22 21:16:20 -07:00
|
|
|
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
2016-08-25 13:08:19 -07:00
|
|
|
session, _ := store.Get(r, "creds")
|
|
|
|
if loggedIn := session.Values["authenticated"]; loggedIn == true {
|
|
|
|
http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
2017-02-04 19:48:13 -08:00
|
|
|
oauthConf.ClientID = s.clientID
|
|
|
|
oauthConf.ClientSecret = s.clientSecret
|
2016-08-22 21:16:20 -07:00
|
|
|
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 {
|
2016-08-25 11:26:49 -07:00
|
|
|
if !session.IsNew {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2016-08-22 21:16:20 -07:00
|
|
|
}
|
|
|
|
session.Values["authenticated"] = true
|
|
|
|
session.Values["uname"] = u.Email
|
|
|
|
if err := session.Save(r, w); err != nil {
|
|
|
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
|
|
|
}
|
2017-02-04 23:24:38 -08:00
|
|
|
// TODO add psql user here
|
|
|
|
//addUser(u)
|
2016-08-22 21:16:20 -07:00
|
|
|
http.Redirect(w, r, "/static/", http.StatusTemporaryRedirect)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b, _ := json.Marshal(NewFailure("Not a authorized user"))
|
|
|
|
http.Error(w, string(b), http.StatusForbidden)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-21 22:31:37 -07:00
|
|
|
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")
|
2016-08-22 21:16:20 -07:00
|
|
|
session, err := store.Get(r, "creds")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2016-08-21 22:31:37 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2016-08-25 11:26:49 -07:00
|
|
|
func (s *Server) logout(w http.ResponseWriter, r *http.Request) {
|
|
|
|
session, err := store.Get(r, "creds")
|
2016-08-22 21:16:20 -07:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2016-08-21 22:31:37 -07:00
|
|
|
delete(session.Values, "authenticated")
|
|
|
|
delete(session.Values, "uname")
|
2016-08-25 11:26:49 -07:00
|
|
|
session.Save(r, w)
|
|
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
2016-08-21 22:31:37 -07:00
|
|
|
}
|
|
|
|
|
2016-08-25 11:26:49 -07:00
|
|
|
func (s *Server) serverInfo(w http.ResponseWriter, r *http.Request) {
|
2016-08-21 22:31:37 -07:00
|
|
|
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) {
|
2016-08-22 21:16:20 -07:00
|
|
|
session, err := store.Get(r, "creds")
|
|
|
|
if err != nil {
|
2016-08-25 11:26:49 -07:00
|
|
|
if session.IsNew {
|
|
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
|
|
return
|
|
|
|
}
|
2016-08-22 21:16:20 -07:00
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
2016-08-21 22:31:37 -07:00
|
|
|
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}`)
|
|
|
|
}
|