diff --git a/src/api/routes/handlers.go b/src/api/routes/handlers.go new file mode 100644 index 0000000..6d0e4d3 --- /dev/null +++ b/src/api/routes/handlers.go @@ -0,0 +1,41 @@ +package routes + +import ( + "database/sql" + "encoding/json" + "log" + "net/http" + + "github.com/danieljsummers/myPrayerJournal/src/api/data" + "github.com/julienschmidt/httprouter" +) + +/* Support */ + +// Set the content type, the HTTP error code, and return the error message. +func sendError(w http.ResponseWriter, r *http.Request, err error) { + w.Header().Set("Content-Type", "application/json; encoding=UTF-8") + w.WriteHeader(http.StatusInternalServerError) + if err := json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}); err != nil { + log.Print("Error creating error JSON: " + err.Error()) + } +} + +// Set the content type and return the JSON to the user. +func sendJSON(w http.ResponseWriter, r *http.Request, result interface{}) { + w.Header().Set("Content-Type", "application/json; encoding=UTF-8") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(map[string]interface{}{"data": result}); err != nil { + sendError(w, r, err) + } +} + +/* Handlers */ + +func journal(w http.ResponseWriter, r *http.Request, _ httprouter.Params, db *sql.DB) { + reqs := data.Journal(db, "TODO: get user ID") + if reqs == nil { + reqs = []data.JournalRequest{} + } + sendJSON(w, r, reqs) +} diff --git a/src/api/routes/router.go b/src/api/routes/router.go new file mode 100644 index 0000000..7390156 --- /dev/null +++ b/src/api/routes/router.go @@ -0,0 +1,66 @@ +package routes + +import ( + "database/sql" + "fmt" + "net/http" + + auth0 "github.com/auth0-community/go-auth0" + "github.com/julienschmidt/httprouter" + jose "gopkg.in/square/go-jose.v2" +) + +// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object. +type AuthConfig struct { + Domain string `json:"domain"` + ClientID string `json:"id"` + ClientSecret string `json:"secret"` +} + +// DBHandler extends httprouter's handler with a DB instance +type DBHandler func(http.ResponseWriter, *http.Request, httprouter.Params, *sql.DB) + +//type APIHandler func(http.ResponseWriter, *http.Request, httprouter.Params, *sql.DB, string) + +func withDB(next DBHandler, db *sql.DB) httprouter.Handle { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { + next(w, r, p, db) + } +} + +func withAuth(next DBHandler, cfg *AuthConfig) DBHandler { + return func(w http.ResponseWriter, r *http.Request, p httprouter.Params, db *sql.DB) { + secret := []byte(cfg.ClientSecret) + secretProvider := auth0.NewKeyProvider(secret) + audience := []string{"{YOUR-AUTH0-API-AUDIENCE}"} + + configuration := auth0.NewConfiguration(secretProvider, audience, fmt.Sprintf("https://%s.auth0.com/", cfg.Domain), jose.HS256) + validator := auth0.NewValidator(configuration) + + token, err := validator.ValidateRequest(r) + + if err != nil { + fmt.Println(err) + fmt.Println("Token is not valid:", token) + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Unauthorized")) + } else { + // TODO pass the user ID (sub) along; this -> doesn't work | r.Header.Add("user-id", token.Claims("sub")) + next(w, r, p, db) + } + } + +} + +// NewRouter returns a configured router to handle all incoming requests. +func NewRouter(db *sql.DB, cfg *AuthConfig) *httprouter.Router { + router := httprouter.New() + for _, route := range routes { + if route.IsPublic { + router.Handle(route.Method, route.Pattern, withDB(route.Func, db)) + } else { + router.Handle(route.Method, route.Pattern, withDB(withAuth(route.Func, cfg), db)) + } + } + return router +} diff --git a/src/api/routes/routes.go b/src/api/routes/routes.go index 7a02cc1..6d9add8 100644 --- a/src/api/routes/routes.go +++ b/src/api/routes/routes.go @@ -1,64 +1,25 @@ // Package routes contains endpoint handlers for the myPrayerJournal API. package routes -import ( - "database/sql" - "encoding/json" - "log" - "net/http" - - "github.com/danieljsummers/myPrayerJournal/src/api/data" - "github.com/julienschmidt/httprouter" -) - -/* Support */ - -// Set the content type, the HTTP error code, and return the error message. -func sendError(w http.ResponseWriter, r *http.Request, err error) { - m := map[string]string{"error": err.Error()} - j, jErr := json.Marshal(m) - if jErr != nil { - log.Print("Error creating error JSON: " + jErr.Error()) - } - w.WriteHeader(500) - w.Header().Set("Content-Type", "application/json") - w.Write(j) +// Route is a route served in the application. +type Route struct { + Name string + Method string + Pattern string + Func DBHandler + IsPublic bool } -// Set the content type and return the JSON to the user. -func sendJSON(w http.ResponseWriter, r *http.Request, result interface{}) { - payload, err := json.Marshal(result) - if err != nil { - sendError(w, r, err) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write([]byte("{ data: ")) - w.Write(payload) - w.Write([]byte(" }")) -} - -/* Handlers */ - -func journal(w http.ResponseWriter, r *http.Request, _ httprouter.Params, db *sql.DB) { - reqs := data.Journal(db, "TODO: get user ID") - if reqs == nil { - reqs = []data.JournalRequest{} - } - sendJSON(w, r, reqs) -} - -/* Wrappers */ - -func withDB(fn func(w http.ResponseWriter, r *http.Request, p httprouter.Params, db *sql.DB), db *sql.DB) httprouter.Handle { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { - fn(w, r, p, db) - } -} - -// Routes returns a configured router to handle all incoming requests. -func Routes(db *sql.DB) *httprouter.Router { - router := httprouter.New() - router.GET("/journal", withDB(journal, db)) - return router +// Routes is the collection of all routes served in the application. +type Routes []Route + +// routes is the actual list of routes for the application. +var routes = Routes{ + Route{ + "Journal", + "GET", + "/journal", + journal, + false, + }, } diff --git a/src/my-prayer-journal.go b/src/my-prayer-journal.go index 4993504..647587e 100644 --- a/src/my-prayer-journal.go +++ b/src/my-prayer-journal.go @@ -2,17 +2,48 @@ package main import ( + "encoding/json" "log" + "net/http" + "os" "github.com/danieljsummers/myPrayerJournal/src/api/data" "github.com/danieljsummers/myPrayerJournal/src/api/routes" ) +// Web contains configuration for the web server. +type Web struct { + Port string `json:"port"` +} + +// Settings contains configuration for the myPrayerJournal API. +type Settings struct { + Data *data.Settings `json:"data"` + Web *Web `json:"web"` + Auth *routes.AuthConfig `json:"auth"` +} + +// readSettings parses the JSON configuration file into the Settings struct. +func readSettings(f string) *Settings { + config, err := os.Open(f) + if err != nil { + log.Fatal(err) + } + defer config.Close() + parser := json.NewDecoder(config) + settings := Settings{} + if err = parser.Decode(&settings); err != nil { + log.Fatal(err) + } + return &settings +} + func main() { - db, ok := data.Connect(&data.Settings{}) + cfg := readSettings("config.json") + db, ok := data.Connect(cfg.Data) if !ok { log.Fatal("Unable to connect to database; exiting") } - router := routes.Routes(db) - _ = router // TODO: remove + log.Printf("myPrayerJournal API listening on %s", cfg.Web.Port) + log.Fatal(http.ListenAndServe(cfg.Web.Port, routes.NewRouter(db, cfg.Auth))) }