Copy passkey auth fix from Mk plugin repo
This commit is contained in:
parent
90667d96c7
commit
1dbdec1905
4 changed files with 250 additions and 1 deletions
|
@ -15,4 +15,7 @@ const (
|
|||
HttpErrIdDbFailure
|
||||
HttpErrIdNotAuthenticated
|
||||
HttpErrIdJsonMarshalFail
|
||||
HttpErrIdBadRequest
|
||||
HttpErrIdAlreadyExists
|
||||
HttpErrIdNotFound
|
||||
)
|
||||
|
|
233
server/middlewareFixPasskeyPerms.go
Normal file
233
server/middlewareFixPasskeyPerms.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue