diff --git a/auth/auth.go b/auth/auth.go index 17b6ad3..b8ce571 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -2,12 +2,26 @@ package auth import ( //nilfm.cc/git/goldbug/cookie + "time" ) +type User struct { + Name string + Pass string + Session string + LoginTime time.Time + LastSeen time.Time + + Data map[string]interface{} +} + type UserStore interface { - InitiateSession(user string, sessionId string) error - ValidateUser(user string, password string, sessionId string) (bool, error) + InitiateSession(user string, password string) (string, error) + ValidateUser(user string, sessionId string) (bool, error) EndSession(user string) error + AddUser(user string, password string) error + DeleteUser(user string) error + ChangePassword(user string, oldPassword string, newPassword string) error } func Login(user string, password string, userStore UserStore) (string, error) { diff --git a/indentalUserDB/indentalUserDB.go b/indentalUserDB/indentalUserDB.go new file mode 100644 index 0000000..588eded --- /dev/null +++ b/indentalUserDB/indentalUserDB.go @@ -0,0 +1,189 @@ +package indentalUserDB + +import ( + "time" + "nilfm.cc/git/goldbug/cookie" + "nilfm.cc/git/goldbug/auth" + "golang.org/x/crypto/bcrypt" + //"io" + "os" + "strings" + "fmt" + "errors" +) + +type IndentalUserDB struct { + Users map[string]*auth.User + Basis string +} + +func CreateIndentalUserDB(filePath string) *IndentalUserDB { + u, err := readDB(filePath) + if err == nil { + uMap := map[string]*auth.User{} + for _, usr := range u { + uMap[usr.Name] = usr + } + return &IndentalUserDB{ + Users: uMap, + Basis: filePath, + } + } else { + return &IndentalUserDB{ + Users: map[string]*auth.User{}, + Basis: filePath, + } + } +} + +func (self *IndentalUserDB) InitiateSession(user string, password string) (string, error) { + if _, exists := self.Users[user]; !exists { + return "", errors.New("User not in DB") + } + if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(password)) != nil { + return "", errors.New("Incorrect password") + } + sessionId := cookie.GenToken(64) + self.Users[user].Session = sessionId + writeDB(self.Basis, self.Users) + return sessionId, nil +} + +func (self *IndentalUserDB) ValidateUser(user string, sessionId string) (bool, error) { + if _, exists := self.Users[user]; !exists { + return false, errors.New("User not in DB") + } + + validated := self.Users[user].Session == sessionId + if validated { + self.Users[user].LastSeen = time.Now() + writeDB(self.Basis, self.Users) + } + + return validated, nil +} + +func (self *IndentalUserDB) EndSession(user string) error { + if _, exists := self.Users[user]; !exists { + return errors.New("User not in DB") + } + + self.Users[user].Session = "" + self.Users[user].LastSeen = time.Now() + writeDB(self.Basis, self.Users) + return nil +} + +func (self *IndentalUserDB) DeleteUser(user string) error { + if _, exists := self.Users[user]; !exists { + return errors.New("User not in DB") + } + + delete(self.Users, user) + writeDB(self.Basis, self.Users) + return nil +} + +func (self *IndentalUserDB) ChangePassword(user string, password string, oldPassword string) error { + if _, exists := self.Users[user]; !exists { + return errors.New("User not in DB") + } + if bcrypt.CompareHashAndPassword([]byte(self.Users[user].Pass), []byte(oldPassword)) != nil { + return errors.New("Incorrect password") + } + + hash, _ := bcrypt.GenerateFromPassword([]byte(password), 10) + self.Users[user].Pass = string(hash[:]) + writeDB(self.Basis, self.Users) + return nil +} + +func (self *IndentalUserDB) AddUser(user string, password string) error{ + if _, exists := self.Users[user]; exists { + return errors.New("User already in DB") + } + + hash, _ := bcrypt.GenerateFromPassword([]byte(password), 10) + + self.Users[user] = &auth.User{ + Name: user, + Pass: string(hash[:]), + LastSeen: time.UnixMicro(0), + LoginTime: time.UnixMicro(0), + Session: "", + } + writeDB(self.Basis, self.Users) + return nil; +} + +const timeFmt = "2006-01-02T15:04Z" + +func readDB(filePath string) (map[string]*auth.User, error) { + f, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + data := string(f[:]) + users := map[string]*auth.User{} + + lines := strings.Split(data, "\n") + var name string + var pass string + var session string + var loginTime time.Time + var lastSeen time.Time + procFields := 0 + for _, l := range lines { + if !strings.HasPrefix(l, " ") { + name = l + procFields++ + } else { + kvp := strings.Split(l, ":") + k := strings.TrimSpace(kvp[0]) + v := strings.TrimSpace(kvp[1]) + switch k { + case "pass": + pass = v + case "session": + session = v + case "loginTime": + loginTime, _ = time.Parse(timeFmt, v) + case "lastSeen": + lastSeen, _ = time.Parse(timeFmt, v) + } + procFields++ + if procFields == 5 { + users[name] = &auth.User{ + Name: name, + Pass: pass, + Session: session, + LoginTime: loginTime, + LastSeen: lastSeen, + } + procFields = 0 + } + } + } + return users, nil +} + +func writeDB(filePath string, users map[string]*auth.User) error { + f, err := os.Create(filePath) + if err != nil { + return err + } + + defer f.Close() + + for _, user := range users { + f.WriteString(fmt.Sprintf("%s:\n pass: %s\n session: %s\n loginTime: %s\n lastSeen: %s\n", + user.Name, + user.Pass, + user.Session, + user.LoginTime, + user.LastSeen)); + } + f.Sync() + return nil +} diff --git a/renderer/renderer.go b/renderer/renderer.go index 79860cc..031986e 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -7,8 +7,8 @@ import ( "encoding/xml" ) -func Template(t string) http.Handler { - tmpl := template.Must(template.ParseFiles(t)) +func Template(t ...string) http.Handler { + tmpl := template.Must(template.ParseFiles(t...)) handlerFunc := func(w http.ResponseWriter, req *http.Request) { tmpl.Execute(w, req) diff --git a/router/router.go b/router/router.go index d984ad0..f0ba53b 100644 --- a/router/router.go +++ b/router/router.go @@ -10,6 +10,7 @@ import ( "path" "os" "errors" + "context" ) type Router struct { @@ -24,7 +25,6 @@ type Router struct { StaticPaths map[string]string } - type Route struct { path *regexp.Regexp handlerMap map[string]http.Handler @@ -125,7 +125,7 @@ func (self *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { } for method, handler := range r.handlerMap { if method == req.Method { - /* Parse the form and add the params to it */ + /* Parse the form and add the params to the context */ req.ParseForm() ProcessParams(req, params) /* handle the request! */ @@ -142,9 +142,7 @@ func (self *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { *******************/ func ProcessParams(req *http.Request, params map[string]string) { - for key, value := range params { - req.Form.Add(key, value) - } + *req = *req.WithContext(context.WithValue(req.Context(), "params", params)) } func (self *Route) Match(r *http.Request) map[string]string { @@ -165,8 +163,10 @@ func (self *Route) Match(r *http.Request) map[string]string { 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) + params := map[string]string{ + "ErrorCode": strconv.Itoa(code), + "ErrorMessage": errMsg, + } + ProcessParams(req, params) self.Fallback.Execute(w, req) } diff --git a/userDB.ndtl b/userDB.ndtl new file mode 100644 index 0000000..0bf3576 --- /dev/null +++ b/userDB.ndtl @@ -0,0 +1,5 @@ +nilix: + pass: $2a$10$.Y59TRn/.qBjT8KwleyrBePsC34EuPzrRlQr014bjEKuLoUCWDMtO + session: eMOrLtCvjo_DTV_NqDLicJOugUALtiCIjdvPuzY@O!TOAAzunOs!jnCvCv#sQFxR + loginTime: 1969-12-31 17:00:00 -0700 MST + lastSeen: 1969-12-31 17:00:00 -0700 MST