linstrom/server/middlewareFixPasskeyPerms.go

233 lines
6.9 KiB
Go

package server
import (
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"time"
"github.com/rs/zerolog/hlog"
"gitlab.com/mstarongitlab/goutils/other"
"gitlab.com/mstarongitlab/linstrom/storage"
)
func forceCorrectPasskeyAuthFlowMiddleware(
handler http.Handler,
) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log := hlog.FromRequest(r)
// Don't fuck with the request if not intended for starting to register or login
if strings.HasSuffix(r.URL.Path, "loginFinish") {
log.Debug().Msg("Request to finish login method, doing nothing")
handler.ServeHTTP(w, r)
return
} else if strings.HasSuffix(r.URL.Path, "registerFinish") {
handler.ServeHTTP(w, r)
// Force unset session cookie here
w.Header().Del("Set-Cookie")
http.SetCookie(w, &http.Cookie{
Name: "sid",
Value: "",
Path: "",
MaxAge: 0,
Expires: time.UnixMilli(0),
})
return
} else if strings.HasSuffix(r.URL.Path, "loginBegin") {
fuckWithLoginRequest(w, r, handler)
} else if strings.HasSuffix(r.URL.Path, "registerBegin") {
fuckWithRegisterRequest(w, r, handler)
}
})
}
func fuckWithRegisterRequest(
w http.ResponseWriter,
r *http.Request,
nextHandler http.Handler,
) {
log := hlog.FromRequest(r)
log.Debug().Msg("Messing with register start request")
store := StorageFromRequest(w, r)
if store == nil {
return
}
cookie, cookieErr := r.Cookie("sid")
var username struct {
Username string `json:"username"`
}
body, _ := io.ReadAll(r.Body)
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
err := json.Unmarshal(body, &username)
if err != nil {
other.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest)
return
}
if cookieErr == nil {
// Already authenticated, overwrite username to logged in account's name
// Get session from cookie
log.Debug().Msg("Session token exists, force overwriting username of register request")
session, ok := store.GetSession(cookie.Value)
if !ok {
log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
other.HttpErr(
w,
HttpErrIdDbFailure,
"Passkey session missing",
http.StatusInternalServerError,
)
return
}
acc, err := store.FindAccountByPasskeyId(session.UserID)
// Assume account must exist if a session for it exists
if err != nil {
log.Error().Err(err).Msg("Failed to get account from passkey id from session")
other.HttpErr(
w,
HttpErrIdDbFailure,
"Failed to get authenticated account",
http.StatusInternalServerError,
)
return
}
// Replace whatever username may be given with username of logged in account
newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
// Assign to request
r.Body = io.NopCloser(strings.NewReader(newBody))
r.ContentLength = int64(len(newBody))
// And pass on
nextHandler.ServeHTTP(w, r)
} else {
// Not authenticated, ensure that no existing name is registered with
_, err = store.FindLocalAccountByUsername(username.Username)
switch err {
case nil:
// No error while getting account means account exists, refuse access
log.Info().
Str("username", username.Username).
Msg("Account with same name already exists, preventing login")
other.HttpErr(
w,
HttpErrIdAlreadyExists,
"Account with that name already exists",
http.StatusBadRequest,
)
case storage.ErrEntryNotFound:
// Didn't find account with that name, give access
log.Debug().
Str("username", username.Username).
Msg("No account with this username exists yet, passing through")
// Copy original body since previous reader hit EOF
r.Body = io.NopCloser(strings.NewReader(string(body)))
r.ContentLength = int64(len(body))
nextHandler.ServeHTTP(w, r)
default:
// Some other error, log it and return appropriate message
log.Error().
Err(err).
Str("username", username.Username).
Msg("Failed to check if account with username already exists")
other.HttpErr(
w,
HttpErrIdDbFailure,
"Failed to check if account with that name already exists",
http.StatusInternalServerError,
)
}
}
}
func fuckWithLoginRequest(
w http.ResponseWriter,
r *http.Request,
nextHandler http.Handler,
) {
log := hlog.FromRequest(r)
log.Debug().Msg("Messing with login start request")
store := StorageFromRequest(w, r)
if store == nil {
return
}
cookie, cookieErr := r.Cookie("sid")
var username struct {
Username string `json:"username"`
}
// Force ignore cookie for now
_ = cookieErr
var err error = errors.New("placeholder")
if err == nil {
// Someone is logged in, overwrite username with logged in account's one
body, _ := io.ReadAll(r.Body)
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
err := json.Unmarshal(body, &username)
if err != nil {
other.HttpErr(
w,
HttpErrIdBadRequest,
"Not a username json object",
http.StatusBadRequest,
)
return
}
session, ok := store.GetSession(cookie.Value)
if !ok {
log.Error().Str("session-id", cookie.Value).Msg("Passkey session missing")
other.HttpErr(
w,
HttpErrIdDbFailure,
"Passkey session missing",
http.StatusInternalServerError,
)
return
}
acc, err := store.FindAccountByPasskeyId(session.UserID)
// Assume account must exist if a session for it exists
if err != nil {
log.Error().Err(err).Msg("Failed to get account from passkey id from session")
other.HttpErr(
w,
HttpErrIdDbFailure,
"Failed to get authenticated account",
http.StatusInternalServerError,
)
return
}
// Replace whatever username may be given with username of logged in account
newBody := strings.ReplaceAll(string(body), username.Username, acc.Username)
// Assign to request
r.Body = io.NopCloser(strings.NewReader(newBody))
r.ContentLength = int64(len(newBody))
// And pass on
nextHandler.ServeHTTP(w, r)
} else {
// No one logged in, check if user exists to prevent creating a bugged account
body, _ := io.ReadAll(r.Body)
log.Debug().Bytes("body", body).Msg("Body of auth begin request")
err := json.Unmarshal(body, &username)
if err != nil {
other.HttpErr(w, HttpErrIdBadRequest, "Not a username json object", http.StatusBadRequest)
return
}
_, err = store.FindLocalAccountByUsername(username.Username)
switch err {
case nil:
// All good, account exists, keep going
// Do nothing in this branch
case storage.ErrEntryNotFound:
// Account doesn't exist, catch it
other.HttpErr(w, HttpErrIdNotFound, "Username not found", http.StatusNotFound)
return
default:
// catch db failures
log.Error().Err(err).Str("username", username.Username).Msg("Db failure while getting account")
other.HttpErr(w, HttpErrIdDbFailure, "Failed to check for account in db", http.StatusInternalServerError)
return
}
// Restore body as new reader of the same content
r.Body = io.NopCloser(strings.NewReader(string(body)))
nextHandler.ServeHTTP(w, r)
}
}