mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 19:14:23 +00:00
959530ddad
* fix: correct selectors for extended writemodel * fix: no previous checks in eventstore * start check previous * feat: auth user commands * feat: auth user commands * feat: auth user commands * feat: otp * feat: corrections from pr merge * feat: webauthn * feat: comment old webauthn * feat: refactor user, human, machine * feat: webauth command side * feat: command and query side in login * feat: fix user writemodel append events * fix: remove creation dates on command side * fix: remove previous sequence * previous sequence * fix: external idps * Update internal/api/grpc/management/user.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/v2/command/user_human_email.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix: pr changes * fix: phone verification Co-authored-by: adlerhurst <silvan.reusser@gmail.com> Co-authored-by: Livio Amstutz <livio.a@gmail.com>
228 lines
8.5 KiB
Go
228 lines
8.5 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
|
"github.com/caos/zitadel/internal/eventstore/v2"
|
|
"github.com/caos/zitadel/internal/telemetry/tracing"
|
|
"github.com/caos/zitadel/internal/v2/domain"
|
|
usr_repo "github.com/caos/zitadel/internal/v2/repository/user"
|
|
)
|
|
|
|
func (r *CommandSide) getHumanU2FTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
|
|
tokenReadModel := NewHumanU2FTokensReadModel(userID, resourceowner)
|
|
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tokenReadModel.UserState == domain.UserStateDeleted {
|
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-4M0ds", "Errors.User.NotFound")
|
|
}
|
|
return readModelToU2FTokens(tokenReadModel), nil
|
|
}
|
|
|
|
func (r *CommandSide) getHumanPasswordlessTokens(ctx context.Context, userID, resourceowner string) ([]*domain.WebAuthNToken, error) {
|
|
tokenReadModel := NewHumanPasswordlessTokensReadModel(userID, resourceowner)
|
|
err := r.eventstore.FilterToQueryReducer(ctx, tokenReadModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tokenReadModel.UserState == domain.UserStateDeleted {
|
|
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Mv9sd", "Errors.User.NotFound")
|
|
}
|
|
return readModelToPasswordlessTokens(tokenReadModel), nil
|
|
}
|
|
|
|
func (r *CommandSide) AddHumanU2F(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
|
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, u2fTokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
|
|
|
|
err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
|
|
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
|
|
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
|
|
createdWebAuthN.UserVerification = webAuthN.UserVerification
|
|
return createdWebAuthN, nil
|
|
}
|
|
|
|
func (r *CommandSide) AddHumanPasswordless(ctx context.Context, userID, resourceowner string, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
|
passwordlessTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addWebAuthN, userAgg, webAuthN, err := r.addHumanWebAuthN(ctx, userID, resourceowner, isLoginUI, passwordlessTokens)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
userAgg.PushEvents(usr_repo.NewHumanU2FAddedEvent(ctx, addWebAuthN.WebauthNTokenID, webAuthN.Challenge))
|
|
|
|
err = r.eventstore.PushAggregate(ctx, addWebAuthN, userAgg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
createdWebAuthN := writeModelToWebAuthN(addWebAuthN)
|
|
createdWebAuthN.CredentialCreationData = webAuthN.CredentialCreationData
|
|
createdWebAuthN.AllowedCredentialIDs = webAuthN.AllowedCredentialIDs
|
|
createdWebAuthN.UserVerification = webAuthN.UserVerification
|
|
return createdWebAuthN, nil
|
|
}
|
|
|
|
func (r *CommandSide) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) {
|
|
if userID == "" || resourceowner == "" {
|
|
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
|
|
}
|
|
user, err := r.getHuman(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
org, err := r.getOrg(ctx, user.ResourceOwner)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
orgPolicy, err := r.getOrgIAMPolicy(ctx, org.AggregateID)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
accountName := domain.GenerateLoginName(user.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
|
|
if accountName == "" {
|
|
accountName = user.EmailAddress
|
|
}
|
|
webAuthN, err := r.webauthn.BeginRegistration(user, accountName, domain.AuthenticatorAttachmentUnspecified, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
tokenID, err := r.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
addWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, tokenID, resourceowner)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&addWebAuthN.WriteModel)
|
|
return addWebAuthN, userAgg, webAuthN, nil
|
|
}
|
|
|
|
func (r *CommandSide) VerifyHumanU2F(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
|
u2fTokens, err := r.getHumanU2FTokens(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userAgg.PushEvents(
|
|
usr_repo.NewHumanU2FVerifiedEvent(
|
|
ctx,
|
|
verifyWebAuthN.WebauthNTokenID,
|
|
webAuthN.WebAuthNTokenName,
|
|
webAuthN.AttestationType,
|
|
webAuthN.KeyID,
|
|
webAuthN.PublicKey,
|
|
webAuthN.AAGUID,
|
|
webAuthN.SignCount,
|
|
),
|
|
)
|
|
|
|
return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg)
|
|
}
|
|
|
|
func (r *CommandSide) VerifyHumanPasswordless(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) error {
|
|
u2fTokens, err := r.getHumanPasswordlessTokens(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
verifyWebAuthN, userAgg, webAuthN, err := r.verifyHumanWebAuthN(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, u2fTokens)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userAgg.PushEvents(
|
|
usr_repo.NewHumanU2FVerifiedEvent(
|
|
ctx,
|
|
verifyWebAuthN.WebauthNTokenID,
|
|
webAuthN.WebAuthNTokenName,
|
|
webAuthN.AttestationType,
|
|
webAuthN.KeyID,
|
|
webAuthN.PublicKey,
|
|
webAuthN.AAGUID,
|
|
webAuthN.SignCount,
|
|
),
|
|
)
|
|
return r.eventstore.PushAggregate(ctx, verifyWebAuthN, userAgg)
|
|
}
|
|
|
|
func (r *CommandSide) verifyHumanWebAuthN(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *usr_repo.Aggregate, *domain.WebAuthNToken, error) {
|
|
if userID == "" || resourceowner == "" {
|
|
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
|
|
}
|
|
user, err := r.getHuman(ctx, userID, resourceowner)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
_, token := domain.GetTokenToVerify(tokens)
|
|
webAuthN, err := r.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
verifyWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, token.WebAuthNTokenID, resourceowner)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
userAgg := UserAggregateFromWriteModel(&verifyWebAuthN.WriteModel)
|
|
return verifyWebAuthN, userAgg, webAuthN, nil
|
|
}
|
|
|
|
func (r *CommandSide) RemoveHumanU2F(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
|
|
event := usr_repo.NewHumanU2FRemovedEvent(ctx, webAuthNID)
|
|
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
|
|
}
|
|
|
|
func (r *CommandSide) RemoveHumanPasswordless(ctx context.Context, userID, webAuthNID, resourceOwner string) error {
|
|
event := usr_repo.NewHumanPasswordlessRemovedEvent(ctx, webAuthNID)
|
|
return r.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
|
|
}
|
|
|
|
func (r *CommandSide) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID, resourceOwner string, event eventstore.EventPusher) error {
|
|
if userID == "" || webAuthNID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M9de", "Errors.IDMissing")
|
|
}
|
|
|
|
existingWebAuthN, err := r.webauthNWriteModelByID(ctx, userID, webAuthNID, resourceOwner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if existingWebAuthN.State == domain.MFAStateUnspecified || existingWebAuthN.State == domain.MFAStateRemoved {
|
|
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9ds", "Errors.User.ExternalIDP.NotFound")
|
|
}
|
|
userAgg := UserAggregateFromWriteModel(&existingWebAuthN.WriteModel)
|
|
userAgg.PushEvents(event)
|
|
|
|
return r.eventstore.PushAggregate(ctx, existingWebAuthN, userAgg)
|
|
}
|
|
|
|
func (r *CommandSide) webauthNWriteModelByID(ctx context.Context, userID, webAuthNID, resourceOwner string) (writeModel *HumanWebAuthNWriteModel, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
writeModel = NewHumanWebAuthNWriteModel(userID, webAuthNID, resourceOwner)
|
|
err = r.eventstore.FilterToQueryReducer(ctx, writeModel)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return writeModel, nil
|
|
}
|