mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 17:27:23 +00:00
fix: passwordless (#1116)
* fix passwordless session handling * only check passwordless when enabled in policy * set preferred user name in webauthn * fix tests * add passwordless in setup * fix(console): exclude credentials for passwordless (#1115) * fix: exclude creds * fix i18n type loginpolicy * fix enter on dialog input * remove arg Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
parent
c5287364a4
commit
b183d49761
@ -98,4 +98,6 @@ SetUp:
|
|||||||
Step7:
|
Step7:
|
||||||
DefaultSecondFactor: 1 #SecondFactorTypeOTP
|
DefaultSecondFactor: 1 #SecondFactorTypeOTP
|
||||||
Step8:
|
Step8:
|
||||||
DefaultSecondFactor: 2 #SecondFactorTypeU2F
|
DefaultSecondFactor: 2 #SecondFactorTypeU2F
|
||||||
|
Step9:
|
||||||
|
Passwordless: true
|
@ -42,7 +42,6 @@ export class ChangesComponent implements OnInit, OnDestroy {
|
|||||||
this.init();
|
this.init();
|
||||||
if (this.refresh) {
|
if (this.refresh) {
|
||||||
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
|
this.refresh.pipe(takeUntil(this.destroyed$), debounceTime(2000)).subscribe(() => {
|
||||||
console.log('asdf');
|
|
||||||
this.init();
|
this.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<cnsl-form-field class="form-field" label="Access Code" required="true">
|
<cnsl-form-field class="form-field" label="Access Code" required="true">
|
||||||
<cnsl-label>{{'MFA.TYPE' | translate}}</cnsl-label>
|
<cnsl-label>{{'LOGINPOLICY.PASSWORDLESS' | translate}}</cnsl-label>
|
||||||
<mat-select [(ngModel)]="loginData.passwordlessType">
|
<mat-select [(ngModel)]="loginData.passwordlessType">
|
||||||
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
|
<mat-option *ngFor="let pt of passwordlessTypes" [value]="pt">
|
||||||
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
|
{{'LOGINPOLICY.PASSWORDLESSTYPE.'+pt | translate}}
|
||||||
|
@ -58,6 +58,13 @@ export class AuthPasswordlessComponent implements OnInit, OnDestroy {
|
|||||||
if (credOptions.publicKey?.challenge) {
|
if (credOptions.publicKey?.challenge) {
|
||||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id 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, {
|
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
||||||
width: '400px',
|
width: '400px',
|
||||||
data: {
|
data: {
|
||||||
|
@ -83,8 +83,12 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
credOptions.publicKey.challenge = _base64ToArrayBuffer(credOptions.publicKey.challenge as any);
|
||||||
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
credOptions.publicKey.user.id = _base64ToArrayBuffer(credOptions.publicKey.user.id as any);
|
||||||
if (credOptions.publicKey.excludeCredentials) {
|
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, {
|
const dialogRef = this.dialog.open(DialogU2FComponent, {
|
||||||
width: '400px',
|
width: '400px',
|
||||||
data: {
|
data: {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<cnsl-form-field class="form-field" label="Name" required="true">
|
<cnsl-form-field class="form-field" label="Name" required="true">
|
||||||
<cnsl-label>{{'USER.MFA.U2F_NAME' | translate}}</cnsl-label>
|
<cnsl-label>{{'USER.MFA.U2F_NAME' | translate}}</cnsl-label>
|
||||||
<input cnslInput [(ngModel)]="name" required/>
|
<input cnslInput [(ngModel)]="name" required (keydown.enter)="name ? closeDialogWithCode() : null" />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
|
|
||||||
<mat-spinner diameter="30" *ngIf="loading"></mat-spinner>
|
<mat-spinner diameter="30" *ngIf="loading"></mat-spinner>
|
||||||
@ -13,6 +13,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions class="action">
|
<div mat-dialog-actions class="action">
|
||||||
<button mat-button (click)="closeDialog()">{{'ACTIONS.CLOSE' | translate}}</button>
|
<button mat-button (click)="closeDialog()">{{'ACTIONS.CLOSE' | translate}}</button>
|
||||||
<button [disabled]="!name" mat-raised-button class="ok-button" color="primary" (click)="closeDialogWithCode()">{{'ACTIONS.VERIFY' | translate}}
|
<button cdkFocusInitial [disabled]="!name" mat-raised-button class="ok-button" color="primary"
|
||||||
|
(click)="closeDialogWithCode()">{{'ACTIONS.VERIFY' | translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
@ -5,7 +5,7 @@
|
|||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<cnsl-form-field class="formfield">
|
<cnsl-form-field class="formfield">
|
||||||
<cnsl-label>{{data.labelKey | translate }}</cnsl-label>
|
<cnsl-label>{{data.labelKey | translate }}</cnsl-label>
|
||||||
<input cnslInput [(ngModel)]="value" />
|
<input cnslInput [(ngModel)]="value" (keydown.enter)="value ? closeDialogWithValue(value) : null" />
|
||||||
</cnsl-form-field>
|
</cnsl-form-field>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions class="action">
|
<div mat-dialog-actions class="action">
|
||||||
|
@ -909,6 +909,7 @@
|
|||||||
"DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen",
|
"DESCRIPTION":"Sie können vordefinierte oder selbsterstellten Provider auswählen",
|
||||||
"SELECTIDPS":"Identity Provider"
|
"SELECTIDPS":"Identity Provider"
|
||||||
},
|
},
|
||||||
|
"PASSWORDLESS":"Passwordloser Login",
|
||||||
"PASSWORDLESSTYPE": {
|
"PASSWORDLESSTYPE": {
|
||||||
"0":"Nicht erlaubt",
|
"0":"Nicht erlaubt",
|
||||||
"1":"Erlaubt"
|
"1":"Erlaubt"
|
||||||
|
@ -909,6 +909,7 @@
|
|||||||
"DESCRIPTION":"You can select predefined or selfcreated providers for authentication.",
|
"DESCRIPTION":"You can select predefined or selfcreated providers for authentication.",
|
||||||
"SELECTIDPS":"Identity providers"
|
"SELECTIDPS":"Identity providers"
|
||||||
},
|
},
|
||||||
|
"PASSWORDLESS":"Passwordless Login",
|
||||||
"PASSWORDLESSTYPE": {
|
"PASSWORDLESSTYPE": {
|
||||||
"0":"Not allowed",
|
"0":"Not allowed",
|
||||||
"1":"Allowed"
|
"1":"Allowed"
|
||||||
|
@ -643,7 +643,7 @@ func (repo *AuthRequestRepo) firstFactorChecked(request *model.AuthRequest, user
|
|||||||
return &model.InitUserStep{PasswordSet: user.PasswordSet}
|
return &model.InitUserStep{PasswordSet: user.PasswordSet}
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.IsPasswordlessReady() {
|
if request.LoginPolicy.PasswordlessType != iam_model.PasswordlessTypeNotAllowed && user.IsPasswordlessReady() {
|
||||||
if !checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) {
|
if !checkVerificationTime(userSession.PasswordlessVerification, repo.MultiFactorCheckLifeTime) {
|
||||||
return &model.PasswordlessStep{}
|
return &model.PasswordlessStep{}
|
||||||
}
|
}
|
||||||
@ -815,9 +815,8 @@ func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eve
|
|||||||
case es_model.UserRemoved:
|
case es_model.UserRemoved:
|
||||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive")
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dG2fe", "Errors.User.NotActive")
|
||||||
}
|
}
|
||||||
if err := sessionCopy.AppendEvent(event); err != nil {
|
err := sessionCopy.AppendEvent(event)
|
||||||
return user_view_model.UserSessionToModel(&sessionCopy), nil
|
logging.Log("EVENT-qbhj3").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Warn("error appending event")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return user_view_model.UserSessionToModel(&sessionCopy), nil
|
return user_view_model.UserSessionToModel(&sessionCopy), nil
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
userEventProvider: &mockEventUser{},
|
userEventProvider: &mockEventUser{},
|
||||||
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
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{}},
|
[]model.NextStep{&model.PasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -475,7 +475,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
MultiFactorCheckLifeTime: 10 * time.Hour,
|
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{}},
|
[]model.NextStep{&model.PasswordlessStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -500,7 +500,8 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
args{&model.AuthRequest{
|
args{&model.AuthRequest{
|
||||||
UserID: "UserID",
|
UserID: "UserID",
|
||||||
LoginPolicy: &iam_model.LoginPolicyView{
|
LoginPolicy: &iam_model.LoginPolicyView{
|
||||||
MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN},
|
PasswordlessType: iam_model.PasswordlessTypeAllowed,
|
||||||
|
MultiFactors: []iam_model.MultiFactorType{iam_model.MultiFactorTypeU2FWithPIN},
|
||||||
},
|
},
|
||||||
}, false},
|
}, false},
|
||||||
[]model.NextStep{&model.VerifyEMailStep{}},
|
[]model.NextStep{&model.VerifyEMailStep{}},
|
||||||
@ -514,7 +515,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
userEventProvider: &mockEventUser{},
|
userEventProvider: &mockEventUser{},
|
||||||
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
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{}},
|
[]model.NextStep{&model.InitPasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -578,7 +579,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
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{}},
|
[]model.NextStep{&model.PasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -887,6 +888,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
&model.AuthRequest{
|
&model.AuthRequest{
|
||||||
UserID: "UserID",
|
UserID: "UserID",
|
||||||
|
LoginPolicy: &iam_model.LoginPolicyView{},
|
||||||
SelectedIDPConfigID: "IDPConfigID",
|
SelectedIDPConfigID: "IDPConfigID",
|
||||||
LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
|
LinkingUsers: []*model.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}},
|
||||||
}, false},
|
}, false},
|
||||||
|
@ -303,11 +303,26 @@ func (repo *UserRepo) RemoveMyMFAOTP(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) AddMFAU2F(ctx context.Context, userID string) (*model.WebAuthNToken, 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) {
|
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 {
|
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) {
|
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) {
|
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) {
|
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 {
|
func (repo *UserRepo) VerifyPasswordlessSetup(ctx context.Context, userID, tokenName, userAgentID string, credentialData []byte) error {
|
||||||
|
@ -15,6 +15,7 @@ const (
|
|||||||
Step6
|
Step6
|
||||||
Step7
|
Step7
|
||||||
Step8
|
Step8
|
||||||
|
Step9
|
||||||
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
|
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
|
||||||
StepCount
|
StepCount
|
||||||
)
|
)
|
||||||
|
@ -525,20 +525,31 @@ func (es *IAMEventstore) AddLoginPolicy(ctx context.Context, policy *iam_model.L
|
|||||||
return model.LoginPolicyToModel(repoIam.DefaultLoginPolicy), nil
|
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() {
|
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)
|
iam, err := es.IAMByID(ctx, policy.AggregateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repoIam := model.IAMFromModel(iam)
|
repoIam := model.IAMFromModel(iam)
|
||||||
repoLoginPolicy := model.LoginPolicyFromModel(policy)
|
repoLoginPolicy := model.LoginPolicyFromModel(policy)
|
||||||
|
|
||||||
addAggregate := LoginPolicyChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoLoginPolicy)
|
changeAgg, err := LoginPolicyChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoLoginPolicy)(ctx)
|
||||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -665,27 +676,38 @@ func (es *IAMEventstore) RemoveSecondFactorFromLoginPolicy(ctx context.Context,
|
|||||||
return nil
|
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 {
|
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)
|
iam, err := es.IAMByID(ctx, aggregateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if _, m := iam.DefaultLoginPolicy.GetMultiFactor(mfa); m != 0 {
|
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)
|
repoIam := model.IAMFromModel(iam)
|
||||||
repoMFA := model.MultiFactorFromModel(mfa)
|
repoMFA := model.MultiFactorFromModel(mfa)
|
||||||
|
|
||||||
addAggregate := LoginPolicyMultiFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)
|
addAggregate, err := LoginPolicyMultiFactorAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMFA)(ctx)
|
||||||
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
es.iamCache.cacheIAM(repoIam)
|
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIAM.AppendEvents, addAggregate)
|
||||||
if _, m := model.GetMFA(repoIam.DefaultLoginPolicy.MultiFactors, int32(mfa)); m != 0 {
|
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 iam_model.MultiFactorType(m), nil
|
||||||
}
|
}
|
||||||
return 0, caos_errs.ThrowInternal(nil, "EVENT-5N9so", "Errors.Internal")
|
return 0, caos_errs.ThrowInternal(nil, "EVENT-5N9so", "Errors.Internal")
|
||||||
|
@ -14,6 +14,7 @@ type IAMSetUp struct {
|
|||||||
Step6 *Step6
|
Step6 *Step6
|
||||||
Step7 *Step7
|
Step7 *Step7
|
||||||
Step8 *Step8
|
Step8 *Step8
|
||||||
|
Step9 *Step9
|
||||||
}
|
}
|
||||||
|
|
||||||
func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) {
|
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.Step6,
|
||||||
setup.Step7,
|
setup.Step7,
|
||||||
setup.Step8,
|
setup.Step8,
|
||||||
|
setup.Step9,
|
||||||
} {
|
} {
|
||||||
if step.step() <= currentDone {
|
if step.step() <= currentDone {
|
||||||
continue
|
continue
|
||||||
|
74
internal/setup/step9.go
Normal file
74
internal/setup/step9.go
Normal file
@ -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)
|
||||||
|
}
|
@ -1302,12 +1302,12 @@ func (es *UserEventstore) verifyMFAOTP(otp *usr_model.OTP, code string) error {
|
|||||||
return nil
|
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)
|
user, err := es.HumanByID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1418,12 +1418,12 @@ func (es *UserEventstore) GetPasswordless(ctx context.Context, userID string) ([
|
|||||||
return user.PasswordlessTokens, nil
|
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)
|
user, err := es.HumanByID(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -155,8 +155,12 @@ func (v *UserSessionView) AppendEvent(event *models.Event) error {
|
|||||||
es_model.HumanSignedOut,
|
es_model.HumanSignedOut,
|
||||||
es_model.UserLocked,
|
es_model.UserLocked,
|
||||||
es_model.UserDeactivated:
|
es_model.UserDeactivated:
|
||||||
|
v.PasswordlessVerification = time.Time{}
|
||||||
v.PasswordVerification = time.Time{}
|
v.PasswordVerification = time.Time{}
|
||||||
v.SecondFactorVerification = 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.ExternalLoginVerification = time.Time{}
|
||||||
v.State = int32(req_model.UserSessionStateTerminated)
|
v.State = int32(req_model.UserSessionStateTerminated)
|
||||||
case es_model.HumanExternalIDPRemoved, es_model.HumanExternalIDPCascadeRemoved:
|
case es_model.HumanExternalIDPRemoved, es_model.HumanExternalIDPCascadeRemoved:
|
||||||
|
@ -42,6 +42,7 @@ func StartServer(sd systemdefaults.WebAuthN) (*WebAuthN, error) {
|
|||||||
|
|
||||||
type webUser struct {
|
type webUser struct {
|
||||||
*usr_model.User
|
*usr_model.User
|
||||||
|
accountName string
|
||||||
credentials []webauthn.Credential
|
credentials []webauthn.Credential
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +51,10 @@ func (u *webUser) WebAuthnID() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *webUser) WebAuthnName() string {
|
func (u *webUser) WebAuthnName() string {
|
||||||
return u.PreferredLoginName
|
if u.accountName != "" {
|
||||||
|
return u.accountName
|
||||||
|
}
|
||||||
|
return u.UserName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *webUser) WebAuthnDisplayName() string {
|
func (u *webUser) WebAuthnDisplayName() string {
|
||||||
@ -65,7 +69,7 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
|
|||||||
return u.credentials
|
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)
|
creds := WebAuthNsToCredentials(webAuthNs)
|
||||||
existing := make([]protocol.CredentialDescriptor, len(creds))
|
existing := make([]protocol.CredentialDescriptor, len(creds))
|
||||||
for i, cred := range 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(
|
credentialOptions, sessionData, err := w.web(isLoginUI).BeginRegistration(
|
||||||
&webUser{
|
&webUser{
|
||||||
User: user,
|
User: user,
|
||||||
|
accountName: accountName,
|
||||||
credentials: creds,
|
credentials: creds,
|
||||||
},
|
},
|
||||||
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
|
webauthn.WithAuthenticatorSelection(protocol.AuthenticatorSelection{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user