mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:57:31 +00:00
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:
@@ -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")
|
||||
|
Reference in New Issue
Block a user