From 1dbdec1905adcc858b1ada0412f8b26b0c7e140f Mon Sep 17 00:00:00 2001 From: mStar Date: Sun, 20 Oct 2024 20:05:13 +0200 Subject: [PATCH] Copy passkey auth fix from Mk plugin repo --- server/constants.go | 3 + server/middlewareFixPasskeyPerms.go | 233 ++++++++++++++++++++++++ server/{auth.go => routerLinstromFe.go} | 0 server/server.go | 15 +- 4 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 server/middlewareFixPasskeyPerms.go rename server/{auth.go => routerLinstromFe.go} (100%) diff --git a/server/constants.go b/server/constants.go index d9ba58b..99cfdc6 100644 --- a/server/constants.go +++ b/server/constants.go @@ -15,4 +15,7 @@ const ( HttpErrIdDbFailure HttpErrIdNotAuthenticated HttpErrIdJsonMarshalFail + HttpErrIdBadRequest + HttpErrIdAlreadyExists + HttpErrIdNotFound ) diff --git a/server/middlewareFixPasskeyPerms.go b/server/middlewareFixPasskeyPerms.go new file mode 100644 index 0000000..8bdf2a7 --- /dev/null +++ b/server/middlewareFixPasskeyPerms.go @@ -0,0 +1,233 @@ +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) + } +} diff --git a/server/auth.go b/server/routerLinstromFe.go similarity index 100% rename from server/auth.go rename to server/routerLinstromFe.go diff --git a/server/server.go b/server/server.go index 47f7882..4082ad1 100644 --- a/server/server.go +++ b/server/server.go @@ -7,6 +7,7 @@ import ( "github.com/mstarongithub/passkey" "github.com/rs/zerolog/log" "gitlab.com/mstarongitlab/goutils/other" + "gitlab.com/mstarongitlab/linstrom/config" "gitlab.com/mstarongitlab/linstrom/storage" ) @@ -29,7 +30,13 @@ func NewServer(store *storage.Storage, pkey *passkey.Passkey, reactiveFS, static func buildRootHandler(pkey *passkey.Passkey, reactiveFS, staticFS fs.FS) http.Handler { mux := http.NewServeMux() - pkey.MountRoutes(mux, "/webauthn/") + mux.Handle( + "/webauthn/", + http.StripPrefix( + "/webauthn", + forceCorrectPasskeyAuthFlowMiddleware(buildPasskeyAuthRouter(pkey)), + ), + ) mux.Handle("/", setupFrontendRouter(reactiveFS, staticFS)) mux.Handle("/pk/", http.StripPrefix("/pk", http.FileServer(http.Dir("pk-auth")))) mux.HandleFunc("/alive", isAliveHandler) @@ -63,6 +70,12 @@ func buildRootHandler(pkey *passkey.Passkey, reactiveFS, staticFS fs.FS) http.Ha return mux } +func buildPasskeyAuthRouter(pkey *passkey.Passkey) http.Handler { + router := http.NewServeMux() + pkey.MountRoutes(router, "/") + return router +} + func (s *Server) Start(addr string) error { log.Info().Str("addr", addr).Msg("Starting server") return http.ListenAndServe(addr, s.router)