package handlers import ( "apjournal/internal/models" "apjournal/pkg/utils" "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "html/template" "net/http" "strings" "time" "golang.org/x/crypto/bcrypt" ) func abortWithError(w http.ResponseWriter, msg string) { tmpl := template.Must(template.ParseGlob("components/*.html")) tmpl.ExecuteTemplate(w, "error", msg) } func (h *Handlers) HandleSignup(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.PostFormValue("username") if username == "" { msg := "username not provided" h.log.Error(msg) abortWithError(w, msg) return } password := r.PostFormValue("password") if password == "" { msg := "password not provided" h.log.Error(msg) abortWithError(w, msg) return } // make sure username does not exists cleanName := utils.RemoveSpacesFromStr(username) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) // create user in db now := time.Now() nextMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).Add(time.Hour * 24) newUser := &models.UserScore{ Username: cleanName, Password: string(hashedPassword), BurnTime: nextMidnight, CreatedAt: now, } if err := h.repo.DBUserScoreCreate(newUser); err != nil { msg := "failed to create user" h.log.Error(msg, "user", newUser, "error", err) abortWithError(w, msg) return } // login user cookie, err := h.makeCookie(cleanName, r.RemoteAddr) if err != nil { h.log.Error("failed to login", "error", err) abortWithError(w, err.Error()) return } http.SetCookie(w, cookie) // http.Redirect(w, r, "/", 302) tmpl, err := template.ParseGlob("components/*.html") if err != nil { abortWithError(w, err.Error()) return } tmpl.ExecuteTemplate(w, "main", newUser) } func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.PostFormValue("username") if username == "" { msg := "username not provided" h.log.Error(msg) abortWithError(w, msg) return } password := r.PostFormValue("password") if password == "" { msg := "password not provided" h.log.Error(msg) abortWithError(w, msg) return } cleanName := utils.RemoveSpacesFromStr(username) tmpl, err := template.ParseGlob("components/*.html") if err != nil { abortWithError(w, err.Error()) return } userScore, err := h.repo.DBUserScoreGet(cleanName) if err != nil { h.log.Warn("failed to find user in db", "err", err) abortWithError(w, err.Error()) return } if err := bcrypt.CompareHashAndPassword( []byte(userScore.Password), []byte(password), ); err != nil { h.log.Warn("wrong password", "err", err) abortWithError(w, err.Error()) return } userScore.Actions, err = h.repo.DBActionList(cleanName) if err != nil { abortWithError(w, err.Error()) return } cookie, err := h.makeCookie(cleanName, r.RemoteAddr) if err != nil { h.log.Error("failed to login", "error", err) abortWithError(w, err.Error()) return } http.SetCookie(w, cookie) tmpl.ExecuteTemplate(w, "main", userScore) } func (h *Handlers) makeCookie(username string, remote string) (*http.Cookie, error) { // secret // Create a new random session token // sessionToken := xid.New().String() sessionToken := "token" expiresAt := time.Now().Add(time.Duration(h.cfg.SessionLifetime) * time.Second) // Set the token in the session map, along with the session information session := &models.Session{ Username: username, Expiry: expiresAt, } cookieName := "session_token" // hmac to protect cookies hm := hmac.New(sha256.New, []byte(h.cfg.CookieSecret)) hm.Write([]byte(cookieName)) hm.Write([]byte(sessionToken)) signature := hm.Sum(nil) // b64 enc to avoid non-ascii cookieValue := base64.URLEncoding.EncodeToString([]byte( string(signature) + sessionToken)) cookie := &http.Cookie{ Name: cookieName, Value: cookieValue, Secure: true, HttpOnly: true, SameSite: http.SameSiteNoneMode, Domain: h.cfg.ServerConfig.Host, } h.log.Info("check remote addr for cookie set", "remote", remote, "session", session) if strings.Contains(remote, "192.168.0") { // no idea what is going on cookie.Domain = "192.168.0.101" } // set ctx? // set user in session if err := h.cacheSetSession(sessionToken, session); err != nil { return nil, err } return cookie, nil } func (h *Handlers) cacheGetSession(key string) (*models.Session, error) { userSessionB, err := h.mc.Get(key) if err != nil { return nil, err } var us *models.Session if err := json.Unmarshal(userSessionB, &us); err != nil { return nil, err } return us, nil } func (h *Handlers) cacheSetSession(key string, session *models.Session) error { sesb, err := json.Marshal(session) if err != nil { return err } h.mc.Set(key, sesb) // expire in 10 min h.mc.Expire(key, 10*60) return nil }