chipmunk/server.go
2017-02-04 23:37:29 -08:00

327 lines
8.4 KiB
Go

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}`)
}