package handlers import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "gralias/models" "gralias/pkg/cache" "gralias/utils" "html/template" "net/http" "strings" ) func abortWithError(w http.ResponseWriter, msg string) { w.WriteHeader(200) // must be 200 for htmx to replace components tmpl := template.Must(template.ParseGlob("components/*.html")) if err := tmpl.ExecuteTemplate(w, "error", msg); err != nil { log.Error("failed to execute error template", "error", err) } } func HandleNameCheck(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { log.Error("failed to parse form", "error", err) abortWithError(w, err.Error()) return } username := r.PostFormValue("username") if username == "" { msg := "username not provided" log.Error(msg) abortWithError(w, msg) return } cleanName := utils.RemoveSpacesFromStr(username) // allNames := getAllNames() allNames, err := repo.PlayerListNames() if err != nil { abortWithError(w, err.Error()) return } log.Info("names check", "taken_names", allNames, "trying_name", cleanName) tmpl, err := template.ParseGlob("components/*.html") if err != nil { abortWithError(w, err.Error()) return } if utils.StrInSlice(cleanName, allNames) { err := fmt.Errorf("name: %s already taken", cleanName) log.Warn("already taken", "error", err) if err := tmpl.ExecuteTemplate(w, "namecheck", 2); err != nil { log.Error("failed to execute namecheck template", "error", err) } return } if err := tmpl.ExecuteTemplate(w, "namecheck", 0); err != nil { log.Error("failed to execute namecheck template", "error", err) } } func HandleFrontLogin(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { abortWithError(w, err.Error()) return } username := r.PostFormValue("username") if username == "" { msg := "username not provided" log.Error(msg) abortWithError(w, msg) return } roomID := r.PostFormValue("room_id") // make sure username does not exists cleanName := utils.RemoveSpacesFromStr(username) // login user cookie, err := makeCookie(cleanName, r.RemoteAddr) if err != nil { log.Error("failed to login", "error", err) abortWithError(w, err.Error()) return } http.SetCookie(w, cookie) // check if that user was already in db // userstate, err := loadState(cleanName) userstate, err := repo.PlayerGetByName(cleanName) if err != nil || userstate == nil { userstate = models.InitPlayer(cleanName) } fi := &models.FullInfo{ State: userstate, } // check if room_id provided and exists if roomID != "" { log.Debug("got room_id in login", "room_id", roomID) // room, err := getRoomByID(roomID) room, err := repo.RoomGetByID(r.Context(), roomID) if err != nil { abortWithError(w, err.Error()) return } // room.PlayerList = append(room.PlayerList, fi.State.Username) fi.Room = room fi.List = nil fi.State.RoomID = room.ID repo.PlayerSetRoomID(fi.State.Username, room.ID) // repo.RoomUpdate() // save full info instead // if err := saveFullInfo(fi); err != nil { // abortWithError(w, err.Error()) // return // } } else { log.Debug("no room_id in login") // fi.List = listRooms(false) fi.List, err = repo.RoomList(r.Context()) if err != nil { abortWithError(w, err.Error()) return } // save state to cache // if err := saveState(cleanName, userstate); err != nil { if err := repo.PlayerUpdate(userstate); err != nil { // if err := saveFullInfo(fi); err != nil { log.Error("failed to save state", "error", err) abortWithError(w, err.Error()) return } } // if err := tmpl.ExecuteTemplate(w, "base", fi); err != nil { // log.Error("failed to execute base template", "error", err) // } http.Redirect(w, r, "/", 302) } func makeCookie(username string, remote string) (*http.Cookie, error) { // secret // Create a new random session token // sessionToken := xid.New().String() sessionToken := "sessionprefix_" + username // expiresAt := time.Now().Add(time.Duration(cfg.SessionLifetime) * time.Second) // Set the token in the session map, along with the session information session := &models.Session{ Username: username, CookieToken: sessionToken, Lifetime: uint32(cfg.SessionLifetime / 60), } cookieName := "session_token" // hmac to protect cookies hm := hmac.New(sha256.New, []byte(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, } log.Info("check remote addr for cookie set", "remote", remote, "session", session) if strings.Contains(remote, "192.168.0") { cookie.Domain = "192.168.0.100" cookie.SameSite = http.SameSiteLaxMode cookie.Secure = false log.Info("changing cookie domain", "domain", cookie.Domain) } // set ctx? // set user in session if err := cacheSetSession(sessionToken, session); err != nil { return nil, err } return cookie, nil } func cacheGetSession(key string) (*models.Session, error) { userSessionB, err := cache.MemCache.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 cacheSetSession(key string, session *models.Session) error { sesb, err := json.Marshal(session) if err != nil { return err } cache.MemCache.Set(key, sesb) cache.MemCache.Expire(key, cfg.SessionLifetime) return nil }