package frontend import ( "context" "errors" "fmt" "html/template" "log" "net/http" "time" "github.com/google/uuid" b "streifling.com/jason/cpolis/cmd/backend" ) type Session struct { ctx context.Context cancel context.CancelFunc cookie *http.Cookie User *b.User } func newSession(w http.ResponseWriter, c *b.Config, sessionExpiryChan chan<- string, user *b.User) *Session { sessionID := uuid.New().String() expires := time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours)) ctx, cancel := context.WithDeadline(context.Background(), expires) session := &Session{ ctx: ctx, cancel: cancel, cookie: &http.Cookie{ Name: "cpolis_session", Value: sessionID, Expires: expires, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, }, User: user, } go func() { <-session.ctx.Done() sessionExpiryChan <- session.cookie.Value session.cookie.Expires = time.Now() http.SetCookie(w, session.cookie) }() return session } func StartSessions() (map[string]*Session, chan string) { sessions := make(map[string]*Session) sessionExpiryChan := make(chan string) go func() { for sessionID := range sessionExpiryChan { delete(sessions, sessionID) } }() return sessions, sessionExpiryChan } // ManageSession is used for verifying that the user is logged in and returns // their session and an error. It also handles cases where the user is not // logged in. func ManageSession(w http.ResponseWriter, r *http.Request, c *b.Config, s map[string]*Session) (*Session, error) { tmpl, tmplErr := template.ParseFiles(c.WebDir+"/templates/index.html", c.WebDir+"/templates/login.html") cookie, err := r.Cookie("cpolis_session") if err != nil { if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil { return nil, fmt.Errorf("error executing template: %v", err) } return nil, errors.New("no cookie set") } session, ok := s[cookie.Value] if !ok { cookie.Expires = time.Now() http.SetCookie(w, cookie) if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil { return nil, fmt.Errorf("error executing template: %v", err) } return nil, errors.New("session does not exist") } session.cookie.Expires = time.Now().Add(time.Hour * time.Duration(c.CookieExpiryHours)) http.SetCookie(w, session.cookie) return session, nil } func Login(c *b.Config, db *b.DB, s map[string]*Session, sessionExpiryChan chan string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userName := r.PostFormValue("username") password := r.PostFormValue("password") id := db.GetID(userName) if id == 0 { http.Error(w, fmt.Sprintf("no such user: %v", userName), http.StatusBadRequest) return } if err := db.CheckPassword(id, password); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } user, err := db.GetUser(c, id) if err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } session := newSession(w, c, sessionExpiryChan, user) s[session.cookie.Value] = session http.SetCookie(w, session.cookie) tmpl, err := template.ParseFiles(c.WebDir + "/templates/hub.html") if err = template.Must(tmpl, err).ExecuteTemplate(w, "page-content", user); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } } } func Logout(c *b.Config, s map[string]*Session) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tmpl, tmplErr := template.ParseFiles(c.WebDir + "/templates/login.html") cookie, err := r.Cookie("cpolis_session") if err != nil { if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } } cookie.Expires = time.Now() http.SetCookie(w, cookie) session, ok := s[cookie.Value] if !ok { if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } } session.cancel() if err = template.Must(tmpl, tmplErr).ExecuteTemplate(w, "page-content", nil); err != nil { log.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } } }