diff --git a/cmd/zitadel/setup.yaml b/cmd/zitadel/setup.yaml index 79611a5022..67910af8a5 100644 --- a/cmd/zitadel/setup.yaml +++ b/cmd/zitadel/setup.yaml @@ -98,4 +98,6 @@ SetUp: Step7: DefaultSecondFactor: 1 #SecondFactorTypeOTP Step8: - DefaultSecondFactor: 2 #SecondFactorTypeU2F \ No newline at end of file + DefaultSecondFactor: 2 #SecondFactorTypeU2F + Step9: + Passwordless: true \ No newline at end of file diff --git a/console/src/app/modules/changes/changes.component.ts b/console/src/app/modules/changes/changes.component.ts index eeee5de055..58001fd7ae 100644 --- a/console/src/app/modules/changes/changes.component.ts +++ b/console/src/app/modules/changes/changes.component.ts @@ -42,7 +42,6 @@ export class ChangesComponent implements OnInit, OnDestroy { this.init(); if (this.refresh) { this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => { - console.log('asdf'); this.init(); }); } diff --git a/console/src/app/modules/policies/login-policy/login-policy.component.html b/console/src/app/modules/policies/login-policy/login-policy.component.html index f76da7bc9c..e88404bf39 100644 --- a/console/src/app/modules/policies/login-policy/login-policy.component.html +++ b/console/src/app/modules/policies/login-policy/login-policy.component.html @@ -53,7 +53,7 @@
- {{'MFA.TYPE' | translate}} + {{'LOGINPOLICY.PASSWORDLESS' | translate}} {{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}} diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts index 2bde75405e..eefdb0efe1 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-passwordless/auth-passwordless.component.ts @@ -58,6 +58,13 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy { if (credOptions.publicKey?.challenge) { credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any); credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any); + if (credOptions.publicKey.excludeCredentials) { + credOptions.publicKey.excludeCredentials.map(cred => { + cred.id = _base64ToArrayBuffer(cred.id as any); + return cred; + }); + } + console.log(credOptions); const dialogRef = this.dialog.open(DialogU2FComponent, { width: '400px', data: { diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts index a8d8d7f935..2926ce8e22 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts +++ b/console/src/app/pages/users/user-detail/auth-user-detail/auth-user-mfa/auth-user-mfa.component.ts @@ -83,8 +83,12 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy { credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any); credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any); if (credOptions.publicKey.excludeCredentials) { - credOptions.publicKey.excludeCredentials.map(cred => cred.id = _base64ToArrayBuffer(cred.id as any)); + credOptions.publicKey.excludeCredentials.map(cred => { + cred.id = _base64ToArrayBuffer(cred.id as any); + return cred; + }); } + console.log(credOptions); const dialogRef = this.dialog.open(DialogU2FComponent, { width: '400px', data: { diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/dialog-u2f/dialog-u2f.component.html b/console/src/app/pages/users/user-detail/auth-user-detail/dialog-u2f/dialog-u2f.component.html index b649faae0c..fa9a136f1e 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/dialog-u2f/dialog-u2f.component.html +++ b/console/src/app/pages/users/user-detail/auth-user-detail/dialog-u2f/dialog-u2f.component.html @@ -4,7 +4,7 @@ {{'USER.MFA.U2F_NAME' | translate}} - + @@ -13,6 +13,7 @@
- -
+ \ No newline at end of file diff --git a/console/src/app/pages/users/user-detail/auth-user-detail/edit-dialog/edit-dialog.component.html b/console/src/app/pages/users/user-detail/auth-user-detail/edit-dialog/edit-dialog.component.html index 37056e74fe..81ded0f7ef 100644 --- a/console/src/app/pages/users/user-detail/auth-user-detail/edit-dialog/edit-dialog.component.html +++ b/console/src/app/pages/users/user-detail/auth-user-detail/edit-dialog/edit-dialog.component.html @@ -5,7 +5,7 @@
{{data.labelKey | translate }} - +
diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 41254071f1..7f12d7b93f 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -909,6 +909,7 @@ "DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen", "SELECTIDPS":"Identity Provider" }, + "PASSWORDLESS":"Passwordloser Login", "PASSWORDLESSTYPE": { "0":"Nicht erlaubt", "1":"Erlaubt" diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index dd79f04a8a..ee90a3b6c8 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -909,6 +909,7 @@ "DESCRIPTION":"You can select predefined or selfcreated providers for authentication.", "SELECTIDPS":"Identity providers" }, + "PASSWORDLESS":"Passwordless Login", "PASSWORDLESSTYPE": { "0":"Not allowed", "1":"Allowed" diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 0c456b1b5e..bc38d00575 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -643,7 +643,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user return &model.InitUserStep{PasswordSet: user.PasswordSet} } - if user.IsPasswordlessReady() { + if request.LoginPolicy.PasswordlessType != iam_model.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() { if !checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) { return &model.PasswordlessStep{} } @@ -815,9 +815,8 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve case es_model.UserRemoved: return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive") } - if err := sessionCopy.AppendEvent(event); err != nil { - return user_view_model.UserSessionToModel(&sessionCopy), nil - } + err := sessionCopy.AppendEvent(event) + logging.Log("EVENT-qbhj3").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("error appending event") } return user_view_model.UserSessionToModel(&sessionCopy), nil } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 32883f97f7..67fef8b588 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -430,7 +430,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, []model.NextStep{&model.PasswordStep{}}, nil, }, @@ -475,7 +475,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, MultiFactorCheckLifeTime: 10 * time.Hour, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{PasswordlessType: iam_model.PasswordlessTypeAllowed}}, false}, []model.NextStep{&model.PasswordlessStep{}}, nil, }, @@ -500,7 +500,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { args{&model.AuthRequest{ UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{ - MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN}, + PasswordlessType: iam_model.PasswordlessTypeAllowed, + MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN}, }, }, false}, []model.NextStep{&model.VerifyEMailStep{}}, @@ -514,7 +515,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, []model.NextStep{&model.InitPasswordStep{}}, nil, }, @@ -578,7 +579,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, PasswordCheckLifeTime: 10 * 24 * time.Hour, }, - args{&model.AuthRequest{UserID: "UserID"}, false}, + args{&model.AuthRequest{UserID: "UserID", LoginPolicy: &iam_model.LoginPolicyView{}}, false}, []model.NextStep{&model.PasswordStep{}}, nil, }, @@ -887,6 +888,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { args{ &model.AuthRequest{ UserID: "UserID", + LoginPolicy: &iam_model.LoginPolicyView{}, SelectedIDPConfigID: "IDPConfigID", LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, }, false}, diff --git a/internal/auth/repository/eventsourcing/eventstore/user.go b/internal/auth/repository/eventsourcing/eventstore/user.go index 1421b97929..d4e96c32e3 100644 --- a/internal/auth/repository/eventsourcing/eventstore/user.go +++ b/internal/auth/repository/eventsourcing/eventstore/user.go @@ -303,11 +303,26 @@ func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error { } func (repo *UserRepo) AddMFAU2F(ctx context.Context, userID string) (*model.WebAuthNToken, error) { - return repo.UserEvents.AddU2F(ctx, userID, true) + accountName := "" + user, err := repo.UserByID(ctx, userID) + if err != nil { + logging.Log("EVENT-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") + } else { + accountName = user.PreferredLoginName + } + return repo.UserEvents.AddU2F(ctx, userID, accountName, true) } func (repo *UserRepo) AddMyMFAU2F(ctx context.Context) (*model.WebAuthNToken, error) { - return repo.UserEvents.AddU2F(ctx, authz.GetCtxData(ctx).UserID, false) + userID := authz.GetCtxData(ctx).UserID + accountName := "" + user, err := repo.UserByID(ctx, userID) + if err != nil { + logging.Log("EVENT-Ghwl1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") + } else { + accountName = user.PreferredLoginName + } + return repo.UserEvents.AddU2F(ctx, userID, accountName, false) } func (repo *UserRepo) VerifyMFAU2FSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { @@ -331,7 +346,14 @@ func (repo *UserRepo) GetPasswordless(ctx context.Context, userID string) ([]*mo } func (repo *UserRepo) AddPasswordless(ctx context.Context, userID string) (*model.WebAuthNToken, error) { - return repo.UserEvents.AddPasswordless(ctx, userID, true) + accountName := "" + user, err := repo.UserByID(ctx, userID) + if err != nil { + logging.Log("EVENT-Vj2k1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") + } else { + accountName = user.PreferredLoginName + } + return repo.UserEvents.AddPasswordless(ctx, userID, accountName, true) } func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNToken, error) { @@ -339,7 +361,15 @@ func (repo *UserRepo) GetMyPasswordless(ctx context.Context) ([]*model.WebAuthNT } func (repo *UserRepo) AddMyPasswordless(ctx context.Context) (*model.WebAuthNToken, error) { - return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, false) + userID := authz.GetCtxData(ctx).UserID + accountName := "" + user, err := repo.UserByID(ctx, userID) + if err != nil { + logging.Log("EVENT-AEq21").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get user for loginname") + } else { + accountName = user.PreferredLoginName + } + return repo.UserEvents.AddPasswordless(ctx, authz.GetCtxData(ctx).UserID, accountName, false) } func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error { diff --git a/internal/iam/model/iam.go b/internal/iam/model/iam.go index f80ea3b400..41ba7a6509 100644 --- a/internal/iam/model/iam.go +++ b/internal/iam/model/iam.go @@ -15,6 +15,7 @@ const ( Step6 Step7 Step8 + Step9 //StepCount marks the the length of possible steps (StepCount-1 == last possible step) StepCount ) diff --git a/internal/iam/repository/eventsourcing/eventstore.go b/internal/iam/repository/eventsourcing/eventstore.go index e1ed4d2d26..cad7dc09c0 100644 --- a/internal/iam/repository/eventsourcing/eventstore.go +++ b/internal/iam/repository/eventsourcing/eventstore.go @@ -525,20 +525,31 @@ func (es *IAMEventstore) AddLoginPolicy(ctx context.Context, policy *iam_model.L return model.LoginPolicyToModel(repoIam.DefaultLoginPolicy), nil } -func (es *IAMEventstore) ChangeLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) { +func (es *IAMEventstore) PrepareChangeLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*model.IAM, *models.Aggregate, error) { if policy == nil || !policy.IsValid() { - return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-3M0so", "Errors.IAM.LoginPolicyInvalid") + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-3M0so", "Errors.IAM.LoginPolicyInvalid") } iam, err := es.IAMByID(ctx, policy.AggregateID) if err != nil { - return nil, err + return nil, nil, err } repoIam := model.IAMFromModel(iam) repoLoginPolicy := model.LoginPolicyFromModel(policy) - addAggregate := LoginPolicyChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoLoginPolicy) - err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate) + changeAgg, err := LoginPolicyChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoLoginPolicy)(ctx) + if err != nil { + return nil, nil, err + } + return repoIam, changeAgg, nil +} + +func (es *IAMEventstore) ChangeLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) { + repoIam, changeAggregate, err := es.PrepareChangeLoginPolicy(ctx, policy) + if err != nil { + return nil, err + } + err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIam.AppendEvents, changeAggregate) if err != nil { return nil, err } @@ -665,27 +676,38 @@ func (es *IAMEventstore) RemoveSecondFactorFromLoginPolicy(ctx context.Context, return nil } -func (es *IAMEventstore) AddMultiFactorToLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.MultiFactorType) (iam_model.MultiFactorType, error) { +func (es *IAMEventstore) PrepareAddMultiFactorToLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.MultiFactorType) (*model.IAM, *models.Aggregate, error) { if mfa == iam_model.MultiFactorTypeUnspecified { - return 0, caos_errs.ThrowPreconditionFailed(nil, "EVENT-2Dh7J", "Errors.IAM.LoginPolicy.MFA.Unspecified") + return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-2Dh7J", "Errors.IAM.LoginPolicy.MFA.Unspecified") } iam, err := es.IAMByID(ctx, aggregateID) if err != nil { - return 0, err + return nil, nil, err } if _, m := iam.DefaultLoginPolicy.GetMultiFactor(mfa); m != 0 { - return 0, caos_errs.ThrowAlreadyExists(nil, "EVENT-4Rk09", "Errors.IAM.LoginPolicy.MFA.AlreadyExists") + return nil, nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-4Rk09", "Errors.IAM.LoginPolicy.MFA.AlreadyExists") } repoIam := model.IAMFromModel(iam) repoMFA := model.MultiFactorFromModel(mfa) - addAggregate := LoginPolicyMultiFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA) - err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate) + addAggregate, err := LoginPolicyMultiFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)(ctx) + if err != nil { + return nil, nil, err + } + return repoIam, addAggregate, nil +} + +func (es *IAMEventstore) AddMultiFactorToLoginPolicy(ctx context.Context, aggregateID string, mfa iam_model.MultiFactorType) (iam_model.MultiFactorType, error) { + repoIAM, addAggregate, err := es.PrepareAddMultiFactorToLoginPolicy(ctx, aggregateID, mfa) if err != nil { return 0, err } - es.iamCache.cacheIAM(repoIam) - if _, m := model.GetMFA(repoIam.DefaultLoginPolicy.MultiFactors, int32(mfa)); m != 0 { + err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIAM.AppendEvents, addAggregate) + if err != nil { + return 0, err + } + es.iamCache.cacheIAM(repoIAM) + if _, m := model.GetMFA(repoIAM.DefaultLoginPolicy.MultiFactors, int32(mfa)); m != 0 { return iam_model.MultiFactorType(m), nil } return 0, caos_errs.ThrowInternal(nil, "EVENT-5N9so", "Errors.Internal") diff --git a/internal/setup/config.go b/internal/setup/config.go index 93e0d4926b..c4e3149a4c 100644 --- a/internal/setup/config.go +++ b/internal/setup/config.go @@ -14,6 +14,7 @@ type IAMSetUp struct { Step6 *Step6 Step7 *Step7 Step8 *Step8 + Step9 *Step9 } func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) { @@ -29,6 +30,7 @@ func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) { setup.Step6, setup.Step7, setup.Step8, + setup.Step9, } { if step.step() <= currentDone { continue diff --git a/internal/setup/step9.go b/internal/setup/step9.go new file mode 100644 index 0000000000..2733dbb51a --- /dev/null +++ b/internal/setup/step9.go @@ -0,0 +1,74 @@ +package setup + +import ( + "context" + + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore/models" + es_sdk "github.com/caos/zitadel/internal/eventstore/sdk" + iam_model "github.com/caos/zitadel/internal/iam/model" + iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" +) + +type Step9 struct { + Passwordless bool + + setup *Setup +} + +func (step *Step9) isNil() bool { + return step == nil +} + +func (step *Step9) step() iam_model.Step { + return iam_model.Step9 +} + +func (step *Step9) init(setup *Setup) { + step.setup = setup +} + +func (step *Step9) execute(ctx context.Context) (*iam_model.IAM, error) { + if !step.Passwordless { + return step.setup.IamEvents.IAMByID(ctx, step.setup.iamID) + } + iam, agg, err := step.setPasswordlessAllowedInPolicy(ctx) + if err != nil { + logging.Log("SETUP-Gdbjq").WithField("step", step.step()).WithError(err).Error("unable to finish setup (add default mfa to login policy)") + return nil, err + } + iam, agg2, err := step.addMFAToPolicy(ctx) + if err != nil { + logging.Log("SETUP-Gdbjq").WithField("step", step.step()).WithError(err).Error("unable to finish setup (add default mfa to login policy)") + return nil, err + } + agg.Events = append(agg.Events, agg2.Events...) + iam, agg, push, err := step.setup.IamEvents.PrepareSetupDone(ctx, iam, agg, step.step()) + if err != nil { + logging.Log("SETUP-Cnf21").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)") + return nil, err + } + err = es_sdk.PushAggregates(ctx, push, iam.AppendEvents, agg) + if err != nil { + logging.Log("SETUP-NFq21").WithField("step", step.step()).WithError(err).Error("unable to finish setup") + return nil, err + } + return iam_es_model.IAMToModel(iam), nil +} + +func (step *Step9) setPasswordlessAllowedInPolicy(ctx context.Context) (*iam_es_model.IAM, *models.Aggregate, error) { + logging.Log("SETUP-DAd1h").Info("enabling passwordless in loginPolicy") + iam, err := step.setup.IamEvents.IAMByID(ctx, step.setup.iamID) + if err != nil { + return nil, nil, err + } + iam.DefaultLoginPolicy.AggregateID = step.setup.iamID + iam.DefaultLoginPolicy.PasswordlessType = iam_model.PasswordlessTypeAllowed + return step.setup.IamEvents.PrepareChangeLoginPolicy(ctx, iam.DefaultLoginPolicy) +} + +func (step *Step9) addMFAToPolicy(ctx context.Context) (*iam_es_model.IAM, *models.Aggregate, error) { + logging.Log("SETUP-DAd1h").Info("adding MFA to loginPolicy") + return step.setup.IamEvents.PrepareAddMultiFactorToLoginPolicy(ctx, step.setup.iamID, iam_model.MultiFactorTypeU2FWithPIN) +} diff --git a/internal/user/repository/eventsourcing/eventstore.go b/internal/user/repository/eventsourcing/eventstore.go index 7e5191d0db..acbf78048b 100644 --- a/internal/user/repository/eventsourcing/eventstore.go +++ b/internal/user/repository/eventsourcing/eventstore.go @@ -1302,12 +1302,12 @@ func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error { return nil } -func (es *UserEventstore) AddU2F(ctx context.Context, userID string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { +func (es *UserEventstore) AddU2F(ctx context.Context, userID string, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - webAuthN, err := es.webauthn.BeginRegistration(user, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) + webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementDiscouraged, isLoginUI, user.U2FTokens...) if err != nil { return nil, err } @@ -1418,12 +1418,12 @@ func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([ return user.PasswordlessTokens, nil } -func (es *UserEventstore) AddPasswordless(ctx context.Context, userID string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { +func (es *UserEventstore) AddPasswordless(ctx context.Context, userID, accountName string, isLoginUI bool) (*usr_model.WebAuthNToken, error) { user, err := es.HumanByID(ctx, userID) if err != nil { return nil, err } - webAuthN, err := es.webauthn.BeginRegistration(user, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) + webAuthN, err := es.webauthn.BeginRegistration(user, accountName, usr_model.AuthenticatorAttachmentUnspecified, usr_model.UserVerificationRequirementRequired, isLoginUI, user.PasswordlessTokens...) if err != nil { return nil, err } diff --git a/internal/user/repository/view/model/user_session.go b/internal/user/repository/view/model/user_session.go index 514c829cf6..b64fb1b303 100644 --- a/internal/user/repository/view/model/user_session.go +++ b/internal/user/repository/view/model/user_session.go @@ -155,8 +155,12 @@ func (v *UserSessionView) AppendEvent(event *models.Event) error { es_model.HumanSignedOut, es_model.UserLocked, es_model.UserDeactivated: + v.PasswordlessVerification = time.Time{} v.PasswordVerification = time.Time{} v.SecondFactorVerification = time.Time{} + v.SecondFactorVerificationType = int32(req_model.MFALevelNotSetUp) + v.MultiFactorVerification = time.Time{} + v.MultiFactorVerificationType = int32(req_model.MFALevelNotSetUp) v.ExternalLoginVerification = time.Time{} v.State = int32(req_model.UserSessionStateTerminated) case es_model.HumanExternalIDPRemoved, es_model.HumanExternalIDPCascadeRemoved: diff --git a/internal/webauthn/webauthn.go b/internal/webauthn/webauthn.go index 8c1fa0093f..b003b3d5f6 100644 --- a/internal/webauthn/webauthn.go +++ b/internal/webauthn/webauthn.go @@ -42,6 +42,7 @@ func StartServer(sd systemdefaults.WebAuthN) (*WebAuthN, error) { type webUser struct { *usr_model.User + accountName string credentials []webauthn.Credential } @@ -50,7 +51,10 @@ func (u *webUser) WebAuthnID() []byte { } func (u *webUser) WebAuthnName() string { - return u.PreferredLoginName + if u.accountName != "" { + return u.accountName + } + return u.UserName } func (u *webUser) WebAuthnDisplayName() string { @@ -65,7 +69,7 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential { return u.credentials } -func (w *WebAuthN) BeginRegistration(user *usr_model.User, authType usr_model.AuthenticatorAttachment, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNToken, error) { +func (w *WebAuthN) BeginRegistration(user *usr_model.User, accountName string, authType usr_model.AuthenticatorAttachment, userVerification usr_model.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*usr_model.WebAuthNToken) (*usr_model.WebAuthNToken, error) { creds := WebAuthNsToCredentials(webAuthNs) existing := make([]protocol.CredentialDescriptor, len(creds)) for i, cred := range creds { @@ -77,6 +81,7 @@ func (w *WebAuthN) BeginRegistration(user *usr_model.User, authType usr_model.Au credentialOptions, sessionData, err := w.web(isLoginUI).BeginRegistration( &webUser{ User: user, + accountName: accountName, credentials: creds, }, webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{