feat: passwordless registration (#2103)

* begin pw less registration

* create pwless one time codes

* send pwless link

* separate send and add passwordless link

* separate send and add passwordless link events

* custom message text for passwordless registration

* begin custom login texts for passwordless

* i18n

* i18n message

* i18n message

* custom message text

* custom login text

* org design and texts

* create link in human import process

* fix import human tests

* begin passwordless init required step

* passwordless init

* passwordless init

* do not return link in mgmt api

* prompt

* passwordless init only (no additional prompt)

* cleanup

* cleanup

* add passwordless prompt to custom login text

* increase init code complexity

* fix grpc

* cleanup

* fix and add some cases for nextStep tests

* fix tests

* Update internal/notification/static/i18n/en.yaml

* Update internal/notification/static/i18n/de.yaml

* Update proto/zitadel/management.proto

* Update internal/ui/login/static/i18n/de.yaml

* Update internal/ui/login/static/i18n/de.yaml

* Update internal/ui/login/static/i18n/de.yaml

Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
Livio Amstutz
2021-08-02 15:24:58 +02:00
committed by GitHub
parent 9b5cb38d62
commit 00220e9532
60 changed files with 2916 additions and 350 deletions

View File

@@ -2,9 +2,11 @@ package command
import (
"context"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@@ -127,8 +129,16 @@ func (c *Commands) HumanAddPasswordlessSetup(ctx context.Context, userID, resour
return createdWebAuthN, nil
}
func (c *Commands) HumanAddPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, codeID, verificationCode string) (*domain.WebAuthNToken, error) {
err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode)
if err != nil {
return nil, err
}
return c.HumanAddPasswordlessSetup(ctx, userID, resourceowner, true)
}
func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner string, isLoginUI bool, tokens []*domain.WebAuthNToken) (*HumanWebAuthNWriteModel, *eventstore.Aggregate, *domain.WebAuthNToken, error) {
if userID == "" || resourceowner == "" {
if userID == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := c.getHuman(ctx, userID, resourceowner)
@@ -198,7 +208,24 @@ func (c *Commands) HumanVerifyU2FSetup(ctx context.Context, userID, resourceowne
return writeModelToObjectDetails(&verifyWebAuthN.WriteModel), nil
}
func (c *Commands) HumanPasswordlessSetupInitCode(ctx context.Context, userID, resourceowner, tokenName, userAgentID, codeID, verificationCode string, credentialData []byte) (*domain.ObjectDetails, error) {
err := c.humanVerifyPasswordlessInitCode(ctx, userID, resourceowner, codeID, verificationCode)
if err != nil {
return nil, err
}
succeededEvent := func(userAgg *eventstore.Aggregate) *usr_repo.HumanPasswordlessInitCodeCheckSucceededEvent {
return usr_repo.NewHumanPasswordlessInitCodeCheckSucceededEvent(ctx, userAgg, codeID)
}
return c.humanHumanPasswordlessSetup(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, succeededEvent)
}
func (c *Commands) HumanHumanPasswordlessSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte) (*domain.ObjectDetails, error) {
return c.humanHumanPasswordlessSetup(ctx, userID, resourceowner, tokenName, userAgentID, credentialData, nil)
}
func (c *Commands) humanHumanPasswordlessSetup(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte,
codeCheckEvent func(*eventstore.Aggregate) *usr_repo.HumanPasswordlessInitCodeCheckSucceededEvent) (*domain.ObjectDetails, error) {
u2fTokens, err := c.getHumanPasswordlessTokens(ctx, userID, resourceowner)
if err != nil {
return nil, err
@@ -208,7 +235,7 @@ func (c *Commands) HumanHumanPasswordlessSetup(ctx context.Context, userID, reso
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx,
events := []eventstore.EventPusher{
usr_repo.NewHumanPasswordlessVerifiedEvent(
ctx,
userAgg,
@@ -221,7 +248,11 @@ func (c *Commands) HumanHumanPasswordlessSetup(ctx context.Context, userID, reso
webAuthN.SignCount,
userAgentID,
),
)
}
if codeCheckEvent != nil {
events = append(events, codeCheckEvent(userAgg))
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
@@ -233,7 +264,7 @@ func (c *Commands) HumanHumanPasswordlessSetup(ctx context.Context, userID, reso
}
func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowner, tokenName, userAgentID string, credentialData []byte, tokens []*domain.WebAuthNToken) (*eventstore.Aggregate, *domain.WebAuthNToken, *HumanWebAuthNWriteModel, error) {
if userID == "" || resourceowner == "" {
if userID == "" {
return nil, nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0od", "Errors.IDMissing")
}
user, err := c.getHuman(ctx, userID, resourceowner)
@@ -452,6 +483,102 @@ func (c *Commands) HumanRemovePasswordless(ctx context.Context, userID, webAuthN
return c.removeHumanWebAuthN(ctx, userID, webAuthNID, resourceOwner, event)
}
func (c *Commands) HumanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string) (*domain.PasswordlessInitCode, error) {
codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, true)
pushedEvents, err := c.eventstore.PushEvents(ctx, codeEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(initCode, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPasswordlessInitCode(initCode, code), nil
}
func (c *Commands) HumanSendPasswordlessInitCode(ctx context.Context, userID, resourceOwner string) (*domain.PasswordlessInitCode, error) {
codeEvent, initCode, code, err := c.humanAddPasswordlessInitCode(ctx, userID, resourceOwner, true)
pushedEvents, err := c.eventstore.PushEvents(ctx, codeEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(initCode, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToPasswordlessInitCode(initCode, code), nil
}
func (c *Commands) humanAddPasswordlessInitCode(ctx context.Context, userID, resourceOwner string, direct bool) (eventstore.EventPusher, *HumanPasswordlessInitCodeWriteModel, string, error) {
if userID == "" {
return nil, nil, "", caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing")
}
codeID, err := c.idGenerator.Next()
if err != nil {
return nil, nil, "", err
}
initCode := NewHumanPasswordlessInitCodeWriteModel(userID, codeID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, initCode)
if err != nil {
return nil, nil, "", err
}
cryptoCode, code, err := crypto.NewCode(c.passwordlessInitCode)
if err != nil {
return nil, nil, "", err
}
codeEventCreator := func(ctx context.Context, agg *eventstore.Aggregate, id string, cryptoCode *crypto.CryptoValue, exp time.Duration) eventstore.EventPusher {
return usr_repo.NewHumanPasswordlessInitCodeAddedEvent(ctx, agg, id, cryptoCode, exp)
}
if !direct {
codeEventCreator = func(ctx context.Context, agg *eventstore.Aggregate, id string, cryptoCode *crypto.CryptoValue, exp time.Duration) eventstore.EventPusher {
return usr_repo.NewHumanPasswordlessInitCodeRequestedEvent(ctx, agg, id, cryptoCode, exp)
}
}
codeEvent := codeEventCreator(ctx, UserAggregateFromWriteModel(&initCode.WriteModel), codeID, cryptoCode, c.passwordlessInitCode.Expiry())
return codeEvent, initCode, code, nil
}
func (c *Commands) HumanPasswordlessInitCodeSent(ctx context.Context, userID, resourceOwner, codeID string) error {
if userID == "" || codeID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-ADggh", "Errors.IDMissing")
}
initCode := NewHumanPasswordlessInitCodeWriteModel(userID, codeID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, initCode)
if err != nil {
return err
}
if initCode.State != domain.PasswordlessInitCodeStateRequested {
return caos_errs.ThrowNotFound(nil, "COMMAND-Gdfg3", "Errors.User.Code.NotFound")
}
_, err = c.eventstore.PushEvents(ctx,
usr_repo.NewHumanPasswordlessInitCodeSentEvent(ctx, UserAggregateFromWriteModel(&initCode.WriteModel), codeID),
)
return err
}
func (c *Commands) humanVerifyPasswordlessInitCode(ctx context.Context, userID, resourceOwner, codeID, verificationCode string) error {
if userID == "" || codeID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-GVfg3", "Errors.IDMissing")
}
initCode := NewHumanPasswordlessInitCodeWriteModel(userID, codeID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, initCode)
if err != nil {
return err
}
err = crypto.VerifyCode(initCode.ChangeDate, initCode.Expiration, initCode.CryptoCode, verificationCode, c.passwordlessInitCode)
if err != nil || initCode.State != domain.PasswordlessInitCodeStateActive {
userAgg := UserAggregateFromWriteModel(&initCode.WriteModel)
_, err = c.eventstore.PushEvents(ctx, usr_repo.NewHumanPasswordlessInitCodeCheckFailedEvent(ctx, userAgg, codeID))
logging.LogWithFields("COMMAND-Gkuud", "userID", userAgg.ID).OnError(err).Error("NewHumanPasswordlessInitCodeCheckFailedEvent push failed")
return caos_errs.ThrowInvalidArgument(err, "COMMAND-Dhz8i", "Errors.User.Code.Invalid")
}
return nil
}
func (c *Commands) removeHumanWebAuthN(ctx context.Context, userID, webAuthNID, resourceOwner string, preparedEvent func(*eventstore.Aggregate) eventstore.EventPusher) (*domain.ObjectDetails, error) {
if userID == "" || webAuthNID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M9de", "Errors.IDMissing")