333 lines
10 KiB
Go
333 lines
10 KiB
Go
package routes
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"sunshine.industries/some-automoderation/rooms"
|
|
"sunshine.industries/some-automoderation/sessions"
|
|
)
|
|
|
|
type MainData struct {
|
|
IsRoomExisting bool
|
|
}
|
|
|
|
// function to register all http routes for servicing auth pages and logic
|
|
func registerLoginRoutes(
|
|
templateFs *embed.FS,
|
|
sessionSM sessions.SessionManagement,
|
|
roomsM rooms.RoomManager,
|
|
) {
|
|
// login page
|
|
http.HandleFunc(loginPath, func(w http.ResponseWriter, r *http.Request) {
|
|
renderLoginPage(w)
|
|
})
|
|
http.HandleFunc("/login/join", joinRoomHandler(templateFs, sessionSM, roomsM))
|
|
http.HandleFunc("/login/create", createRoomHandler(templateFs, sessionSM, roomsM))
|
|
http.HandleFunc("/login/room-name-check", checkRoomName(templateFs, roomsM))
|
|
http.Handle("/logout", authedPageMiddleware(sessionSM,
|
|
http.HandlerFunc(logoutRoute(sessionSM, roomsM))))
|
|
}
|
|
|
|
const authCookieName = "auth"
|
|
const loginPath = "/login"
|
|
|
|
type contextKey string
|
|
|
|
func getContextSession(ctx context.Context) (sessions.SessionData, bool) {
|
|
val := ctx.Value(contextKey("session"))
|
|
if val == nil {
|
|
return sessions.SessionData{}, false
|
|
} else {
|
|
return ctx.Value(contextKey("session")).(sessions.SessionData), true
|
|
}
|
|
}
|
|
|
|
// checks sessionId from cookie
|
|
// when non-zero session found - pass to next http.Hander
|
|
// when no session available - render same as login page and redirect to /
|
|
func authedPageMiddleware(
|
|
sessionsM sessions.SessionManagement, next http.Handler,
|
|
) http.Handler {
|
|
returnNoAccess := func(w http.ResponseWriter, r *http.Request) {
|
|
log.Printf("auth middle > restricting access to %s", r.URL.Path)
|
|
w.Header().Add("HX-Replace-Url", loginPath)
|
|
renderLoginPage(w)
|
|
}
|
|
rerturnSuccess := func(w http.ResponseWriter, r *http.Request, session sessions.SessionData) {
|
|
ctx := context.WithValue(r.Context(), contextKey("session"), session)
|
|
log.Printf("auth middle > allowing access to %s for %+v", r.URL.Path, session)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
sessionCookie, err := r.Cookie(authCookieName)
|
|
if err != nil {
|
|
returnNoAccess(w, r)
|
|
return
|
|
}
|
|
// TODO log here, why i get 'error readin 0'
|
|
sessionId, err := strconv.Atoi(sessionCookie.Value)
|
|
if err != nil {
|
|
returnNoAccess(w, r)
|
|
return
|
|
}
|
|
session := sessionsM.Get(sessionId)
|
|
if session == (sessions.SessionData{}) {
|
|
returnNoAccess(w, r)
|
|
return
|
|
} else {
|
|
rerturnSuccess(w, r, session)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
func renderLoginPage(w http.ResponseWriter) {
|
|
baseFile := "templates/base.gohtml"
|
|
templFile := "templates/login.gohtml"
|
|
tmpl := template.Must(template.ParseFS(templateFs, templFile, baseFile))
|
|
data := pageData{
|
|
Base: baseData{
|
|
Title: "login",
|
|
},
|
|
Content: MainData{
|
|
IsRoomExisting: false,
|
|
},
|
|
}
|
|
|
|
err := tmpl.ExecuteTemplate(w, "full-page", data)
|
|
if err != nil {
|
|
log.Printf("my error in executing template, huh\n %s", err)
|
|
}
|
|
}
|
|
|
|
func createRoomHandler(templateFs *embed.FS,
|
|
sessionSM sessions.SessionManagement,
|
|
roomsM rooms.RoomManager,
|
|
) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
// TODO return error notice somehow
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
roomName := r.PostFormValue("roomName")
|
|
_, exists, _ := roomsM.Get(roomName)
|
|
if exists {
|
|
// TODO return anouther error notice
|
|
log.Printf("error, room name occupied %s", roomName)
|
|
return
|
|
}
|
|
person := rooms.Person{
|
|
Id: rooms.RandomPersonId(),
|
|
Name: r.PostFormValue("personalName"),
|
|
PasswordHash: r.PostFormValue("personalPassword"), // TODO hash the password, not to store
|
|
}
|
|
newRoom := rooms.Room{
|
|
Name: roomName,
|
|
PasswordHash: r.PostFormValue("roomPassword"), // TODO hash the password, not to store
|
|
AdminIds: []rooms.PersonId{person.Id},
|
|
Paricipants: []rooms.PersonId{person.Id},
|
|
AllKnownPeople: map[rooms.PersonId]rooms.Person{
|
|
person.Id: person},
|
|
}
|
|
err = roomsM.Save(newRoom)
|
|
if err != nil {
|
|
log.Printf("what am i to do? error saving room %s", err)
|
|
// todo return error notice somehow
|
|
}
|
|
newSession, err := sessionSM.Save(r.Context(), newRoom.Name, person.Id)
|
|
if err != nil {
|
|
log.Printf("what am i to do? error saving session %s", err)
|
|
// todo return error notice somehow
|
|
}
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: authCookieName,
|
|
Value: fmt.Sprint(newSession.SessionId),
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
Path: "/",
|
|
})
|
|
w.Header().Add("HX-Redirect", fmt.Sprintf("/room/%s", newRoom.Name))
|
|
}
|
|
}
|
|
|
|
// checking whether the room name already exists
|
|
// toggle button between Join or Create
|
|
func checkRoomName(templateFs *embed.FS,
|
|
roomsM rooms.RoomManager,
|
|
) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
roomName := r.PostFormValue("roomName")
|
|
_, isFound, err := roomsM.Get(roomName)
|
|
if err != nil {
|
|
log.Printf("/login/room-name-check error finding room %s\n", err)
|
|
}
|
|
var templFile = "templates/login.gohtml"
|
|
tmpl := template.Must(template.ParseFS(templateFs, templFile))
|
|
err = tmpl.ExecuteTemplate(w, "formButton", isFound)
|
|
}
|
|
}
|
|
|
|
func joinRoomHandler(templateFs *embed.FS,
|
|
sessionSM sessions.SessionManagement,
|
|
roomsM rooms.RoomManager,
|
|
) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
|
|
roomName := r.PostFormValue("roomName")
|
|
roomPass := r.PostFormValue("roomPassword")
|
|
personName := r.PostFormValue("personalName")
|
|
personPass := r.PostFormValue("personalPassword")
|
|
|
|
// a) get room data
|
|
room, _, err := roomsM.Get(roomName)
|
|
if err != nil {
|
|
log.Printf("/login/join error getting room %s", roomName)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
// TODO render error to be put in error place
|
|
return // no such room
|
|
} else {
|
|
log.Printf("/login/submit found room %+v", room)
|
|
}
|
|
|
|
// b) check if room password OK
|
|
if room.PasswordHash != roomPass {
|
|
log.Printf("/login/join bad room pass for %+v", room)
|
|
w.WriteHeader(http.StatusForbidden)
|
|
// TODO render error to be put in error place
|
|
return // bad room password
|
|
}
|
|
|
|
var person rooms.Person
|
|
for _, participant := range room.AllKnownPeople {
|
|
if participant.Name == personName {
|
|
person = participant
|
|
}
|
|
}
|
|
// c) check if such person exists,
|
|
// knownPerson, found :=
|
|
// check the password
|
|
if (person != rooms.Person{}) && person.PasswordHash != personPass {
|
|
log.Printf("/login/join bad person pass for %+s", person.Name)
|
|
w.WriteHeader(http.StatusForbidden)
|
|
// TODO render error to be put in error place
|
|
return // bad person password
|
|
}
|
|
// person joining for thethe first time
|
|
if (person == rooms.Person{}) {
|
|
log.Printf("/login/join room pass correct, new person joins")
|
|
// creating a new person with provided password hash
|
|
person = rooms.Person{
|
|
Name: personName,
|
|
PasswordHash: personPass,
|
|
Id: rooms.RandomPersonId(),
|
|
}
|
|
err := roomsM.Update(r.Context(), room.Name, func(fromRoom rooms.Room) (toRoom rooms.Room) {
|
|
log.Printf("/login/join about to modify room %+v", fromRoom)
|
|
toRoom = fromRoom
|
|
toRoom.AllKnownPeople[person.Id] = person
|
|
log.Printf("/login/join will save %+v", toRoom)
|
|
return toRoom
|
|
})
|
|
if err != nil {
|
|
log.Printf("/login/join problem adding person to room %+v", person.Name)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
// TODO render error to be put in error place
|
|
// with message try again
|
|
return // error adding New person
|
|
}
|
|
}
|
|
// TODO handle context and cancells, with separate function that writeds new updated room
|
|
// now we have room and person, can create a session
|
|
// and we've checked password
|
|
|
|
err = roomsM.Update(r.Context(), room.Name, func(fromRoom rooms.Room) (toRoom rooms.Room) {
|
|
toRoom = fromRoom
|
|
if !slices.Contains(toRoom.Paricipants, person.Id) {
|
|
// consequtive login from additional devices
|
|
toRoom.Paricipants = append(toRoom.Paricipants, person.Id)
|
|
}
|
|
return toRoom
|
|
})
|
|
if err != nil {
|
|
log.Printf("/login/join problem sitting joining person at a table %+v", person.Name)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
// TODO render error to be put in error place
|
|
// with message try again
|
|
return // error sitting a new person
|
|
}
|
|
|
|
newSession, err := sessionSM.Save(r.Context(), room.Name, person.Id)
|
|
if err != nil {
|
|
log.Printf("/login/submit > error saving session %s", err)
|
|
}
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: authCookieName,
|
|
Value: fmt.Sprint(newSession.SessionId),
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
Path: "/",
|
|
})
|
|
log.Printf("is is %+v. room things %s & %s, personal things %s and %s. \n found room %+v",
|
|
newSession, roomName, roomPass, personName, personPass, room,
|
|
)
|
|
// TODO render what? index page with some data passed?
|
|
// or, what? i could just redirect to / for now
|
|
w.Header().Add("HX-Redirect", fmt.Sprintf("/room/%s", newSession.RoomId))
|
|
}
|
|
}
|
|
|
|
func logoutRoute(sessionSM sessions.SessionManagement,
|
|
roomsM rooms.RoomManager) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
session, found := getContextSession(r.Context())
|
|
if !found {
|
|
log.Printf("/logout session not found, it's ok.")
|
|
// though this is under middleware for now, should be impossible
|
|
// TODO return error i guess
|
|
w.Header().Add("HX-Redirect", "/")
|
|
return
|
|
}
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: authCookieName,
|
|
Expires: time.Now().Add(-time.Hour),
|
|
Value: "",
|
|
Secure: true,
|
|
HttpOnly: true,
|
|
Path: "/",
|
|
})
|
|
err := sessionSM.Remove(r.Context(), session.SessionId)
|
|
if err != nil {
|
|
log.Printf("/logout error deleting session: %s", err)
|
|
}
|
|
err = roomsM.Update(r.Context(), session.RoomId, func(fromRoom rooms.Room) (toRoom rooms.Room) {
|
|
toRoom = fromRoom
|
|
toRoom.PersonToStandUpFromTable(session.PersonId)
|
|
return toRoom
|
|
})
|
|
if err != nil {
|
|
log.Printf("/logout error removing person from table: %s", err)
|
|
}
|
|
|
|
log.Printf("/logout deleting session %+v", session)
|
|
|
|
w.Header().Add("HX-Redirect", "/")
|
|
}
|
|
}
|