Files
nhost/services/auth/go/controller/verify_elevate_webauthn.go

105 lines
2.9 KiB
Go

package controller
import (
"bytes"
"context"
"log/slog"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
"github.com/nhost/nhost/services/auth/go/api"
"github.com/nhost/nhost/services/auth/go/sql"
)
func (ctrl *Controller) VerifyElevateWebauthn( //nolint:ireturn
ctx context.Context,
request api.VerifyElevateWebauthnRequestObject,
) (api.VerifyElevateWebauthnResponseObject, error) {
logger := oapimw.LoggerFromContext(ctx)
if !ctrl.config.WebauthnEnabled {
logger.ErrorContext(ctx, "webauthn is disabled")
return ctrl.sendError(ErrDisabledEndpoint), nil
}
// Get the authenticated user from JWT context
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
if apiErr != nil {
return ctrl.sendError(apiErr), nil
}
credData, err := request.Body.Credential.Parse()
if err != nil {
logger.ErrorContext(ctx, "error parsing credential data", logError(err))
return ctrl.sendError(ErrInvalidRequest), nil
}
_, _, apiErr = ctrl.Webauthn.FinishLogin(
ctx,
credData,
ctrl.postElevateWebauthnVerifyUserHandler(ctx, user, credData, logger),
logger,
)
if apiErr != nil {
return ctrl.sendError(apiErr), nil
}
// Create new session with elevated claim
session, err := ctrl.wf.NewSession(
ctx,
user,
map[string]any{"x-hasura-auth-elevated": user.ID.String()},
logger,
)
if err != nil {
logger.ErrorContext(ctx, "failed to create elevated session", logError(err))
return ctrl.sendError(ErrInternalServerError), nil
}
return api.VerifyElevateWebauthn200JSONResponse{
Session: session,
}, nil
}
func (ctrl *Controller) postElevateWebauthnVerifyUserHandler(
ctx context.Context,
user sql.AuthUser,
response *protocol.ParsedCredentialAssertionData,
logger *slog.Logger,
) webauthn.DiscoverableUserHandler {
return func(_, _ []byte) (webauthn.User, error) {
// For elevate, we already know the user from the JWT context
keys, apiErr := ctrl.wf.GetUserSecurityKeys(ctx, user.ID, logger)
if apiErr != nil {
return nil, apiErr
}
creds, apiErr := webauthnCredentials(ctx, keys, logger)
if apiErr != nil {
return nil, apiErr
}
// Update flags from the webauthn response
for i, userCreds := range creds {
if bytes.Equal(response.RawID, userCreds.ID) {
userCreds.Flags = webauthn.CredentialFlags{
UserPresent: response.Response.AuthenticatorData.Flags.UserPresent(),
UserVerified: response.Response.AuthenticatorData.Flags.UserVerified(),
BackupEligible: response.Response.AuthenticatorData.Flags.HasBackupEligible(),
BackupState: response.Response.AuthenticatorData.Flags.HasBackupState(),
}
creds[i] = userCreds
}
}
return WebauthnUser{
ID: user.ID,
Name: user.DisplayName,
Email: user.Email.String,
Credentials: creds,
Discoverable: false,
}, nil
}
}