aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDerek Stevens <nilix@nilfm.cc>2022-01-04 13:23:25 -0700
committerDerek Stevens <nilix@nilfm.cc>2022-01-04 13:23:25 -0700
commit756a0739fd43a53fc4c5df24c96c429a617e9591 (patch)
tree8e34c24396ecace61c054e59974aa7298ce85e8e
initial commit
router with static files and dynamic handlers, renderers for templates, json, and xml, and beginnings of auth and cookie management
-rw-r--r--.gitignore3
-rw-r--r--auth/auth.go19
-rw-r--r--cookie/cookie.go38
-rw-r--r--go.mod11
-rw-r--r--go.sum6
-rw-r--r--renderer/renderer.go48
-rw-r--r--router/router.go173
7 files changed, 298 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4a468a3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+goldbug*
+static/
+templates/
diff --git a/auth/auth.go b/auth/auth.go
new file mode 100644
index 0000000..17b6ad3
--- /dev/null
+++ b/auth/auth.go
@@ -0,0 +1,19 @@
+package auth
+
+import (
+ //nilfm.cc/git/goldbug/cookie
+)
+
+type UserStore interface {
+ InitiateSession(user string, sessionId string) error
+ ValidateUser(user string, password string, sessionId string) (bool, error)
+ EndSession(user string) error
+}
+
+func Login(user string, password string, userStore UserStore) (string, error) {
+ //ValidateUser (check user exists, hash and compare password)
+ //InitiateUserSession (generate token and assign it to the user)
+ //set username in cookie
+ //return token, nil
+ return "", nil
+}
diff --git a/cookie/cookie.go b/cookie/cookie.go
new file mode 100644
index 0000000..935ae46
--- /dev/null
+++ b/cookie/cookie.go
@@ -0,0 +1,38 @@
+package cookie
+
+import (
+ "net/http"
+ "crypto/rand"
+ "time"
+)
+
+var availableChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@!.#$_"
+
+func GenToken(length int) string {
+ ll := len(availableChars)
+ b := make([]byte, length)
+ rand.Read(b)
+ for i := 0; i < length; i++ {
+ b[i] = availableChars[int(b[i])%ll]
+ }
+ return string(b)
+}
+
+func StoreToken(field string, token string, w http.ResponseWriter, hrs int) {
+ cookie := http.Cookie{
+ Name: field,
+ Value: token,
+ Expires: time.Now().Add(time.Duration(hrs) * time.Hour),
+ }
+
+ http.SetCookie(w, &cookie)
+}
+
+func GetToken(field string, req *http.Request) (string, error) {
+ c, err := req.Cookie(field)
+ if err != nil {
+ return c.Value, nil
+ } else {
+ return "", err
+ }
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..938ef52
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,11 @@
+module nilfm.cc/git/goldbug
+
+go 1.17
+
+require (
+ github.com/gorilla/securecookie v1.1.1
+ github.com/gorilla/sessions v1.2.1
+ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
+)
+
+
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..28c6029
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,6 @@
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
diff --git a/renderer/renderer.go b/renderer/renderer.go
new file mode 100644
index 0000000..79860cc
--- /dev/null
+++ b/renderer/renderer.go
@@ -0,0 +1,48 @@
+package renderer
+
+import (
+ "net/http"
+ "html/template"
+ "encoding/json"
+ "encoding/xml"
+)
+
+func Template(t string) http.Handler {
+ tmpl := template.Must(template.ParseFiles(t))
+
+ handlerFunc := func(w http.ResponseWriter, req *http.Request) {
+ tmpl.Execute(w, req)
+ }
+
+ return http.HandlerFunc(handlerFunc)
+}
+
+func JSON(key string) http.Handler {
+ handlerFunc := func(w http.ResponseWriter, req *http.Request) {
+ apiData := req.Context().Value(key)
+
+ data, err := json.Marshal(apiData)
+ if err != nil {
+ panic(err.Error())
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(data)
+ }
+
+ return http.HandlerFunc(handlerFunc)
+}
+
+func XML(key string) http.Handler {
+ handlerFunc := func(w http.ResponseWriter, req *http.Request) {
+ apiData := req.Context().Value(key)
+
+ data, err := xml.MarshalIndent(apiData, "", " ")
+ if err != nil {
+ panic(err.Error())
+ }
+ w.Header().Set("Content-Type", "application/xml")
+ w.Write(data)
+ }
+
+ return http.HandlerFunc(handlerFunc)
+}
diff --git a/router/router.go b/router/router.go
new file mode 100644
index 0000000..35d17f6
--- /dev/null
+++ b/router/router.go
@@ -0,0 +1,173 @@
+package router
+
+import (
+ "net/http"
+ "html/template"
+ "regexp"
+ "log"
+ "strconv"
+ "strings"
+ "path"
+ "os"
+ "errors"
+ "fmt"
+)
+
+type Router struct {
+ /* This is the template for error pages */
+ Fallback template.Template
+ /* Routes are only filled by using the appropriate methods. */
+ routes []Route
+ /* StaticPaths can be filled from outside when constructing the Router.
+ * key = uri
+ * value = file path
+ */
+ StaticPaths map[string]string
+}
+
+
+type Route struct {
+ path *regexp.Regexp
+ handlerMap map[string]http.Handler
+}
+
+/* This represents what the server should do with a given request. */
+type ServerTask struct {
+ /* template and apiFmt are mutually exclusive. */
+ template *template.Template
+ apiFmt string
+
+ /* doWork represents serverside work to fulfill the request.
+ * This function can be composed any way you see fit when creating
+ * a route.
+ */
+ doWork func(http.ResponseWriter, *http.Request)
+}
+
+func (self *Router) Get(path string, h http.Handler) {
+ self.AddRoute("GET", path, h)
+}
+
+func (self *Router) Post(path string, h http.Handler) {
+ self.AddRoute("POST", path, h)
+}
+
+func (self *Router) Put(path string, h http.Handler) {
+ self.AddRoute("PUT", path, h)
+}
+
+func (self *Router) Delete(path string, h http.Handler) {
+ self.AddRoute("DELETE", path, h)
+}
+
+func (self *Router) AddRoute(method string, path string, h http.Handler) {
+
+ exactPath := regexp.MustCompile("^" + path + "$")
+
+ /* If the route already exists, try to add this method to the ServerTask map. */
+ for _, r := range self.routes {
+ if r.path == exactPath {
+ r.handlerMap[method] = h
+ return
+ }
+ }
+
+ /* Otherwise add a new route */
+ self.routes = append(self.routes, Route{
+ path: exactPath,
+ handlerMap: map[string]http.Handler{method: h},
+ })
+
+}
+
+func (self *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ /* Show the 500 error page if we panic */
+ defer func() {
+ if r := recover(); r != nil {
+ log.Println("ERROR:", r)
+ self.ErrorPage(w, req, 500, "There was an error on the server.")
+ }
+ }()
+
+ /* If the request matches any our StaticPaths, try to serve a file. */
+ for uri, dir := range self.StaticPaths {
+ if req.Method == "GET" && strings.HasPrefix(req.URL.Path, uri) {
+ restOfUri := strings.TrimPrefix(req.URL.Path, uri)
+ p := path.Join(dir, restOfUri)
+ p = path.Clean(p)
+
+ /* If the file exists, try to serve it. */
+ info, err := os.Stat(p);
+ if err == nil && !info.IsDir() {
+ http.ServeFile(w, req, p)
+ /* Handle the common errors */
+ } else if errors.Is(err, os.ErrNotExist) || errors.Is(err, os.ErrExist) {
+ self.ErrorPage(w, req, 404, "The requested file does not exist")
+ } else if errors.Is(err, os.ErrPermission) || info.IsDir() {
+ self.ErrorPage(w, req, 403, "Access forbidden")
+ /* If it's some weird error, serve a 500. */
+ } else {
+ self.ErrorPage(w, req, 500, "Internal server error")
+ }
+
+ return
+ }
+ }
+
+ /* Otherwise, this is a normal route */
+ for _, r := range self.routes {
+
+ /* Pull the params out of the regex;
+ * If the path doesn't match the regex, params will be nil.
+ */
+ params := r.Match(req)
+ if params == nil {
+ continue
+ }
+ for method, handler := range r.handlerMap {
+ if method == req.Method {
+ /* Parse the form and add the params to it */
+ req.ParseForm()
+ ProcessParams(req, params)
+ /* handle the request! */
+ handler.ServeHTTP(w, req);
+ return
+ }
+ }
+ }
+ self.ErrorPage(w, req, 404, "The page you requested does not exist!")
+}
+
+/*******************
+ * Utility Methods *
+ *******************/
+
+func ProcessParams(req *http.Request, params map[string]string) {
+ for key, value := range params {
+ req.Form.Add(key, value)
+ }
+}
+
+func (self *Route) Match(r *http.Request) map[string]string {
+ match := self.path.FindStringSubmatch(r.URL.Path)
+ if match == nil {
+ return nil
+ }
+
+ params := map[string]string{}
+ groupNames := self.path.SubexpNames()
+
+ for i, group := range match {
+ params[groupNames[i]] = group
+ }
+
+ return params
+}
+
+func (self *Router) ErrorPage(w http.ResponseWriter, req *http.Request, code int, errMsg string) {
+ w.WriteHeader(code)
+ req.ParseForm()
+ req.Form.Add("ErrorCode", strconv.Itoa(code))
+ req.Form.Add("ErrorMessage", errMsg)
+ self.Fallback.Execute(w, req)
+}