mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-03 16:59:22 +02:00
userinfo: add webauthn buttons to user info page (#3075)
* userinfo: add webauthn buttons to user info page * use new buttons on original page * fix test
This commit is contained in:
parent
38c7089642
commit
35f697e491
14 changed files with 423 additions and 288 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pomerium/pomerium/authenticate/handlers/webauthn"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/log"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
|
@ -33,9 +34,10 @@ func ValidateOptions(o *config.Options) error {
|
|||
|
||||
// Authenticate contains data required to run the authenticate service.
|
||||
type Authenticate struct {
|
||||
cfg *authenticateConfig
|
||||
options *config.AtomicOptions
|
||||
state *atomicAuthenticateState
|
||||
cfg *authenticateConfig
|
||||
options *config.AtomicOptions
|
||||
state *atomicAuthenticateState
|
||||
webauthn *webauthn.Handler
|
||||
}
|
||||
|
||||
// New validates and creates a new authenticate service from a set of Options.
|
||||
|
@ -45,6 +47,7 @@ func New(cfg *config.Config, options ...Option) (*Authenticate, error) {
|
|||
options: config.NewAtomicOptions(),
|
||||
state: newAtomicAuthenticateState(newAuthenticateState()),
|
||||
}
|
||||
a.webauthn = webauthn.New(a.getWebauthnState)
|
||||
|
||||
state, err := newAuthenticateStateFromConfig(cfg)
|
||||
if err != nil {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/pomerium/csrf"
|
||||
|
||||
"github.com/pomerium/pomerium/authenticate/handlers"
|
||||
"github.com/pomerium/pomerium/authenticate/handlers/webauthn"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
|
@ -96,7 +97,7 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
|
|||
sr.Path("/").Handler(a.requireValidSignatureOnRedirect(a.userInfo))
|
||||
sr.Path("/sign_in").Handler(a.requireValidSignature(a.SignIn))
|
||||
sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
|
||||
sr.Path("/webauthn").Handler(webauthn.New(a.getWebauthnState))
|
||||
sr.Path("/webauthn").Handler(a.webauthn)
|
||||
sr.Path("/device-enrolled").Handler(httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
handlers.DeviceEnrolled().ServeHTTP(w, r)
|
||||
return nil
|
||||
|
@ -561,6 +562,8 @@ func (a *Authenticate) userInfo(w http.ResponseWriter, r *http.Request) error {
|
|||
groups = append(groups, pbDirectoryGroup)
|
||||
}
|
||||
|
||||
creationOptions, requestOptions, _ := a.webauthn.GetOptions(ctx)
|
||||
|
||||
handlers.UserInfo(handlers.UserInfoData{
|
||||
CSRFToken: csrf.Token(r),
|
||||
DirectoryGroups: groups,
|
||||
|
@ -568,7 +571,10 @@ func (a *Authenticate) userInfo(w http.ResponseWriter, r *http.Request) error {
|
|||
IsImpersonated: isImpersonated,
|
||||
Session: pbSession,
|
||||
User: pbUser,
|
||||
WebAuthnURL: urlutil.WebAuthnURL(r, authenticateURL, state.sharedKey, r.URL.Query()),
|
||||
|
||||
WebAuthnCreationOptions: creationOptions,
|
||||
WebAuthnRequestOptions: requestOptions,
|
||||
WebAuthnURL: urlutil.WebAuthnURL(r, authenticateURL, state.sharedKey, r.URL.Query()),
|
||||
}).ServeHTTP(w, r)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/user"
|
||||
"github.com/pomerium/pomerium/ui"
|
||||
"github.com/pomerium/webauthn"
|
||||
)
|
||||
|
||||
// UserInfoData is the data for the UserInfo page.
|
||||
|
@ -21,7 +22,10 @@ type UserInfoData struct {
|
|||
IsImpersonated bool
|
||||
Session *session.Session
|
||||
User *user.User
|
||||
WebAuthnURL string
|
||||
|
||||
WebAuthnCreationOptions *webauthn.PublicKeyCredentialCreationOptions
|
||||
WebAuthnRequestOptions *webauthn.PublicKeyCredentialRequestOptions
|
||||
WebAuthnURL string
|
||||
}
|
||||
|
||||
// ToJSON converts the data into a JSON map.
|
||||
|
@ -45,6 +49,8 @@ func (data UserInfoData) ToJSON() map[string]interface{} {
|
|||
if bs, err := protojson.Marshal(data.User); err == nil {
|
||||
m["user"] = json.RawMessage(bs)
|
||||
}
|
||||
m["webAuthnCreationOptions"] = data.WebAuthnCreationOptions
|
||||
m["webAuthnRequestOptions"] = data.WebAuthnRequestOptions
|
||||
m["webAuthnUrl"] = data.WebAuthnURL
|
||||
return m
|
||||
}
|
||||
|
|
|
@ -72,11 +72,50 @@ func New(getState StateProvider) *Handler {
|
|||
}
|
||||
}
|
||||
|
||||
// GetOptions returns the creation and request options for WebAuthn.
|
||||
func (h *Handler) GetOptions(ctx context.Context) (
|
||||
creationOptions *webauthn.PublicKeyCredentialCreationOptions,
|
||||
requestOptions *webauthn.PublicKeyCredentialRequestOptions,
|
||||
err error,
|
||||
) {
|
||||
state, err := h.getState(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return h.getOptions(ctx, state, webauthnutil.DefaultDeviceType)
|
||||
}
|
||||
|
||||
// ServeHTTP serves the HTTP handler.
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
httputil.HandlerFunc(h.handle).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) getOptions(ctx context.Context, state *State, deviceTypeParam string) (
|
||||
creationOptions *webauthn.PublicKeyCredentialCreationOptions,
|
||||
requestOptions *webauthn.PublicKeyCredentialRequestOptions,
|
||||
err error,
|
||||
) {
|
||||
// get the user information
|
||||
u, err := user.Get(ctx, state.Client, state.Session.GetUserId())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// get the device credentials
|
||||
knownDeviceCredentials, err := getKnownDeviceCredentials(ctx, state.Client, u.GetDeviceCredentialIds()...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// get the stored device type
|
||||
deviceType := webauthnutil.GetDeviceType(ctx, state.Client, deviceTypeParam)
|
||||
|
||||
creationOptions = webauthnutil.GenerateCreationOptions(state.SharedKey, deviceType, u)
|
||||
requestOptions = webauthnutil.GenerateRequestOptions(state.SharedKey, deviceType, knownDeviceCredentials)
|
||||
return creationOptions, requestOptions, nil
|
||||
}
|
||||
|
||||
func (h *Handler) handle(w http.ResponseWriter, r *http.Request) error {
|
||||
s, err := h.getState(r.Context())
|
||||
if err != nil {
|
||||
|
@ -351,24 +390,11 @@ func (h *Handler) handleView(w http.ResponseWriter, r *http.Request, state *Stat
|
|||
return errMissingDeviceType
|
||||
}
|
||||
|
||||
// get the user information
|
||||
u, err := user.Get(ctx, state.Client, state.Session.GetUserId())
|
||||
creationOptions, requestOptions, err := h.getOptions(ctx, state, deviceTypeParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the device credentials
|
||||
knownDeviceCredentials, err := getKnownDeviceCredentials(ctx, state.Client, u.GetDeviceCredentialIds()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get the stored device type
|
||||
deviceType := webauthnutil.GetDeviceType(ctx, state.Client, deviceTypeParam)
|
||||
|
||||
creationOptions := webauthnutil.GenerateCreationOptions(state.SharedKey, deviceType, u)
|
||||
requestOptions := webauthnutil.GenerateRequestOptions(state.SharedKey, deviceType, knownDeviceCredentials)
|
||||
|
||||
return ui.ServePage(w, r, "WebAuthnRegistration", map[string]interface{}{
|
||||
"creationOptions": creationOptions,
|
||||
"requestOptions": requestOptions,
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/pomerium/pomerium/authenticate/handlers/webauthn"
|
||||
"github.com/pomerium/pomerium/config"
|
||||
"github.com/pomerium/pomerium/internal/encoding"
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
|
@ -748,6 +749,7 @@ func TestAuthenticate_userInfo(t *testing.T) {
|
|||
directoryClient: new(mockDirectoryServiceClient),
|
||||
}),
|
||||
}
|
||||
a.webauthn = webauthn.New(a.getWebauthnState)
|
||||
r := httptest.NewRequest(tt.method, tt.url.String(), nil)
|
||||
state, err := tt.sessionStore.LoadSession(r)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue