cpolis/cmd/frontend/sessions.go
2025-01-14 21:27:24 +01:00

169 lines
4.4 KiB
Go

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
Article *b.Article
}
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
}
}
}