hlfw.ca

webbing

Download patch

ref: 6dcaa76c7cb8da52cb14de66688556934dca39ca
parent: baf2c3b670574cdb96cd5a567476b1fc85c899ed
author: halfwit <michaelmisch1985@gmail.com>
date: Tue Nov 12 08:29:03 PST 2019

Set up form and sessioning plugins

--- a/forms/doctor/application.go
+++ b/forms/doctor/application.go
@@ -5,6 +5,7 @@
 	"net/http"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -15,7 +16,7 @@
 		Path:      "doctor/application",
 		Validator: Application,
 		Redirect:  "/index.html",
-		After:     router.EmailForm | router.ValidateCountries | router.ValidateSpecialties | router.ValidateToken,
+		After:     plugins.EmailForm | plugins.Countries | plugins.Services | plugins.FormToken,
 	}
 	router.AddPost(b)
 }
--- a/forms/doctor/profile.go
+++ b/forms/doctor/profile.go
@@ -5,6 +5,7 @@
 	"time"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -15,7 +16,7 @@
 		Path:      "doctor/profile",
 		Validator: DoctorProfile,
 		Redirect:  "/doctor/profile.html",
-		After:     router.ValidateToken | router.AddAppointment,
+		After:     plugins.FormToken | plugins.AddAppointment,
 	}
 	router.AddPost(b)
 }
--- a/forms/login.go
+++ b/forms/login.go
@@ -4,6 +4,7 @@
 	"net/http"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -12,14 +13,14 @@
 	b := &router.Form{
 		Access:    router.GuestAuth,
 		Path:      "login",
-		Validator: Login,
-		After:     router.ValidateLogin,
+		Validator: login,
+		After:     plugins.ValidateLogin,
 		Redirect:  "/profile.html",
 	}
 	router.AddPost(b)
 }
 
-func Login(r *http.Request, p *message.Printer) []string {
+func login(r *http.Request, p *message.Printer) []string {
 	var errors []string
 	data, err := forms.Parse(r)
 	if err != nil {
--- a/forms/newpassword.go
+++ b/forms/newpassword.go
@@ -4,6 +4,7 @@
 	"net/http"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -14,7 +15,7 @@
 		Path:      "newpassword",
 		Validator: NewPassword,
 		Redirect:  "/login.html",
-		After:     router.SetPassword | router.ValidateToken,
+		After:     plugins.ResetPassword | plugins.FormToken,
 	}
 	router.AddPost(b)
 }
--- a/forms/patient/offer.go
+++ b/forms/patient/offer.go
@@ -5,6 +5,7 @@
 	"time"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -14,7 +15,7 @@
 		Access:    router.PatientAuth,
 		Path:      "patient/offer",
 		Validator: Offer,
-		After:     router.Search | router.ValidateSpecialty,
+		After:     plugins.Search | plugins.Services,
 		Redirect:  "results.html",
 	}
 	router.AddPost(b)
--- a/forms/patient/symptoms.go
+++ b/forms/patient/symptoms.go
@@ -5,6 +5,7 @@
 	"time"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -14,7 +15,7 @@
 		Access:    router.PatientAuth,
 		Path:      "patient/symptoms",
 		Validator: Symptoms,
-		After:     router.EmailForm | router.WithOffer,
+		After:     plugins.EmailForm | plugins.MakeOffer,
 		Redirect:  "patient/profile.html",
 	}
 	router.AddPost(b)
--- a/forms/resetpassword.go
+++ b/forms/resetpassword.go
@@ -4,6 +4,7 @@
 	"net/http"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -14,7 +15,7 @@
 		Path:      "resetpassword",
 		Validator: Reset,
 		Redirect:  "/login.html",
-		After:     router.SendReset,
+		After:     plugins.ResetPassword,
 	}
 	router.AddPost(b)
 }
--- a/forms/signup.go
+++ b/forms/signup.go
@@ -4,6 +4,7 @@
 	"net/http"
 
 	"github.com/albrow/forms"
+	"github.com/olmaxmedical/olmax_go/plugins"
 	"github.com/olmaxmedical/olmax_go/router"
 	"golang.org/x/text/message"
 )
@@ -14,7 +15,7 @@
 		Path:      "signup",
 		Validator: Signin,
 		Redirect:  "/login.html",
-		After:     router.SendSignup,
+		After:     plugins.SendSignup,
 	}
 	router.AddPost(b)
 }
