ref: 5e1a3e668a38b83b901f97ab8c9829b2601b2c60
parent: 19830d1a2ae03226e6f2dc487f8d1339d2d0a19f
author: halfwit <michaelmisch1985@gmail.com>
date: Thu Aug 6 05:23:15 PDT 2020
Add router code
--- a/footer.go
+++ /dev/null
@@ -1,33 +1,0 @@
-package router
-
-import (
- "golang.org/x/text/message"
-)
-
-func footer(p *message.Printer) map[string]string {
- return map[string]string{
- "faq": p.Sprintf("FAQ"),
- "help": p.Sprintf("Help"),
- "banner": p.Sprintf("Quality Healthcare"),
- "pay": p.Sprintf("Payment Methods"),
- "fees": p.Sprintf("Prices and Fees"),
- "verify": p.Sprintf("Verification"),
- "appt": p.Sprintf("Appointments"),
- "legal": p.Sprintf("Legal"),
- "privacy": p.Sprintf("Privacy Policy"),
- "howworks": p.Sprint("How It Works"),
- "contact": p.Sprint("Contact Us"),
- "pricing": p.Sprint("Pricing"),
- "catalog": p.Sprint("Catalog"),
- "appts": p.Sprint("Appointments"),
- "proc": p.Sprint("Payment Procedures"),
- "payments": p.Sprint("Payment Methods"),
- "phone": p.Sprint("Call toll free"),
- "number": p.Sprint("1(555)555-1234"),
- "email": p.Sprint("Email"),
- "partHead": p.Sprintf("Work with us!"),
- "partner": p.Sprintf("Become A Partner"),
- "provider": p.Sprint("Become A Provider"),
- "copy": p.Sprintf("Copyright 2017, 2018, 2019"),
- }
-}
--- a/forms.go
+++ /dev/null
@@ -1,66 +1,0 @@
-package router
-
-import (
- "fmt"
- "net/http"
-
- "github.com/olmaxmedical/session"
- "golang.org/x/text/message"
-)
-
-var formlist map[string]*Form
-
-// Form - POST requests
-type Form struct {
- Access Access
- After PluginMask
- Path string
- Redirect string
- Validator func(r *http.Request, p *message.Printer) []string
-}
-
-func init() {
- formlist = make(map[string]*Form)
-}
-
-// AddPost - Register a POST form from forms/
-func AddPost(f *Form) {
- formlist[f.Path+".html"] = f
-}
-
-func parseForm(p *Request, w http.ResponseWriter, r *http.Request) (*Form, []string) {
- var errors []string
- form, ok := formlist[p.path]
- if !ok {
- errors = append(errors, "No such page")
- return nil, errors
- }
- if errs := form.Validator(r, p.printer); len(errs) > 0 {
- return nil, errs
- }
- for _, key := range pluginKey {
- if (form.After&key) != 0 && pluginCache[key].Validate != nil {
- if e := pluginCache[key].Validate(p); e != nil {
- errors = append(errors, fmt.Sprint(e))
- return nil, errors
- }
- }
- }
- return form, errors
-}
-
-func postform(p *Request, us session.Session, w http.ResponseWriter, r *http.Request) {
- form, errors := parseForm(p, w, r)
- if len(errors) > 0 && errors[0] != "nil" {
- // NOTE(halfwit) this stashes previous entries, but does not work
- // on multipart forms (with file uploads)
- us.Set("errors", errors)
- // Maybe store form args instead here in session
- url := fmt.Sprintf("%s?%s", r.URL.String(), r.Form.Encode())
- http.Redirect(w, r, url, 302)
- }
- if form != nil {
- us.Set("errors", []string{})
- http.Redirect(w, r, form.Redirect, 302)
- }
-}
--- a/go.mod
+++ /dev/null
@@ -1,10 +1,0 @@
-module github.com/olmaxmedical/router
-
-go 1.14
-
-require (
- github.com/olmaxmedical/database v0.0.1
- github.com/olmaxmedical/email v0.0.1
- github.com/olmaxmedical/session v0.0.1
- golang.org/x/text v0.3.2
-)
--- a/go.sum
+++ /dev/null
@@ -1,14 +1,0 @@
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/olmaxmedical/database v0.0.0/go.mod h1:BPYEBAP3GYeSeqg5hOaH7GC8+P/VnD2OuHYounbVjQs=
-github.com/olmaxmedical/database v0.0.1 h1:cuocVljXq7cPRS9HygFg2B/WSdzWAZEHrc5uMnN+A0A=
-github.com/olmaxmedical/database v0.0.1/go.mod h1:/5Tl6/p0jpvLpj4GaoFki3wRG/3b+ipNNhM5Dyi6Zf8=
-github.com/olmaxmedical/email v0.0.1 h1:bhOERmPiUmFJqC133s+FFXucSI3dNnfDKsboDYFEbkc=
-github.com/olmaxmedical/email v0.0.1/go.mod h1:bz6en9uc6h9fyu3MW2jTwYW19ZclQ22JkcIxsl3/epc=
-github.com/olmaxmedical/session v0.0.1 h1:2xdSjpEg89+ClRFLPp/gR3lNxl4JjPAUd2Hsds++FFs=
-github.com/olmaxmedical/session v0.0.1/go.mod h1:XOVyHL+cKa5t2fLDIJtFxwEzJOa3r1hUkwlL4aybdqA=
-github.com/scorredoira/email v0.0.0-20191107070024-dc7b732c55da h1:hhmnjfzz7szp75AyXxn8tDfEA0oU4REQLmpuW6zNAOY=
-github.com/scorredoira/email v0.0.0-20191107070024-dc7b732c55da/go.mod h1:Q5ljvYIBpukMH+wgB8kcPV1i9NX8TqU++8GgBKq3pt0=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
--- a/handler.go
+++ /dev/null
@@ -1,145 +1,0 @@
-package router
-
-import (
- "fmt"
- "net/http"
-
- "github.com/olmaxmedical/database"
- "github.com/olmaxmedical/email"
- "github.com/olmaxmedical/session"
- "golang.org/x/text/message"
-)
-
-// Handle specific endpoints
-type handler struct {
- manager *session.Manager
-}
-
-func (d *handler) logout(w http.ResponseWriter, r *http.Request) {
- d.manager.Destroy(w, r)
- w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- http.Redirect(w, r, "/index.html", 302)
-}
-
-func (d *handler) normal(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- if r.URL.Path == "/" {
- http.Redirect(w, r, "/index.html", 302)
- return
- }
- user, status, us, role := d.getUser(w, r)
- p := &Request{
- printer: userLang(r),
- status: status,
- request: r,
- user: user,
- role: role,
- session: us,
- path: r.URL.Path[1:],
- }
-
- switch r.Method {
- case "GET":
- getpage(p, w)
- case "POST":
- postform(p, us, w, r)
- }
-}
-
-// TODO: This will require actual client data from the database to populate the page
-func (d *handler) profile(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- user, status, us, role := d.getUser(w, r)
- if status == "false" {
- http.Redirect(w, r, "/login.html", 302)
- return
- }
- if rd, ok := us.Get("redirect").(string); ok {
- us.Delete("redirect")
- http.Redirect(w, r, "/"+rd, 302)
- return
- }
- p := &Request{
- printer: userLang(r),
- status: status,
- session: us,
- user: user,
- role: role,
- }
- var data []byte
- var err error
- switch database.UserRole(user) {
- case database.DoctorAuth:
- if role != database.DoctorAuth {
- http.Error(w, "Unauthorized", 401)
- return
- }
- p.path = "doctor/profile.html"
- data, err = getData(p, "doctor")
- case database.PatientAuth:
- if role != database.PatientAuth {
- http.Error(w, "Unauthorized", 401)
- return
- }
- p.path = "patient/profile.html"
- data, err = getData(p, "patient")
- default:
- http.Error(w, "Forbidden", 403)
- return
- }
- if err != nil {
- http.Error(w, "Service Unavailable", 503)
- return
- }
- fmt.Fprintf(w, "%s", data)
-}
-
-func (d *handler) activate(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
- if len(r.URL.Path) != 46 && r.URL.Path[:9] != "/activate" {
- http.Error(w, "Bad Request", 400)
- return
- }
- email.ValidateSignupToken(w, r, r.URL.Path[10:])
-}
-
-func (d *handler) reset(w http.ResponseWriter, r *http.Request) {
- if len(r.URL.Path) != 43 && r.URL.Path[:6] != "/reset" {
- http.Error(w, "Bad Request", 400)
- return
- }
- p := userLang(r)
- user, _, us, _ := d.getUser(w, r)
- token := email.NextResetToken(r.URL.Path[7:], user)
- if token == "" {
- us.Set("errors", [1]string{p.Sprint("Token expired")})
- return
- }
- us.Set("token", token)
- r.URL.Path = "/newpassword.html"
- d.normal(w, r)
-}
-
-func (d *handler) getUser(w http.ResponseWriter, r *http.Request) (string, string, session.Session, database.Access) {
- us := d.manager.Start(w, r)
- user, ok1 := us.Get("username").(string)
- status, ok2 := us.Get("login").(string)
- role, ok3 := us.Get("role").(database.Access)
- if !ok1 || !ok2 || status != "true" {
- status = "false"
- }
- if !ok3 {
- role = database.GuestAuth
- }
- if status == "true" {
- us.Set("token", database.NewToken())
- }
- return user, status, us, role
-}
-
-func userLang(r *http.Request) *message.Printer {
- accept := r.Header.Get("Accept-Language")
- lang := r.FormValue("lang")
- tag := message.MatchLanguage(lang, accept)
- return message.NewPrinter(tag)
-}
--- a/header.go
+++ /dev/null
@@ -1,16 +1,0 @@
-package router
-
-import (
- "golang.org/x/text/message"
-)
-
-func header(p *message.Printer, status string) map[string]string {
- return map[string]string{
- "home": p.Sprint("Home"),
- "login": p.Sprint("Login"),
- "logout": p.Sprint("Logout"),
- "signup": p.Sprint("Sign Up"),
- "profile": p.Sprint("Profile"),
- "status": status,
- }
-}
--- a/pages.go
+++ /dev/null
@@ -1,176 +1,0 @@
-package router
-
-import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "html/template"
- "net/http"
- "os"
- "path"
- "strings"
-
- "github.com/olmaxmedical/database"
- "golang.org/x/text/message"
-)
-
-var pagecache map[string]*Page
-
-func init() {
- pagecache = make(map[string]*Page)
-}
-
-// Access defines the access rights for a specific page
-type Access uint8
-
-const (
- // GuestAuth - non registered user
- GuestAuth Access = 1 << iota
- // PatientAuth - normal user, added by registration process
- PatientAuth
- // DoctorAuth - manually added to the database
- DoctorAuth
-)
-
-// Page defines what a client receives from a GET request
-type Page struct {
- Access Access
- Extra PluginMask
- CSS string
- Path string
- Data func(p *message.Printer) map[string]interface{}
- tmpl *template.Template
-}
-
-// AddPage - register a *Page to the cache
-func AddPage(p *Page) {
- pagecache[p.Path+".html"] = p
-}
-
-// ValidatePages - Walk all our templates, test them, and finally return applicable errors as an array
-func ValidatePages() []error {
- var errs []error
- hd := path.Join("templates", "header.tpl")
- fd := path.Join("templates", "footer.tpl")
- ld := path.Join("templates", "layout.tpl")
-
- extra, err := os.Open(path.Join("templates", "plugins"))
- if err != nil {
- errs = append(errs, errors.New("Unable to locate templates/plugins"))
- return errs
- }
-
- dirs, err := extra.Readdirnames(0)
- if err != nil {
- errs = append(errs, errors.New("Unable to read plugin dir"))
- }
-
- for n, dir := range dirs {
- dirs[n] = path.Join("templates", "plugins", dir)
- }
-
- // TODO(halfwit) Validate our plugin templates here as well
- dirs = append(dirs, hd, fd, ld)
- printer := message.NewPrinter(message.MatchLanguage("en"))
- for _, item := range pagecache {
- var err error
- tp := path.Join("templates", item.Path) + ".tpl"
- t := template.New(path.Base(tp))
- // TODO(halfwit) Contemplate only adding templates for plugins each page uses
- item.tmpl, _ = t.ParseFiles(dirs...)
- item.tmpl, err = t.ParseFiles(tp)
- if err != nil {
- errs = append(errs, fmt.Errorf("parsing in %s - %v", path.Dir(item.Path), err))
- continue
- }
- p := &Request{
- printer: printer,
- path: item.Path + ".html",
- role: database.PatientAuth | database.DoctorAuth | database.GuestAuth,
- }
-
- _, err = getData(p, "")
- if err != nil {
- errs = append(errs, err)
- }
- }
- return errs
-}
-
-func RunPages() error {
- printer := message.NewPrinter(message.MatchLanguage("en"))
-
- for _, page := range pagecache {
- data := page.Data(printer)
- if len(data) < 1 {
- return errors.New("no data from page")
- }
- }
-
- return nil
-}
-
-func getpage(p *Request, w http.ResponseWriter) {
- var data []byte
- var err error
- switch database.UserRole(p.user) {
- case database.DoctorAuth:
- data, err = getData(p, "doctor")
- case database.PatientAuth:
- data, err = getData(p, "patient")
- default:
- data, err = getData(p, "guest")
- }
- if err != nil && err.Error() == "Unauthorized" {
- p.Session().Set("redirect", p.path)
- http.Redirect(w, p.Request(), "/login.html", 302)
- return
- }
- if err != nil {
- http.Error(w, "Service Unavailable", 503)
- return
- }
- fmt.Fprintf(w, "%s", data)
-}
-
-func getData(p *Request, in string) ([]byte, error) {
- cache, ok := pagecache[p.path]
- if !ok {
- return nil, fmt.Errorf("No such page: %s", p.path)
- }
- if uint8(p.role)&uint8(cache.Access) == 0 {
- return nil, errors.New("Unauthorized")
- }
- r := cache.Data(p.printer)
- r["css"] = cache.CSS
- r["header"] = header(p.printer, p.status)
- r["footer"] = footer(p.printer)
- r["basedir"] = getBaseDir(cache.Path)
- // TODO(halfwit) Test chunking in to go routines if n gets too large
- for _, key := range pluginKey {
- if (cache.Extra&key) != 0 && pluginCache[key].Run != nil {
- r[pluginCache[key].Name] = pluginCache[key].Run(p)
- }
- }
- if p.session != nil {
- r["username"] = p.session.Get("username")
- if _, ok := p.session.Get("redirect").(string); ok {
- r["redirect"] = true
- }
- }
- return cache.render(r)
-}
-
-func (page *Page) render(i map[string]interface{}) ([]byte, error) {
- var buf bytes.Buffer
- data := bufio.NewWriter(&buf)
- err := page.tmpl.ExecuteTemplate(data, "layout", i)
- data.Flush()
- return buf.Bytes(), err
-
-}
-
-func getBaseDir(fp string) string {
- return strings.Repeat("../", strings.Count(fp, "/"))
-}
--- a/plugins.go
+++ /dev/null
@@ -1,49 +1,0 @@
-package router
-
-import (
- "fmt"
-)
-
-// PluginMask - (Must be unique) ID for a plugin
-type PluginMask uint32
-
-// DEAD is a magic string to indicate a non-unique plugin key
-const DEAD PluginMask = 1
-
-var pluginCache map[PluginMask]*Plugin
-var pluginKey []PluginMask
-
-// Plugin - Provide extra data or functionality from GET/POST pages
-type Plugin struct {
- Name string
- Run func(p *Request) map[string]interface{}
- Validate func(p *Request) error
-}
-
-func init() {
- pluginCache = make(map[PluginMask]*Plugin)
-}
-
-// ValidatePlugins - Make sure that its mapping isn't redundant with any other
-// Plugins have external testing to validate they are correct
-func ValidatePlugins() []error {
- errs := []error{}
- for key, item := range pluginCache {
- if item.Validate == nil {
- continue
- }
- if (key & DEAD) != 0 {
- errs = append(errs, fmt.Errorf("Error registering %s: Key requested already in use", item.Name))
- }
- }
- return errs
-}
-
-// AddPlugin - Add Plugin to map by key
-func AddPlugin(p *Plugin, key PluginMask) {
- if pluginCache[key] != nil {
- key |= DEAD
- }
- pluginKey = append(pluginKey, key)
- pluginCache[key] = p
-}
--- a/request.go
+++ /dev/null
@@ -1,35 +1,0 @@
-package router
-
-import (
- "net/http"
-
- "github.com/olmaxmedical/database"
- "github.com/olmaxmedical/session"
- "golang.org/x/text/message"
-)
-
-// Request represents an incoming GET/POST
-type Request struct {
- printer *message.Printer
- session session.Session
- request *http.Request
- user string
- status string
- path string
- role database.Access
-}
-
-// Printer - returns the client's localized printer handler
-func (r *Request) Printer() *message.Printer {
- return r.printer
-}
-
-// Session - returns the client's session
-func (r *Request) Session() session.Session {
- return r.session
-}
-
-// Request - underlying http.Request for forms and such
-func (r *Request) Request() *http.Request {
- return r.request
-}
--- /dev/null
+++ b/router/footer.go
@@ -1,0 +1,33 @@
+package router
+
+import (
+ "golang.org/x/text/message"
+)
+
+func footer(p *message.Printer) map[string]string {
+ return map[string]string{
+ "faq": p.Sprintf("FAQ"),
+ "help": p.Sprintf("Help"),
+ "banner": p.Sprintf("Quality Healthcare"),
+ "pay": p.Sprintf("Payment Methods"),
+ "fees": p.Sprintf("Prices and Fees"),
+ "verify": p.Sprintf("Verification"),
+ "appt": p.Sprintf("Appointments"),
+ "legal": p.Sprintf("Legal"),
+ "privacy": p.Sprintf("Privacy Policy"),
+ "howworks": p.Sprint("How It Works"),
+ "contact": p.Sprint("Contact Us"),
+ "pricing": p.Sprint("Pricing"),
+ "catalog": p.Sprint("Catalog"),
+ "appts": p.Sprint("Appointments"),
+ "proc": p.Sprint("Payment Procedures"),
+ "payments": p.Sprint("Payment Methods"),
+ "phone": p.Sprint("Call toll free"),
+ "number": p.Sprint("1(555)555-1234"),
+ "email": p.Sprint("Email"),
+ "partHead": p.Sprintf("Work with us!"),
+ "partner": p.Sprintf("Become A Partner"),
+ "provider": p.Sprint("Become A Provider"),
+ "copy": p.Sprintf("Copyright 2017, 2018, 2019"),
+ }
+}
--- /dev/null
+++ b/router/forms.go
@@ -1,0 +1,66 @@
+package router
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/olmaxmedical/session"
+ "golang.org/x/text/message"
+)
+
+var formlist map[string]*Form
+
+// Form - POST requests
+type Form struct {
+ Access Access
+ After PluginMask
+ Path string
+ Redirect string
+ Validator func(r *http.Request, p *message.Printer) []string
+}
+
+func init() {
+ formlist = make(map[string]*Form)
+}
+
+// AddPost - Register a POST form from forms/
+func AddPost(f *Form) {
+ formlist[f.Path+".html"] = f
+}
+
+func parseForm(p *Request, w http.ResponseWriter, r *http.Request) (*Form, []string) {
+ var errors []string
+ form, ok := formlist[p.path]
+ if !ok {
+ errors = append(errors, "No such page")
+ return nil, errors
+ }
+ if errs := form.Validator(r, p.printer); len(errs) > 0 {
+ return nil, errs
+ }
+ for _, key := range pluginKey {
+ if (form.After&key) != 0 && pluginCache[key].Validate != nil {
+ if e := pluginCache[key].Validate(p); e != nil {
+ errors = append(errors, fmt.Sprint(e))
+ return nil, errors
+ }
+ }
+ }
+ return form, errors
+}
+
+func postform(p *Request, us session.Session, w http.ResponseWriter, r *http.Request) {
+ form, errors := parseForm(p, w, r)
+ if len(errors) > 0 && errors[0] != "nil" {
+ // NOTE(halfwit) this stashes previous entries, but does not work
+ // on multipart forms (with file uploads)
+ us.Set("errors", errors)
+ // Maybe store form args instead here in session
+ url := fmt.Sprintf("%s?%s", r.URL.String(), r.Form.Encode())
+ http.Redirect(w, r, url, 302)
+ }
+ if form != nil {
+ us.Set("errors", []string{})
+ http.Redirect(w, r, form.Redirect, 302)
+ }
+}
--- /dev/null
+++ b/router/handler.go
@@ -1,0 +1,145 @@
+package router
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/olmaxmedical/database"
+ "github.com/olmaxmedical/email"
+ "github.com/olmaxmedical/session"
+ "golang.org/x/text/message"
+)
+
+// Handle specific endpoints
+type handler struct {
+ manager *session.Manager
+}
+
+func (d *handler) logout(w http.ResponseWriter, r *http.Request) {
+ d.manager.Destroy(w, r)
+ w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ http.Redirect(w, r, "/index.html", 302)
+}
+
+func (d *handler) normal(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ if r.URL.Path == "/" {
+ http.Redirect(w, r, "/index.html", 302)
+ return
+ }
+ user, status, us, role := d.getUser(w, r)
+ p := &Request{
+ printer: userLang(r),
+ status: status,
+ request: r,
+ user: user,
+ role: role,
+ session: us,
+ path: r.URL.Path[1:],
+ }
+
+ switch r.Method {
+ case "GET":
+ getpage(p, w)
+ case "POST":
+ postform(p, us, w, r)
+ }
+}
+
+// TODO: This will require actual client data from the database to populate the page
+func (d *handler) profile(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ user, status, us, role := d.getUser(w, r)
+ if status == "false" {
+ http.Redirect(w, r, "/login.html", 302)
+ return
+ }
+ if rd, ok := us.Get("redirect").(string); ok {
+ us.Delete("redirect")
+ http.Redirect(w, r, "/"+rd, 302)
+ return
+ }
+ p := &Request{
+ printer: userLang(r),
+ status: status,
+ session: us,
+ user: user,
+ role: role,
+ }
+ var data []byte
+ var err error
+ switch database.UserRole(user) {
+ case database.DoctorAuth:
+ if role != database.DoctorAuth {
+ http.Error(w, "Unauthorized", 401)
+ return
+ }
+ p.path = "doctor/profile.html"
+ data, err = getData(p, "doctor")
+ case database.PatientAuth:
+ if role != database.PatientAuth {
+ http.Error(w, "Unauthorized", 401)
+ return
+ }
+ p.path = "patient/profile.html"
+ data, err = getData(p, "patient")
+ default:
+ http.Error(w, "Forbidden", 403)
+ return
+ }
+ if err != nil {
+ http.Error(w, "Service Unavailable", 503)
+ return
+ }
+ fmt.Fprintf(w, "%s", data)
+}
+
+func (d *handler) activate(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
+ if len(r.URL.Path) != 46 && r.URL.Path[:9] != "/activate" {
+ http.Error(w, "Bad Request", 400)
+ return
+ }
+ email.ValidateSignupToken(w, r, r.URL.Path[10:])
+}
+
+func (d *handler) reset(w http.ResponseWriter, r *http.Request) {
+ if len(r.URL.Path) != 43 && r.URL.Path[:6] != "/reset" {
+ http.Error(w, "Bad Request", 400)
+ return
+ }
+ p := userLang(r)
+ user, _, us, _ := d.getUser(w, r)
+ token := email.NextResetToken(r.URL.Path[7:], user)
+ if token == "" {
+ us.Set("errors", [1]string{p.Sprint("Token expired")})
+ return
+ }
+ us.Set("token", token)
+ r.URL.Path = "/newpassword.html"
+ d.normal(w, r)
+}
+
+func (d *handler) getUser(w http.ResponseWriter, r *http.Request) (string, string, session.Session, database.Access) {
+ us := d.manager.Start(w, r)
+ user, ok1 := us.Get("username").(string)
+ status, ok2 := us.Get("login").(string)
+ role, ok3 := us.Get("role").(database.Access)
+ if !ok1 || !ok2 || status != "true" {
+ status = "false"
+ }
+ if !ok3 {
+ role = database.GuestAuth
+ }
+ if status == "true" {
+ us.Set("token", database.NewToken())
+ }
+ return user, status, us, role
+}
+
+func userLang(r *http.Request) *message.Printer {
+ accept := r.Header.Get("Accept-Language")
+ lang := r.FormValue("lang")
+ tag := message.MatchLanguage(lang, accept)
+ return message.NewPrinter(tag)
+}
--- /dev/null
+++ b/router/header.go
@@ -1,0 +1,16 @@
+package router
+
+import (
+ "golang.org/x/text/message"
+)
+
+func header(p *message.Printer, status string) map[string]string {
+ return map[string]string{
+ "home": p.Sprint("Home"),
+ "login": p.Sprint("Login"),
+ "logout": p.Sprint("Logout"),
+ "signup": p.Sprint("Sign Up"),
+ "profile": p.Sprint("Profile"),
+ "status": status,
+ }
+}
--- /dev/null
+++ b/router/pages.go
@@ -1,0 +1,176 @@
+package router
+
+import (
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "html/template"
+ "net/http"
+ "os"
+ "path"
+ "strings"
+
+ "github.com/olmaxmedical/database"
+ "golang.org/x/text/message"
+)
+
+var pagecache map[string]*Page
+
+func init() {
+ pagecache = make(map[string]*Page)
+}
+
+// Access defines the access rights for a specific page
+type Access uint8
+
+const (
+ // GuestAuth - non registered user
+ GuestAuth Access = 1 << iota
+ // PatientAuth - normal user, added by registration process
+ PatientAuth
+ // DoctorAuth - manually added to the database
+ DoctorAuth
+)
+
+// Page defines what a client receives from a GET request
+type Page struct {
+ Access Access
+ Extra PluginMask
+ CSS string
+ Path string
+ Data func(p *message.Printer) map[string]interface{}
+ tmpl *template.Template
+}
+
+// AddPage - register a *Page to the cache
+func AddPage(p *Page) {
+ pagecache[p.Path+".html"] = p
+}
+
+// ValidatePages - Walk all our templates, test them, and finally return applicable errors as an array
+func ValidatePages() []error {
+ var errs []error
+ hd := path.Join("templates", "header.tpl")
+ fd := path.Join("templates", "footer.tpl")
+ ld := path.Join("templates", "layout.tpl")
+
+ extra, err := os.Open(path.Join("templates", "plugins"))
+ if err != nil {
+ errs = append(errs, errors.New("Unable to locate templates/plugins"))
+ return errs
+ }
+
+ dirs, err := extra.Readdirnames(0)
+ if err != nil {
+ errs = append(errs, errors.New("Unable to read plugin dir"))
+ }
+
+ for n, dir := range dirs {
+ dirs[n] = path.Join("templates", "plugins", dir)
+ }
+
+ // TODO(halfwit) Validate our plugin templates here as well
+ dirs = append(dirs, hd, fd, ld)
+ printer := message.NewPrinter(message.MatchLanguage("en"))
+ for _, item := range pagecache {
+ var err error
+ tp := path.Join("templates", item.Path) + ".tpl"
+ t := template.New(path.Base(tp))
+ // TODO(halfwit) Contemplate only adding templates for plugins each page uses
+ item.tmpl, _ = t.ParseFiles(dirs...)
+ item.tmpl, err = t.ParseFiles(tp)
+ if err != nil {
+ errs = append(errs, fmt.Errorf("parsing in %s - %v", path.Dir(item.Path), err))
+ continue
+ }
+ p := &Request{
+ printer: printer,
+ path: item.Path + ".html",
+ role: database.PatientAuth | database.DoctorAuth | database.GuestAuth,
+ }
+
+ _, err = getData(p, "")
+ if err != nil {
+ errs = append(errs, err)
+ }
+ }
+ return errs
+}
+
+func RunPages() error {
+ printer := message.NewPrinter(message.MatchLanguage("en"))
+
+ for _, page := range pagecache {
+ data := page.Data(printer)
+ if len(data) < 1 {
+ return errors.New("no data from page")
+ }
+ }
+
+ return nil
+}
+
+func getpage(p *Request, w http.ResponseWriter) {
+ var data []byte
+ var err error
+ switch database.UserRole(p.user) {
+ case database.DoctorAuth:
+ data, err = getData(p, "doctor")
+ case database.PatientAuth:
+ data, err = getData(p, "patient")
+ default:
+ data, err = getData(p, "guest")
+ }
+ if err != nil && err.Error() == "Unauthorized" {
+ p.Session().Set("redirect", p.path)
+ http.Redirect(w, p.Request(), "/login.html", 302)
+ return
+ }
+ if err != nil {
+ http.Error(w, "Service Unavailable", 503)
+ return
+ }
+ fmt.Fprintf(w, "%s", data)
+}
+
+func getData(p *Request, in string) ([]byte, error) {
+ cache, ok := pagecache[p.path]
+ if !ok {
+ return nil, fmt.Errorf("No such page: %s", p.path)
+ }
+ if uint8(p.role)&uint8(cache.Access) == 0 {
+ return nil, errors.New("Unauthorized")
+ }
+ r := cache.Data(p.printer)
+ r["css"] = cache.CSS
+ r["header"] = header(p.printer, p.status)
+ r["footer"] = footer(p.printer)
+ r["basedir"] = getBaseDir(cache.Path)
+ // TODO(halfwit) Test chunking in to go routines if n gets too large
+ for _, key := range pluginKey {
+ if (cache.Extra&key) != 0 && pluginCache[key].Run != nil {
+ r[pluginCache[key].Name] = pluginCache[key].Run(p)
+ }
+ }
+ if p.session != nil {
+ r["username"] = p.session.Get("username")
+ if _, ok := p.session.Get("redirect").(string); ok {
+ r["redirect"] = true
+ }
+ }
+ return cache.render(r)
+}
+
+func (page *Page) render(i map[string]interface{}) ([]byte, error) {
+ var buf bytes.Buffer
+ data := bufio.NewWriter(&buf)
+ err := page.tmpl.ExecuteTemplate(data, "layout", i)
+ data.Flush()
+ return buf.Bytes(), err
+
+}
+
+func getBaseDir(fp string) string {
+ return strings.Repeat("../", strings.Count(fp, "/"))
+}
--- /dev/null
+++ b/router/plugins.go
@@ -1,0 +1,49 @@
+package router
+
+import (
+ "fmt"
+)
+
+// PluginMask - (Must be unique) ID for a plugin
+type PluginMask uint32
+
+// DEAD is a magic string to indicate a non-unique plugin key
+const DEAD PluginMask = 1
+
+var pluginCache map[PluginMask]*Plugin
+var pluginKey []PluginMask
+
+// Plugin - Provide extra data or functionality from GET/POST pages
+type Plugin struct {
+ Name string
+ Run func(p *Request) map[string]interface{}
+ Validate func(p *Request) error
+}
+
+func init() {
+ pluginCache = make(map[PluginMask]*Plugin)
+}
+
+// ValidatePlugins - Make sure that its mapping isn't redundant with any other
+// Plugins have external testing to validate they are correct
+func ValidatePlugins() []error {
+ errs := []error{}
+ for key, item := range pluginCache {
+ if item.Validate == nil {
+ continue
+ }
+ if (key & DEAD) != 0 {
+ errs = append(errs, fmt.Errorf("Error registering %s: Key requested already in use", item.Name))
+ }
+ }
+ return errs
+}
+
+// AddPlugin - Add Plugin to map by key
+func AddPlugin(p *Plugin, key PluginMask) {
+ if pluginCache[key] != nil {
+ key |= DEAD
+ }
+ pluginKey = append(pluginKey, key)
+ pluginCache[key] = p
+}
--- /dev/null
+++ b/router/request.go
@@ -1,0 +1,35 @@
+package router
+
+import (
+ "net/http"
+
+ "github.com/olmaxmedical/database"
+ "github.com/olmaxmedical/session"
+ "golang.org/x/text/message"
+)
+
+// Request represents an incoming GET/POST
+type Request struct {
+ printer *message.Printer
+ session session.Session
+ request *http.Request
+ user string
+ status string
+ path string
+ role database.Access
+}
+
+// Printer - returns the client's localized printer handler
+func (r *Request) Printer() *message.Printer {
+ return r.printer
+}
+
+// Session - returns the client's session
+func (r *Request) Session() session.Session {
+ return r.session
+}
+
+// Request - underlying http.Request for forms and such
+func (r *Request) Request() *http.Request {
+ return r.request
+}
--- /dev/null
+++ b/router/run.go
@@ -1,0 +1,35 @@
+package router
+
+import (
+ "crypto/tls"
+ "net/http"
+
+ "github.com/olmaxmedical/session"
+)
+
+// Route - All requests pass through here first
+func Route(manager *session.Manager) error {
+ d := &handler{
+ manager: manager,
+ }
+ css := http.FileServer(http.Dir("resources/css/"))
+ jss := http.FileServer(http.Dir("resources/scripts"))
+ img := http.FileServer(http.Dir("resources/images/"))
+ mux := http.NewServeMux()
+ mux.Handle("/css/", http.StripPrefix("/css/", css))
+ mux.Handle("/scripts/", http.StripPrefix("/scripts/", jss))
+ mux.Handle("/images/", http.StripPrefix("/images/", img))
+ mux.HandleFunc("/activate/", d.activate)
+ mux.HandleFunc("/reset/", d.reset)
+ mux.HandleFunc("/logout.html", d.logout)
+ mux.HandleFunc("/profile.html", d.profile)
+ mux.HandleFunc("/", d.normal)
+ //from https://github.com/denji/golang-tls (creative commons)
+ srv := &http.Server{
+ Addr: ":8443",
+ Handler: mux,
+ TLSConfig: getTlsConfig(),
+ TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
+ }
+ return srv.ListenAndServeTLS("cert.pem", "key.pem")
+}
--- /dev/null
+++ b/router/tls.go
@@ -1,0 +1,21 @@
+package router
+
+import (
+ "crypto/tls"
+)
+
+func getTlsConfig() *tls.Config {
+ tlsConfig := &tls.Config{
+ MinVersion: tls.VersionTLS12,
+ CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
+ PreferServerCipherSuites: true,
+ CipherSuites: []uint16{
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ },
+ }
+ tlsConfig.BuildNameToCertificate()
+ return tlsConfig
+}
--- a/run.go
+++ /dev/null
@@ -1,35 +1,0 @@
-package router
-
-import (
- "crypto/tls"
- "net/http"
-
- "github.com/olmaxmedical/session"
-)
-
-// Route - All requests pass through here first
-func Route(manager *session.Manager) error {
- d := &handler{
- manager: manager,
- }
- css := http.FileServer(http.Dir("resources/css/"))
- jss := http.FileServer(http.Dir("resources/scripts"))
- img := http.FileServer(http.Dir("resources/images/"))
- mux := http.NewServeMux()
- mux.Handle("/css/", http.StripPrefix("/css/", css))
- mux.Handle("/scripts/", http.StripPrefix("/scripts/", jss))
- mux.Handle("/images/", http.StripPrefix("/images/", img))
- mux.HandleFunc("/activate/", d.activate)
- mux.HandleFunc("/reset/", d.reset)
- mux.HandleFunc("/logout.html", d.logout)
- mux.HandleFunc("/profile.html", d.profile)
- mux.HandleFunc("/", d.normal)
- //from https://github.com/denji/golang-tls (creative commons)
- srv := &http.Server{
- Addr: ":8443",
- Handler: mux,
- TLSConfig: getTlsConfig(),
- TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),
- }
- return srv.ListenAndServeTLS("cert.pem", "key.pem")
-}
--- a/tls.go
+++ /dev/null
@@ -1,21 +1,0 @@
-package router
-
-import (
- "crypto/tls"
-)
-
-func getTlsConfig() *tls.Config {
- tlsConfig := &tls.Config{
- MinVersion: tls.VersionTLS12,
- CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
- PreferServerCipherSuites: true,
- CipherSuites: []uint16{
- tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
- tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
- tls.TLS_RSA_WITH_AES_256_CBC_SHA,
- },
- }
- tlsConfig.BuildNameToCertificate()
- return tlsConfig
-}