mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 07:57:32 +00:00
chore: move the go code into a subfolder
This commit is contained in:
82
apps/api/internal/webauthn/client.go
Normal file
82
apps/api/internal/webauthn/client.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/descope/virtualwebauthn"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
rp virtualwebauthn.RelyingParty
|
||||
auth virtualwebauthn.Authenticator
|
||||
authVerifyUser virtualwebauthn.Authenticator
|
||||
credential virtualwebauthn.Credential
|
||||
}
|
||||
|
||||
func NewClient(name, domain, origin string) *Client {
|
||||
rp := virtualwebauthn.RelyingParty{
|
||||
Name: name,
|
||||
ID: domain,
|
||||
Origin: origin,
|
||||
}
|
||||
return &Client{
|
||||
rp: rp,
|
||||
auth: virtualwebauthn.NewAuthenticatorWithOptions(virtualwebauthn.AuthenticatorOptions{
|
||||
UserNotVerified: true,
|
||||
}),
|
||||
authVerifyUser: virtualwebauthn.NewAuthenticator(),
|
||||
credential: virtualwebauthn.NewCredential(virtualwebauthn.KeyTypeEC2),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) CreateAttestationResponse(optionsPb *structpb.Struct) (*structpb.Struct, error) {
|
||||
options, err := protojson.Marshal(optionsPb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAttestationResponse: %w", err)
|
||||
}
|
||||
attestationResponse, err := c.CreateAttestationResponseData(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := new(structpb.Struct)
|
||||
err = protojson.Unmarshal(attestationResponse, resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAttestationResponse: %w", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateAttestationResponseData(options []byte) ([]byte, error) {
|
||||
parsedAttestationOptions, err := virtualwebauthn.ParseAttestationOptions(string(options))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAttestationResponse: %w", err)
|
||||
}
|
||||
return []byte(virtualwebauthn.CreateAttestationResponse(
|
||||
c.rp, c.auth, c.credential, *parsedAttestationOptions,
|
||||
)), nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateAssertionResponse(optionsPb *structpb.Struct, verifyUser bool) (*structpb.Struct, error) {
|
||||
options, err := protojson.Marshal(optionsPb)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAssertionResponse: %w", err)
|
||||
}
|
||||
parsedAssertionOptions, err := virtualwebauthn.ParseAssertionOptions(string(options))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAssertionResponse: %w", err)
|
||||
}
|
||||
authenticator := c.auth
|
||||
if verifyUser {
|
||||
authenticator = c.authVerifyUser
|
||||
}
|
||||
resp := new(structpb.Struct)
|
||||
err = protojson.Unmarshal([]byte(virtualwebauthn.CreateAssertionResponse(
|
||||
c.rp, authenticator, c.credential, *parsedAssertionOptions,
|
||||
)), resp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("webauthn.Client.CreateAssertionResponse: %w", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
91
apps/api/internal/webauthn/converter.go
Normal file
91
apps/api/internal/webauthn/converter.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
func WebAuthNsToCredentials(ctx context.Context, webAuthNs []*domain.WebAuthNToken, rpID string) []webauthn.Credential {
|
||||
creds := make([]webauthn.Credential, 0)
|
||||
for _, webAuthN := range webAuthNs {
|
||||
// only add credentials that are ready and
|
||||
// either match the rpID or
|
||||
// if they were added through Console / old login UI, there is no stored rpID set;
|
||||
// then we check if the requested rpID matches the instance domain
|
||||
if webAuthN.State == domain.MFAStateReady &&
|
||||
(webAuthN.RPID == rpID ||
|
||||
(webAuthN.RPID == "" && rpID == strings.Split(http.DomainContext(ctx).InstanceHost, ":")[0])) {
|
||||
creds = append(creds, webauthn.Credential{
|
||||
ID: webAuthN.KeyID,
|
||||
PublicKey: webAuthN.PublicKey,
|
||||
AttestationType: webAuthN.AttestationType,
|
||||
Authenticator: webauthn.Authenticator{
|
||||
AAGUID: webAuthN.AAGUID,
|
||||
SignCount: webAuthN.SignCount,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return creds
|
||||
}
|
||||
|
||||
func WebAuthNToSessionData(webAuthN *domain.WebAuthNToken) webauthn.SessionData {
|
||||
return webauthn.SessionData{
|
||||
Challenge: webAuthN.Challenge,
|
||||
UserID: []byte(webAuthN.AggregateID),
|
||||
AllowedCredentialIDs: webAuthN.AllowedCredentialIDs,
|
||||
UserVerification: UserVerificationFromDomain(webAuthN.UserVerification),
|
||||
}
|
||||
}
|
||||
|
||||
func WebAuthNLoginToSessionData(webAuthN *domain.WebAuthNLogin) webauthn.SessionData {
|
||||
return webauthn.SessionData{
|
||||
Challenge: webAuthN.Challenge,
|
||||
UserID: []byte(webAuthN.AggregateID),
|
||||
AllowedCredentialIDs: webAuthN.AllowedCredentialIDs,
|
||||
UserVerification: UserVerificationFromDomain(webAuthN.UserVerification),
|
||||
}
|
||||
}
|
||||
|
||||
func UserVerificationToDomain(verification protocol.UserVerificationRequirement) domain.UserVerificationRequirement {
|
||||
switch verification {
|
||||
case protocol.VerificationRequired:
|
||||
return domain.UserVerificationRequirementRequired
|
||||
case protocol.VerificationPreferred:
|
||||
return domain.UserVerificationRequirementPreferred
|
||||
case protocol.VerificationDiscouraged:
|
||||
return domain.UserVerificationRequirementDiscouraged
|
||||
default:
|
||||
return domain.UserVerificationRequirementUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func UserVerificationFromDomain(verification domain.UserVerificationRequirement) protocol.UserVerificationRequirement {
|
||||
switch verification {
|
||||
case domain.UserVerificationRequirementRequired:
|
||||
return protocol.VerificationRequired
|
||||
case domain.UserVerificationRequirementPreferred:
|
||||
return protocol.VerificationPreferred
|
||||
case domain.UserVerificationRequirementDiscouraged:
|
||||
return protocol.VerificationDiscouraged
|
||||
default:
|
||||
return protocol.VerificationDiscouraged
|
||||
}
|
||||
}
|
||||
|
||||
func AuthenticatorAttachmentFromDomain(authType domain.AuthenticatorAttachment) protocol.AuthenticatorAttachment {
|
||||
switch authType {
|
||||
case domain.AuthenticatorAttachmentPlattform:
|
||||
return protocol.Platform
|
||||
case domain.AuthenticatorAttachmentCrossPlattform:
|
||||
return protocol.CrossPlatform
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
153
apps/api/internal/webauthn/converter_test.go
Normal file
153
apps/api/internal/webauthn/converter_test.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
func TestWebAuthNsToCredentials(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
webAuthNs []*domain.WebAuthNToken
|
||||
rpID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []webauthn.Credential
|
||||
}{
|
||||
{
|
||||
name: "unready credential",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
webAuthNs: []*domain.WebAuthNToken{
|
||||
{
|
||||
KeyID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
State: domain.MFAStateNotReady,
|
||||
},
|
||||
},
|
||||
rpID: "example.com",
|
||||
},
|
||||
want: []webauthn.Credential{},
|
||||
},
|
||||
{
|
||||
name: "not matching rpID",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
webAuthNs: []*domain.WebAuthNToken{
|
||||
{
|
||||
KeyID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
State: domain.MFAStateReady,
|
||||
RPID: "other.com",
|
||||
},
|
||||
},
|
||||
rpID: "example.com",
|
||||
},
|
||||
want: []webauthn.Credential{},
|
||||
},
|
||||
{
|
||||
name: "matching rpID",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
webAuthNs: []*domain.WebAuthNToken{
|
||||
{
|
||||
KeyID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
State: domain.MFAStateReady,
|
||||
RPID: "example.com",
|
||||
},
|
||||
},
|
||||
rpID: "example.com",
|
||||
},
|
||||
want: []webauthn.Credential{
|
||||
{
|
||||
ID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
Authenticator: webauthn.Authenticator{
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no rpID, different host",
|
||||
args: args{
|
||||
ctx: http.WithDomainContext(context.Background(), &http.DomainCtx{
|
||||
InstanceHost: "other.com:443",
|
||||
PublicHost: "other.com:443",
|
||||
Protocol: "https",
|
||||
}),
|
||||
webAuthNs: []*domain.WebAuthNToken{
|
||||
{
|
||||
KeyID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
State: domain.MFAStateReady,
|
||||
RPID: "",
|
||||
},
|
||||
},
|
||||
rpID: "example.com",
|
||||
},
|
||||
want: []webauthn.Credential{},
|
||||
},
|
||||
{
|
||||
name: "no rpID, same host",
|
||||
args: args{
|
||||
ctx: http.WithDomainContext(context.Background(), &http.DomainCtx{
|
||||
InstanceHost: "example.com:443",
|
||||
PublicHost: "example.com:443",
|
||||
Protocol: "https",
|
||||
}),
|
||||
webAuthNs: []*domain.WebAuthNToken{
|
||||
{
|
||||
KeyID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
State: domain.MFAStateReady,
|
||||
RPID: "",
|
||||
},
|
||||
},
|
||||
rpID: "example.com",
|
||||
},
|
||||
want: []webauthn.Credential{
|
||||
{
|
||||
ID: []byte("key1"),
|
||||
PublicKey: []byte("publicKey1"),
|
||||
AttestationType: "attestation1",
|
||||
Authenticator: webauthn.Authenticator{
|
||||
AAGUID: []byte("aaguid1"),
|
||||
SignCount: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, WebAuthNsToCredentials(tt.args.ctx, tt.args.webAuthNs, tt.args.rpID), "WebAuthNsToCredentials(%v, %v, %v)", tt.args.ctx, tt.args.webAuthNs, tt.args.rpID)
|
||||
})
|
||||
}
|
||||
}
|
219
apps/api/internal/webauthn/webauthn.go
Normal file
219
apps/api/internal/webauthn/webauthn.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DisplayName string
|
||||
ExternalSecure bool
|
||||
}
|
||||
|
||||
type webUser struct {
|
||||
*domain.Human
|
||||
accountName string
|
||||
credentials []webauthn.Credential
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnID() []byte {
|
||||
return []byte(u.AggregateID)
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnName() string {
|
||||
if u.accountName != "" {
|
||||
return u.accountName
|
||||
}
|
||||
return u.GetUsername()
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnDisplayName() string {
|
||||
if u.DisplayName != "" {
|
||||
return u.DisplayName
|
||||
}
|
||||
return u.GetUsername()
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnIcon() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
|
||||
return u.credentials
|
||||
}
|
||||
|
||||
func (w *Config) BeginRegistration(ctx context.Context, user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, rpID string, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
|
||||
webAuthNServer, err := w.serverFromContext(ctx, rpID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creds := WebAuthNsToCredentials(ctx, webAuthNs, rpID)
|
||||
existing := make([]protocol.CredentialDescriptor, len(creds))
|
||||
for i, cred := range creds {
|
||||
existing[i] = protocol.CredentialDescriptor{
|
||||
Type: protocol.PublicKeyCredentialType,
|
||||
CredentialID: cred.ID,
|
||||
}
|
||||
}
|
||||
credentialOptions, sessionData, err := webAuthNServer.BeginRegistration(
|
||||
&webUser{
|
||||
Human: user,
|
||||
accountName: accountName,
|
||||
credentials: creds,
|
||||
},
|
||||
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
|
||||
UserVerification: UserVerificationFromDomain(userVerification),
|
||||
AuthenticatorAttachment: AuthenticatorAttachmentFromDomain(authType),
|
||||
}),
|
||||
webauthn.WithConveyancePreference(protocol.PreferNoAttestation),
|
||||
webauthn.WithExclusions(existing),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-bM8sd", "Errors.User.WebAuthN.BeginRegisterFailed")
|
||||
}
|
||||
cred, err := json.Marshal(credentialOptions)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-D7cus", "Errors.User.WebAuthN.MarshalError")
|
||||
}
|
||||
return &domain.WebAuthNToken{
|
||||
Challenge: sessionData.Challenge,
|
||||
CredentialCreationData: cred,
|
||||
AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
|
||||
UserVerification: UserVerificationToDomain(sessionData.UserVerification),
|
||||
RPID: webAuthNServer.Config.RPID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *Config) FinishRegistration(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte) (*domain.WebAuthNToken, error) {
|
||||
if webAuthN == nil {
|
||||
return nil, zerrors.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
|
||||
}
|
||||
credentialData, err := protocol.ParseCredentialCreationResponseBody(bytes.NewReader(credData))
|
||||
if err != nil {
|
||||
logging.WithFields("error", tryExtractProtocolErrMsg(err), "err_id", "WEBAU-sEr8c").Debug("webauthn credential could not be parsed")
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential")
|
||||
}
|
||||
sessionData := WebAuthNToSessionData(webAuthN)
|
||||
webAuthNServer, err := w.serverFromContext(ctx, webAuthN.RPID, credentialData.Response.CollectedClientData.Origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credential, err := webAuthNServer.CreateCredential(
|
||||
&webUser{
|
||||
Human: user,
|
||||
},
|
||||
sessionData,
|
||||
credentialData)
|
||||
if err != nil {
|
||||
logging.WithFields("error", tryExtractProtocolErrMsg(err), "err_id", "WEBAU-3Vb9s").Debug("webauthn credential could not be created")
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-3Vb9s", "Errors.User.WebAuthN.CreateCredentialFailed")
|
||||
}
|
||||
|
||||
webAuthN.KeyID = credential.ID
|
||||
webAuthN.PublicKey = credential.PublicKey
|
||||
webAuthN.AttestationType = credential.AttestationType
|
||||
webAuthN.AAGUID = credential.Authenticator.AAGUID
|
||||
webAuthN.SignCount = credential.Authenticator.SignCount
|
||||
webAuthN.WebAuthNTokenName = tokenName
|
||||
webAuthN.RPID = webAuthNServer.Config.RPID
|
||||
return webAuthN, nil
|
||||
}
|
||||
|
||||
func (w *Config) BeginLogin(ctx context.Context, user *domain.Human, userVerification domain.UserVerificationRequirement, rpID string, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
|
||||
webAuthNServer, err := w.serverFromContext(ctx, rpID, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assertion, sessionData, err := webAuthNServer.BeginLogin(&webUser{
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(ctx, webAuthNs, rpID),
|
||||
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
|
||||
if err != nil {
|
||||
logging.WithFields("error", tryExtractProtocolErrMsg(err)).Debug("webauthn login could not be started")
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-4G8sw", "Errors.User.WebAuthN.BeginLoginFailed")
|
||||
}
|
||||
cred, err := json.Marshal(assertion)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-2M0s9", "Errors.User.WebAuthN.MarshalError")
|
||||
}
|
||||
return &domain.WebAuthNLogin{
|
||||
Challenge: sessionData.Challenge,
|
||||
CredentialAssertionData: cred,
|
||||
AllowedCredentialIDs: sessionData.AllowedCredentialIDs,
|
||||
UserVerification: userVerification,
|
||||
RPID: webAuthNServer.Config.RPID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *Config) FinishLogin(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, webAuthNs ...*domain.WebAuthNToken) (*webauthn.Credential, error) {
|
||||
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
|
||||
if err != nil {
|
||||
logging.WithFields("error", tryExtractProtocolErrMsg(err)).Debug("webauthn assertion could not be parsed")
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||
}
|
||||
webUser := &webUser{
|
||||
Human: user,
|
||||
credentials: WebAuthNsToCredentials(ctx, webAuthNs, webAuthN.RPID),
|
||||
}
|
||||
webAuthNServer, err := w.serverFromContext(ctx, webAuthN.RPID, assertionData.Response.CollectedClientData.Origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
credential, err := webAuthNServer.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
||||
if err != nil {
|
||||
logging.WithFields("error", tryExtractProtocolErrMsg(err)).Debug("webauthn assertion failed")
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||
}
|
||||
|
||||
if credential.Authenticator.CloneWarning {
|
||||
return credential, zerrors.ThrowInternal(nil, "WEBAU-4M90s", "Errors.User.WebAuthN.CloneWarning")
|
||||
}
|
||||
return credential, nil
|
||||
}
|
||||
|
||||
func (w *Config) serverFromContext(ctx context.Context, id, origin string) (*webauthn.WebAuthn, error) {
|
||||
config := w.config(id, origin)
|
||||
if id == "" {
|
||||
config = w.configFromContext(ctx)
|
||||
}
|
||||
webAuthn, err := webauthn.New(config)
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "WEBAU-UX9ta", "Errors.User.WebAuthN.ServerConfig")
|
||||
}
|
||||
return webAuthn, nil
|
||||
}
|
||||
|
||||
func (w *Config) configFromContext(ctx context.Context) *webauthn.Config {
|
||||
domainCtx := http.DomainContext(ctx)
|
||||
return &webauthn.Config{
|
||||
RPDisplayName: w.DisplayName,
|
||||
RPID: domainCtx.RequestedDomain(),
|
||||
RPOrigins: []string{domainCtx.Origin()},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Config) config(id, origin string) *webauthn.Config {
|
||||
return &webauthn.Config{
|
||||
RPDisplayName: w.DisplayName,
|
||||
RPID: id,
|
||||
RPOrigins: []string{origin},
|
||||
}
|
||||
}
|
||||
|
||||
func tryExtractProtocolErrMsg(err error) string {
|
||||
var e *protocol.Error
|
||||
if errors.As(err, &e) {
|
||||
return e.Details + ": " + e.DevInfo
|
||||
}
|
||||
return e.Error()
|
||||
}
|
79
apps/api/internal/webauthn/webauthn_test.go
Normal file
79
apps/api/internal/webauthn/webauthn_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package webauthn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestConfig_serverFromContext(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
origin string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *webauthn.WebAuthn
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "webauthn error",
|
||||
args: args{context.Background(), "", ""},
|
||||
wantErr: zerrors.ThrowInternal(nil, "WEBAU-UX9ta", "Errors.User.WebAuthN.ServerConfig"),
|
||||
},
|
||||
{
|
||||
name: "success from ctx",
|
||||
args: args{
|
||||
ctx: http_util.WithDomainContext(context.Background(), &http_util.DomainCtx{InstanceHost: "example.com", Protocol: "https"}),
|
||||
id: "",
|
||||
origin: "",
|
||||
},
|
||||
want: &webauthn.WebAuthn{
|
||||
Config: &webauthn.Config{
|
||||
RPDisplayName: "DisplayName",
|
||||
RPID: "example.com",
|
||||
RPOrigins: []string{"https://example.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success from id",
|
||||
args: args{
|
||||
ctx: http_util.WithDomainContext(context.Background(), &http_util.DomainCtx{InstanceHost: "example.com", Protocol: "https"}),
|
||||
id: "external.com",
|
||||
origin: "https://external.com",
|
||||
},
|
||||
want: &webauthn.WebAuthn{
|
||||
Config: &webauthn.Config{
|
||||
RPDisplayName: "DisplayName",
|
||||
RPID: "external.com",
|
||||
RPOrigins: []string{"https://external.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &Config{
|
||||
DisplayName: "DisplayName",
|
||||
ExternalSecure: true,
|
||||
}
|
||||
got, err := w.serverFromContext(tt.args.ctx, tt.args.id, tt.args.origin)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if tt.want != nil {
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, tt.want.Config.RPDisplayName, got.Config.RPDisplayName)
|
||||
assert.Equal(t, tt.want.Config.RPID, got.Config.RPID)
|
||||
assert.Equal(t, tt.want.Config.RPOrigins, got.Config.RPOrigins)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user