--- a/pages/doctor/application.go
+++ b/pages/doctor/application.go
@@ -12,7 +12,7 @@
 		CSS:    "",
 		Path:   "doctor/application",
 		Data:   Application,
-		Extra:  plugins.ListCountries | plugins.ListServices | plugins.FormErrors | plugins.FormToken,
+		Extra:  plugins.Countries | plugins.Services | plugins.FormErrors | plugins.FormToken,
 	}
 	router.AddPage(b)
 }
--- a/pages/help/provider.go
+++ b/pages/help/provider.go
@@ -13,7 +13,7 @@
 		CSS:    "",
 		Path:   "help/provider",
 		Data:   Provider,
-		Extra:  plugins.ListServices,
+		Extra:  plugins.Services,
 	}
 	router.AddPage(b)
 }
--- a/pages/patient/offer.go
+++ b/pages/patient/offer.go
@@ -12,7 +12,7 @@
 		CSS:    "",
 		Path:   "patient/offer",
 		Data:   Createoffer,
-		Extra:  plugins.ListServices | plugins.FormErrors,
+		Extra:  plugins.Services | plugins.FormErrors,
 	}
 	router.AddPage(b)
 }
--- /dev/null
+++ b/plugins/appointments.go
@@ -1,0 +1,20 @@
+package plugins
+
+import "github.com/olmaxmedical/olmax_go/router"
+
+// AddAppointment registers an appointment into the appointment book
+// TODO(halfwit) message/email client to fill out Symptoms form
+const AddAppointment router.PluginMask = 1
+
+func init() {
+	b := &router.Plugin{
+		Name:     "Add Appointments",
+		Run:      nil,
+		Validate: addAppointment,
+	}
+	router.AddPlugin(b, AddAppointment)
+}
+
+func addAppointment(s *router.Request) error {
+	return nil
+}
--- a/plugins/countries.go
+++ b/plugins/countries.go
@@ -1,6 +1,7 @@
 package plugins
 
 import (
+	"fmt"
 	"sort"
 	"strings"
 
@@ -9,8 +10,8 @@
 	"golang.org/x/text/message"
 )
 
-// ListCountries - Populate a localized spinner to select country
-const ListCountries router.PluginMask = 4
+// Countries - Populate a localized spinner to select country
+const Countries router.PluginMask = 2
 
 // Country - Mapping token to internationalized country code
 type Country struct {
@@ -36,10 +37,10 @@
 	sort.Sort(cache)
 	b := &router.Plugin{
 		Name:     "countrylist",
-		Run:      Countries,
-		Validate: nil,
+		Run:      listCountries,
+		Validate: validateCountries,
 	}
-	router.AddPlugin(b, ListCountries)
+	router.AddPlugin(b, Countries)
 }
 
 // Len - For Sort implementation
@@ -64,8 +65,7 @@
 	c.list[j] = tmp
 }
 
-// Countries - return a localized list of countries
-func Countries(_ *router.Request) map[string]interface{} {
+func listCountries(_ *router.Request) map[string]interface{} {
 	// TODO(halfwit): Use Request to get a localized country name
 	c := make(map[string]interface{})
 	for _, item := range cache.list {
@@ -74,21 +74,21 @@
 	return c
 }
 
-// TODO: Export this so it's available to form parsing as a bitmask
-func validateCountries(p *message.Printer, countries []string) string {
-	for _, c := range countries {
-		if msg := validateCountry(p, c); msg != "" {
+func validateCountries(r *router.Request) error {
+	s := r.Request()
+	for _, c := range s.PostFormValue("country") {
+		if msg := checkCountry(r.Printer(), c); msg != nil {
 			return msg
 		}
 	}
-	return ""
+	return nil
 }
 
-func validateCountry(p *message.Printer, country string) string {
+func checkCountry(p *message.Printer, country rune) error {
 	for _, item := range cache.list {
-		if item.Name.Common == country {
-			return ""
+		if item.Name.Common == string(country) {
+			return nil
 		}
 	}
-	return p.Sprint("No country entered/nil value entered")
+	return fmt.Errorf("%s", p.Sprint("No country entered/nil value entered"))
 }
--- /dev/null
+++ b/plugins/email.go
@@ -1,0 +1,83 @@
+package plugins
+
+import (
+	"errors"
+	"log"
+	"mime/multipart"
+
+	"github.com/olmaxmedical/olmax_go/email"
+	"github.com/olmaxmedical/olmax_go/router"
+)
+
+// EmailForm - Patient form to gmail
+const EmailForm router.PluginMask = 4
+
+// SendSignup - Send account creation validation email
+const SendSignup router.PluginMask = 5
+
+// SendReset - Send password reset email
+const SendReset router.PluginMask = 6
+
+func init() {
+	b := &router.Plugin{
+		Name:     "Email client information form",
+		Run:      nil,
+		Validate: emailForm,
+	}
+	router.AddPlugin(b, EmailForm)
+	c := &router.Plugin{
+		Name:     "Send signup email",
+		Run:      nil,
+		Validate: signupEmail,
+	}
+	router.AddPlugin(c, SendSignup)
+	d := &router.Plugin{
+		Name:     "Send password reset email",
+		Run:      nil,
+		Validate: resetPassword,
+	}
+	router.AddPlugin(d, SendReset)
+}
+
+func signupEmail(s *router.Request) error {
+	r := s.Request()
+	first := r.PostFormValue("fname")
+	last := r.PostFormValue("lname")
+	address := r.PostFormValue("email")
+	pass := r.PostFormValue("pass")
+	email.SendSignup(first, last, address, pass, s.Printer())
+	return nil
+}
+
+func resetPassword(s *router.Request) error {
+	p := s.Printer()
+	r := s.Request()
+	email.SendReset(r.PostFormValue("email"), p)
+	return nil
+}
+
+func emailForm(s *router.Request) error {
+	p := s.Printer()
+	r := s.Request()
+	r.ParseMultipartForm(10 << 20) // parse up to 10MB
+	if r.PostFormValue("name") == "" || r.PostFormValue("email") == "" {
+		return errors.New(p.Sprint("Missing name or email in form. Please contact us at olmaxmedical@gmail.com"))
+	}
+	if b, ok := r.Form["sendto"]; !ok || b[0] == "" {
+		return errors.New(p.Sprint("Missing value for target email. Please contact us at olmaxmedical.gmail.com"))
+	}
+	attachments := make(map[string]multipart.File)
+	m := r.MultipartForm
+	for _, headers := range m.File {
+		for _, header := range headers {
+			file, err := header.Open()
+			if err != nil { //non fatal, log any oddities and continue
+				log.Println(err)
+				continue
+			}
+			attachments[header.Filename] = file
+		}
+	}
+	email.SendForm(r.Form, p, attachments)
+	return nil
+}
--- a/plugins/errors.go
+++ b/plugins/errors.go
@@ -5,7 +5,7 @@
 )
 
 // FormErrors - A list of errors present on a POST request
-const FormErrors router.PluginMask = 6
+const FormErrors router.PluginMask = 7
 
 func init() {
 	b := &router.Plugin{
@@ -16,7 +16,7 @@
 	router.AddPlugin(b, FormErrors)
 }
 
-// GetFormErrors - Return all errors encountered during form parse
+// GetFormErrors - return the client a list of any errors in the form
 func GetFormErrors(r *router.Request) map[string]interface{} {
 	s := r.Session()
 	if s == nil {
--- /dev/null
+++ b/plugins/offers.go
@@ -1,0 +1,19 @@
+package plugins
+
+import "github.com/olmaxmedical/olmax_go/router"
+
+// Offer - Request a time slot with doctor
+const MakeOffer router.PluginMask = 8
+
+func init() {
+	b := &router.Plugin{
+		Name:     "offer",
+		Run:      nil,
+		Validate: offer,
+	}
+	router.AddPlugin(b, MakeOffer)
+}
+
+func offer(s *router.Request) error {
+	return nil
+}
--- /dev/null
+++ b/plugins/password.go
@@ -1,0 +1,61 @@
+package plugins
+
+import (
+	"errors"
+
+	"github.com/olmaxmedical/olmax_go/db"
+	"github.com/olmaxmedical/olmax_go/router"
+)
+
+// ValidateLogin - Check user/pass combo exists
+const ValidateLogin router.PluginMask = 9
+
+// ResetPassword - Update database entry for password
+const ResetPassword router.PluginMask = 10
+
+func init() {
+	b := &router.Plugin{
+		Name:     "Validate login",
+		Run:      nil,
+		Validate: login,
+	}
+	router.AddPlugin(b, ValidateLogin)
+	c := &router.Plugin{
+		Name:     "Set password",
+		Run:      nil,
+		Validate: setPass,
+	}
+	router.AddPlugin(c, ResetPassword)
+}
+
+func login(s *router.Request) error {
+	r := s.Request()
+	us := s.Session()
+	p := s.Printer()
+	user := r.PostFormValue("email")
+	pass := r.PostFormValue("pass")
+	if db.ValidateLogin(user, pass) {
+		us.Set("username", user)
+		us.Set("login", "true")
+		us.Set("role", db.UserRole(user))
+		return nil
+	}
+	return errors.New(p.Sprint("Invalid login"))
+}
+
+func setPass(s *router.Request) error {
+	r := s.Request()
+	p := s.Printer()
+
+	pass := r.PostFormValue("password")
+	repeat := r.PostFormValue("reenter")
+	if pass != repeat {
+		return errors.New(p.Sprint("Passwords do not match"))
+	}
+	token := r.PostFormValue("token")
+	if !db.FindTempEntry(token) {
+		return errors.New(p.Sprint("Session expired"))
+	}
+	db.UpdateUserPassword(token, pass)
+	return nil
+}
--- /dev/null
+++ b/plugins/search.go
@@ -1,0 +1,20 @@
+package plugins
+
+import "github.com/olmaxmedical/olmax_go/router"
+
+// Search - generic search for doctors in area
+const Search router.PluginMask = 11
+
+func init() {
+	b := &router.Plugin{
+		Name:     "Search",
+		Run:      nil,
+		Validate: search,
+	}
+	router.AddPlugin(b, Search)
+}
+
+// Stuuuuubbb
+func search(r *router.Request) error {
+	return nil
+}
--- /dev/null
+++ b/plugins/services.go
@@ -1,0 +1,113 @@
+package plugins
+
+import (
+	"fmt"
+
+	"github.com/olmaxmedical/olmax_go/router"
+)
+
+// Services - Bitmask to list services in native language
+const Services router.PluginMask = 12
+
+func init() {
+	b := &router.Plugin{
+		Name:     "specialties",
+		Run:      ListServices,
+		Validate: ValidateServices,
+	}
+	router.AddPlugin(b, Services)
+}
+
+// ValidateServices - Ensure the specialties entered exist in our map
+func ValidateServices(r *router.Request) error {
+	s := r.Request()
+	var errs []string
+	for _, entry := range s.PostFormValue("specialty") {
+		switch string(entry) {
+		case "acutepain":
+		case "anesthesiology":
+		case "bariatric":
+		case "cardiology":
+		case "chiropractic":
+		case "chronic":
+		case "critcare":
+		case "dermatology":
+		case "emergency":
+		case "endocrinology":
+		case "otolaringology":
+		case "familymedicine":
+		case "gastro":
+		case "headneck":
+		case "hematology":
+		case "hepatology":
+		case "hyperbaric":
+		case "immunology":
+		case "diseases":
+		case "internal":
+		case "neonatal":
+		case "nephrology":
+		case "neurology":
+		case "neurosurgery":
+		case "obstetrics":
+		case "occupational":
+		case "opthamology":
+		case "orthopedics":
+		case "palliative":
+		case "pediatrics":
+		case "podiatry":
+		case "pulmonology":
+		case "radiology":
+		case "radiation":
+		case "transplants":
+			continue
+		default:
+			errs = append(errs, fmt.Sprintf("Unknown entry: %q\n", entry))
+		}
+		if len(errs) > 0 {
+			return fmt.Errorf("%s", errs)
+		}
+	}
+	return nil
+}
+
+// ListServices - return a list of native language representations of our medical fields
+func ListServices(r *router.Request) map[string]interface{} {
+	p := r.Printer()
+	return map[string]interface{}{
+		"acutepain":      p.Sprintf("Acute Pain Medicine"),
+		"anesthesiology": p.Sprintf("Anesthesiology"),
+		"bariatric":      p.Sprintf("Bariatric Surgery"),
+		"cardiology":     p.Sprintf("Cardiology"),
+		"chiropractic":   p.Sprintf("Chiropractics"),
+		"chronic":        p.Sprintf("Chronic Pain"),
+		"critcare":       p.Sprintf("Critical Care"),
+		"dermatology":    p.Sprintf("Dermatology"),
+		"emergency":      p.Sprintf("Emergency Medicine"),
+		"endocrinology":  p.Sprintf("Endocrinology"),
+		"otolaringology": p.Sprintf("Ear Nose and Throat"),
+		"familymedicine": p.Sprintf("Family Medicine"),
+		"gastro":         p.Sprintf("Gastrointestinology"),
+		"headneck":       p.Sprintf("Head and Neck"),
+		"hematology":     p.Sprintf("Hematology and Oncology"),
+		"hepatology":     p.Sprintf("Hepatology"),
+		"hyperbaric":     p.Sprintf("Hyperbaric"),
+		"immunology":     p.Sprintf("Immunology"),
+		"diseases":       p.Sprintf("Infectious Diseases"),
+		"internal":       p.Sprintf("Internal Medicine"),
+		"neonatal":       p.Sprintf("Neonatology"),
+		"nephrology":     p.Sprintf("Nephrology"),
+		"neurology":      p.Sprintf("Neurology"),
+		"neurosurgery":   p.Sprintf("Neurosurgery"),
+		"obstetrics":     p.Sprintf("Obstetrics and Gynecology"),
+		"occupational":   p.Sprintf("Occupational Medicine"),
+		"opthamology":    p.Sprintf("Opthamology"),
+		"orthopedics":    p.Sprintf("Orthopedic Surgery"),
+		"palliative":     p.Sprintf("Palliative Care"),
+		"pediatrics":     p.Sprintf("Pediatrics"),
+		"podiatry":       p.Sprintf("Podiatry"),
+		"pulmonology":    p.Sprintf("Pulmonology"),
+		"radiology":      p.Sprintf("Radiology"),
+		"radiation":      p.Sprintf("Radiaton Oncology"),
+		"transplants":    p.Sprintf("Transplant Surgery"),
+	}
+}
--- a/plugins/specialties.go
+++ /dev/null
@@ -1,59 +1,0 @@
-package plugins
-
-import (
-	"github.com/olmaxmedical/olmax_go/router"
-)
-
-// ListServices - Bitmask to list services in native language
-const ListServices router.PluginMask = 2
-
-func init() {
-	b := &router.Plugin{
-		Name:     "specialties",
-		Run:      Specialties,
-		Validate: nil,
-	}
-	router.AddPlugin(b, ListServices)
-}
-
-// Specialties - return a list of native language representations of our medical fields
-func Specialties(r *router.Request) map[string]interface{} {
-	p := r.Printer()
-	return map[string]interface{}{
-		"acutepain":      p.Sprintf("Acute Pain Medicine"),
-		"anesthesiology": p.Sprintf("Anesthesiology"),
-		"bariatric":      p.Sprintf("Bariatric Surgery"),
-		"cardiology":     p.Sprintf("Cardiology"),
-		"chiropractic":   p.Sprintf("Chiropractics"),
-		"chronic":        p.Sprintf("Chronic Pain"),
-		"critcare":       p.Sprintf("Critical Care"),
-		"dermatology":    p.Sprintf("Dermatology"),
-		"emergency":      p.Sprintf("Emergency Medicine"),
-		"endocrinology":  p.Sprintf("Endocrinology"),
-		"otolaringology": p.Sprintf("Ear Nose and Throat"),
-		"familymedicine": p.Sprintf("Family Medicine"),
-		"gastro":         p.Sprintf("Gastrointestinology"),
-		"headneck":       p.Sprintf("Head and Neck"),
-		"hematology":     p.Sprintf("Hematology and Oncology"),
-		"hepatology":     p.Sprintf("Hepatology"),
-		"hyperbaric":     p.Sprintf("Hyperbaric"),
-		"immunology":     p.Sprintf("Immunology"),
-		"diseases":       p.Sprintf("Infectious Diseases"),
-		"internal":       p.Sprintf("Internal Medicine"),
-		"neonatal":       p.Sprintf("Neonatology"),
-		"nephrology":     p.Sprintf("Nephrology"),
-		"neurology":      p.Sprintf("Neurology"),
-		"neurosurgery":   p.Sprintf("Neurosurgery"),
-		"obstetrics":     p.Sprintf("Obstetrics and Gynecology"),
-		"occupational":   p.Sprintf("Occupational Medicine"),
-		"opthamology":    p.Sprintf("Opthamology"),
-		"orthopedics":    p.Sprintf("Orthopedic Surgery"),
-		"palliative":     p.Sprintf("Palliative Care"),
-		"pediatrics":     p.Sprintf("Pediatrics"),
-		"podiatry":       p.Sprintf("Podiatry"),
-		"pulmonology":    p.Sprintf("Pulmonology"),
-		"radiology":      p.Sprintf("Radiology"),
-		"radiation":      p.Sprintf("Radiaton Oncology"),
-		"transplants":    p.Sprintf("Transplant Surgery"),
-	}
-}
--- a/plugins/tokens.go
+++ b/plugins/tokens.go
@@ -1,6 +1,8 @@
 package plugins
 
 import (
+	"errors"
+
 	"github.com/google/uuid"
 	"github.com/olmaxmedical/olmax_go/router"
 )
@@ -8,22 +10,22 @@
 var tokens []string
 
 // SessionToken - An in-memory token to allow a client to track
-const SessionToken router.PluginMask = 4
+const SessionToken router.PluginMask = 13
 
 // FormToken - A database-persisted one time use token to relate forms to POST requests
-const FormToken router.PluginMask = 5
+const FormToken router.PluginMask = 14
 
 func init() {
 	b := &router.Plugin{
 		Name:     "sessionToken",
 		Run:      NewSessionToken,
-		Validate: nil,
+		Validate: ValidateToken,
 	}
 	router.AddPlugin(b, SessionToken)
 	c := &router.Plugin{
 		Name:     "formToken",
 		Run:      NewFormToken,
-		Validate: nil,
+		Validate: ValidateToken,
 	}
 	router.AddPlugin(c, FormToken)
 }
@@ -36,7 +38,7 @@
 }
 
 // NewFormToken returns a unique token associated with a client's form entry session
-// This will fall back to a database call
+// TODO(halfwit) - database
 func NewFormToken(r *router.Request) map[string]interface{} {
 	return map[string]interface{}{
 		"token": newToken(),
@@ -43,17 +45,22 @@
 	}
 }
 
-// TODO(halfwit) - Form plugins will use this
-func validateToken(token string) bool {
+// ValidateToken - Verify token exists
+func ValidateToken(r *router.Request) error {
+	s := r.Request()
+	if s == nil {
+		return errors.New("Invalid session")
+	}
+	token := s.PostFormValue("token")
 	for n, t := range tokens {
 		if token == t {
 			// n will always be at least 0, tokens at least 1
 			tokens[n] = tokens[len(tokens)-1]
 			tokens = tokens[:len(tokens)-1]
-			return true
+			return nil
 		}
 	}
-	return false
+	return errors.New("Invalid/missing token")
 }
 
 func newToken() string {
--- a/router/forms.go
+++ b/router/forms.go
@@ -2,41 +2,17 @@
 
 import (
 	"fmt"
-	"log"
-	"mime/multipart"
 	"net/http"
 
-	"github.com/olmaxmedical/olmax_go/db"
-	"github.com/olmaxmedical/olmax_go/email"
-	"github.com/olmaxmedical/olmax_go/session"
 	"golang.org/x/text/message"
 )
 
 var formlist map[string]*Form
 
-// After will go away when with plugins
-type After uint16
-
-const (
-	ValidateLogin After = 1 << iota
-	ValidateCountry
-	ValidateSpecialty
-	ValidateCountries
-	ValidateSpecialties
-	ValidateToken
-	WithOffer
-	Search
-	SendSignup
-	SendReset
-	SetPassword
-	EmailForm
-	AddAppointment
-)
-
 // Form - POST requests
 type Form struct {
 	Access    Access
-	After     After
+	After     PluginMask
 	Path      string
 	Redirect  string
 	Validator func(r *http.Request, p *message.Printer) []string
@@ -51,165 +27,26 @@
 	formlist[f.Path+".html"] = f
 }
 
-// This large ladder just adds conditional logic to forms for a more generic
-// Ideally, the *page abstraction will never leak into the form validation
 func parseform(p *Request, w http.ResponseWriter, r *http.Request) (*Form, []string) {
-	var errors, errs []string
-	var msg string
+	var errors []string
 	form, ok := formlist[p.path]
 	if !ok {
 		errors = append(errors, "No such page")
 		return nil, errors
 	}
-	/*
-		if form.After&ValidateToken != 0 {
-			t := r.PostFormValue("token")
-			if !validateToken(t) {
-				return nil, []string{p.printer.Sprint("Invalid form token")}
-			}
-		}*/
-	if form.After&WithOffer != 0 {
-		//token := r.PostFormValue("sessiontoken")
-		//offer := db.GetOffer(token)
-		//r.Form["sendto"] = []string{offer.Email}
-		//r.Form["offerid"] = []string{offer.Id}
-		r.Form["sendto"] = []string{
-			"michaelmisch1985@gmail.com",
-		}
-		r.Form["offerid"] = []string{
-			"void",
-		}
-	}
-	if errs = form.Validator(r, p.printer); len(errs) > 0 {
+	if errs := form.Validator(r, p.printer); len(errs) > 0 {
 		return nil, errs
 	}
-	if form.After&ValidateLogin != 0 {
-		if errs = validateLogin(p.printer, p.session, r); len(errs) > 0 {
-			return nil, errs
-		}
-	}
-	/*
-		if form.After&ValidateCountry != 0 {
-			c := r.PostFormValue("country")
-			if e = validateCountry(p.printer, c); e != "" {
-				errors = append(errors, e)
+	var errlist []error
+	for _, key := range pluginKey {
+		if (form.After&key) != 0 && pluginCache[key].Validate != nil {
+			if e := pluginCache[key].Validate(p); e != nil {
+				errlist = append(errlist, e)
 			}
 		}
-		if form.After&ValidateSpecialty != 0 {
-			s := r.PostFormValue("specialty")
-			if e = validateSpecialty(p.printer, s); e != "" {
-				errors = append(errors, e)
-			}
-		}
-		if form.After&ValidateCountries != 0 {
-			c := r.Form["country"]
-			if e = validateCountries(p.printer, c); e != "" {
-				errors = append(errors, e)
-			}
-		}
-		if form.After&ValidateSpecialties != 0 {
-			s := r.Form["specialty"]
-			if e = validateSpecialties(p.printer, s); e != "" {
-				errors = append(errors, e)
-			}
-		}*/
-	if form.After&SetPassword != 0 {
-		if errs = setPassword(p.printer, p.session, r); len(errs) > 0 {
-			errors = append(errors, errs...)
-		}
 	}
-	if len(errors) > 0 {
-		return nil, errors
+	if len(errlist) > 0 {
+		errors = append(errors, fmt.Sprint(errlist))
 	}
-	if form.After&SendSignup != 0 {
-		msg = signupEmail(p.printer, r)
-	}
-	if form.After&SendReset != 0 {
-		msg = resetPassword(p.printer, r)
-	}
-	if form.After&EmailForm != 0 {
-		msg = emailForm(p.printer, r)
-	}
-	/* TODO(halfwit) once database is live
-	if form.After&AddAppointment != 0 {
-		e = db.AddAppointment(r.Form)
-	}
-	if form.After&Search != 0 {
-		results = db.Search(r.Form)
-	}
-	*/
-	if msg != "" {
-		fmt.Fprintf(w, "%s\n", msg)
-		return nil, []string{"nil"}
-	}
 	return form, errors
-}
-
-func setPassword(p *message.Printer, us session.Session, r *http.Request) []string {
-	var errors []string
-	pass := r.PostFormValue("password")
-	repeat := r.PostFormValue("reenter")
-	if pass != repeat {
-		errors = append(errors, p.Sprint("Passwords do not match"))
-		return errors
-	}
-	token := r.PostFormValue("token")
-	if !db.FindTempEntry(token) {
-		errors = append(errors, p.Sprint("Session expired"))
-		return errors
-	}
-	db.UpdateUserPassword(token, pass)
-	return errors
-}
-
-func validateLogin(p *message.Printer, us session.Session, r *http.Request) []string {
-	var errors []string
-	user := r.PostFormValue("email")
-	pass := r.PostFormValue("pass")
-	if db.ValidateLogin(user, pass) {
-		us.Set("username", user)
-		us.Set("login", "true")
-		us.Set("role", db.UserRole(user))
-		return errors
-	}
-	errors = append(errors, p.Sprint("Invalid username or password"))
-	return errors
-}
-
-func signupEmail(p *message.Printer, r *http.Request) string {
-	first := r.PostFormValue("fname")
-	last := r.PostFormValue("lname")
-	address := r.PostFormValue("email")
-	pass := r.PostFormValue("pass")
-	email.SendSignup(first, last, address, pass, p)
-	return p.Sprint("An email has been sent to the provided email with instructions on finalizing your account creation")
-}
-
-func resetPassword(p *message.Printer, r *http.Request) string {
-	email.SendReset(r.PostFormValue("email"), p)
-	return p.Sprint("An email has been sent to the provided email with a link to reset your password")
-}
-
-func emailForm(p *message.Printer, r *http.Request) string {
-	r.ParseMultipartForm(10 << 20) // parse up to 10MB
-	if r.PostFormValue("name") == "" || r.PostFormValue("email") == "" {
-		return p.Sprint("Missing name or email in form. Please contact us at olmaxmedical@gmail.com")
-	}
-	if b, ok := r.Form["sendto"]; !ok || b[0] == "" {
-		return p.Sprint("Missing value for target email. Please contact us at olmaxmedical.gmail.com")
-	}
-	attachments := make(map[string]multipart.File)
-	m := r.MultipartForm
-	for _, headers := range m.File {
-		for _, header := range headers {
-			file, err := header.Open()
-			if err != nil { //non fatal, log any oddities and continue
-				log.Println(err)
-				continue
-			}
-			attachments[header.Filename] = file
-		}
-	}
-	email.SendForm(r.Form, p, attachments)
-	return p.Sprint("Your form has been submitted via email ")
 }
--- a/router/pages.go
+++ b/router/pages.go
@@ -14,11 +14,8 @@
 
 var pagecache map[string]*Page
 
-//var countrylist []Country
-
 func init() {
 	pagecache = make(map[string]*Page)
-	//countrylist = listcountries()
 }
 
 // Access defines the access rights for a specific page
@@ -92,8 +89,10 @@
 	r["header"] = header(p.printer, p.status)
 	r["footer"] = footer(p.printer)
 	r["basedir"] = getBaseDir(cache.Path)
+	// TODO(halfwit) test running each of these in a goroutine
+	// As n increases
 	for _, key := range pluginKey {
-		if (cache.Extra & key) != 0 {
+		if (cache.Extra&key) != 0 && pluginCache[key].Run != nil {
 			r[pluginCache[key].Name] = pluginCache[key].Run(p)
 		}
 	}
--- a/router/plugins.go
+++ b/router/plugins.go
@@ -17,7 +17,7 @@
 type Plugin struct {
 	Name     string
 	Run      func(p *Request) map[string]interface{}
-	Validate func() error
+	Validate func(p *Request) error
 }
 
 func init() {
@@ -24,8 +24,8 @@
 	pluginCache = make(map[PluginMask]*Plugin)
 }
 
-// ValidatePlugins - Run through each plugin
-// make sure that its mapping isn't redundant with any other
+// 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 {
@@ -32,10 +32,6 @@
 		if item.Validate == nil {
 			continue
 		}
-		err := item.Validate()
-		if err != nil {
-			errs = append(errs, err)
-		}
 		if (key & DEAD) != 0 {
 			errs = append(errs, fmt.Errorf("Error registering %s: Key requested already in use", item.Name))
 		}
@@ -45,7 +41,6 @@
 
 // AddPlugin - Add Plugin to map by key
 func AddPlugin(p *Plugin, key PluginMask) {
-
 	if pluginCache[key] != nil {
 		key |= DEAD
 	}
--- a/router/run.go
+++ b/router/run.go
@@ -64,6 +64,7 @@
 type Request struct {
 	printer *message.Printer
 	session session.Session
+	request *http.Request
 	user    string
 	status  string
 	path    string
@@ -80,6 +81,11 @@
 	return r.session
 }
 
+// Request - underlying http.Request for forms and such
+func (r *Request) Request() *http.Request {
+	return r.request
+}
+
 func (d *handle) normal(w http.ResponseWriter, r *http.Request) {
 	if r.URL.Path == "/" {
 		http.Redirect(w, r, "/index.html", 302)
@@ -89,6 +95,7 @@
 	p := &Request{
 		printer: userLang(r),
 		status:  status,
+		request: r,
 		user:    user,
 		role:    role,
 		session: us,
--- a/templates/help/provider.tpl
+++ b/templates/help/provider.tpl
@@ -11,7 +11,7 @@
 		</select>
 	        <button>{{.getStartedHeader}}</button>
 	    </form>
-	    <a href="profile.html">{{.getStartedLink}}</a> 
+	    <a href="TODO.html">{{.getStartedLink}}</a> 
 	    <h3>{{.providerWhy}} </h3>
 	    <p>{{.whyText}}</p>
 	    <h3>{{.control}}</h3>