mirror of
https://github.com/zitadel/zitadel.git
synced 2025-06-03 05:38:21 +00:00
feat: ResetPassword endpoint
This commit is contained in:
parent
1e9d58c924
commit
04f5ed8d1c
@ -31,3 +31,31 @@ func setPasswordRequestToSetSchemaUserPassword(req *user.SetPasswordRequest) *co
|
||||
ChangeRequired: req.GetNewPassword().GetChangeRequired(),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
func (s *Server) RemovePassword(ctx context.Context, req *user.RemovePasswordRequest) (_ *user.RemovePasswordResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteSchemaUserPassword(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemovePasswordResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePassword(ctx context.Context, req *user.RemovePasswordRequest) (_ *user.RemovePasswordResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteSchemaUserPassword(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemovePasswordResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
*/
|
||||
|
@ -2,10 +2,12 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
@ -16,6 +18,9 @@ type SetSchemaUserPassword struct {
|
||||
Password string
|
||||
EncodedPasswordHash string
|
||||
ChangeRequired bool
|
||||
|
||||
CurrentPassword string
|
||||
VerificationCode string
|
||||
}
|
||||
|
||||
func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
|
||||
@ -35,38 +40,47 @@ func (p *SetSchemaUserPassword) Validate(hasher *crypto.Hasher) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetSchemaUserPassword(ctx context.Context, username *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
|
||||
if err := username.Validate(c.userPasswordHasher); err != nil {
|
||||
func (c *Commands) SetSchemaUserPassword(ctx context.Context, user *SetSchemaUserPassword) (*domain.ObjectDetails, error) {
|
||||
if err := user.Validate(c.userPasswordHasher); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existing, err := c.getPasswordExistsWithVerification(ctx, username.ResourceOwner, username.UserID)
|
||||
schemaUser := &schemaUserPassword{
|
||||
ResourceOwner: user.ResourceOwner,
|
||||
UserID: user.UserID,
|
||||
VerificationCode: user.VerificationCode,
|
||||
CurrentPassword: user.CurrentPassword,
|
||||
Password: user.Password,
|
||||
EncodedPasswordHash: user.EncodedPasswordHash,
|
||||
}
|
||||
|
||||
existing, err := c.getSchemaUserPasswordWithVerification(ctx, schemaUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceOwner := existing.ResourceOwner
|
||||
if existing.EncodedHash == "" {
|
||||
existingUser, err := c.getSchemaUserExists(ctx, username.ResourceOwner, username.UserID)
|
||||
existingUser, err := c.getSchemaUserExists(ctx, user.ResourceOwner, user.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !existingUser.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "TODO", "TODO")
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")
|
||||
}
|
||||
resourceOwner = existingUser.ResourceOwner
|
||||
}
|
||||
|
||||
// If password is provided, let's check if is compliant with the policy.
|
||||
// If only a encodedPassword is passed, we can skip this.
|
||||
if username.Password != "" {
|
||||
if err = c.checkPasswordComplexity(ctx, username.Password, resourceOwner); err != nil {
|
||||
if user.Password != "" {
|
||||
if err = c.checkPasswordComplexity(ctx, user.Password, resourceOwner); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
encodedPassword := username.EncodedPasswordHash
|
||||
if username.Password != "" {
|
||||
encodedPassword, err = c.userPasswordHasher.Hash(username.Password)
|
||||
encodedPassword := schemaUser.EncodedPasswordHash
|
||||
if user.Password != "" {
|
||||
encodedPassword, err = c.userPasswordHasher.Hash(user.Password)
|
||||
if err = convertPasswapErr(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -74,10 +88,10 @@ func (c *Commands) SetSchemaUserPassword(ctx context.Context, username *SetSchem
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
authenticator.NewPasswordCreatedEvent(ctx,
|
||||
&authenticator.NewAggregate(username.UserID, resourceOwner).Aggregate,
|
||||
&authenticator.NewAggregate(user.UserID, resourceOwner).Aggregate,
|
||||
existing.UserID,
|
||||
encodedPassword,
|
||||
username.ChangeRequired,
|
||||
user.ChangeRequired,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
@ -86,8 +100,51 @@ func (c *Commands) SetSchemaUserPassword(ctx context.Context, username *SetSchem
|
||||
return pushedEventsToObjectDetails(events), nil
|
||||
}
|
||||
|
||||
type RequestSchemaUserPasswordReset struct {
|
||||
ResourceOwner string
|
||||
UserID string
|
||||
|
||||
URLTemplate string
|
||||
NotificationType domain.NotificationType
|
||||
PlainCode string
|
||||
ReturnCode bool
|
||||
}
|
||||
|
||||
func (c *Commands) RequestSchemaUserPasswordReset(ctx context.Context, user *RequestSchemaUserPasswordReset) (_ *domain.ObjectDetails, err error) {
|
||||
existing, err := c.getSchemaUserPasswordExists(ctx, user.ResourceOwner, user.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if existing.EncodedHash == "" {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-TODO", "Errors.User.Password.NotFound")
|
||||
}
|
||||
|
||||
code, err := c.newEncryptedCode(ctx, c.eventstore.Filter, domain.SecretGeneratorTypePasswordResetCode, c.userEncryption) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := c.eventstore.Push(ctx,
|
||||
authenticator.NewPasswordCodeAddedEvent(ctx,
|
||||
&authenticator.NewAggregate(existing.UserID, existing.ResourceOwner).Aggregate,
|
||||
code.Crypted,
|
||||
code.Expiry,
|
||||
user.NotificationType,
|
||||
user.URLTemplate,
|
||||
user.ReturnCode,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user.ReturnCode {
|
||||
user.PlainCode = code.Plain
|
||||
}
|
||||
return pushedEventsToObjectDetails(events), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner, id string) (_ *domain.ObjectDetails, err error) {
|
||||
existing, err := c.getPasswordExistsWithVerification(ctx, resourceOwner, id)
|
||||
existing, err := c.getSchemaUserPasswordExists(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -106,18 +163,107 @@ func (c *Commands) DeleteSchemaUserPassword(ctx context.Context, resourceOwner,
|
||||
return pushedEventsToObjectDetails(events), nil
|
||||
}
|
||||
|
||||
func (c *Commands) getPasswordExistsWithVerification(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) {
|
||||
if id == "" {
|
||||
type schemaUserPassword struct {
|
||||
ResourceOwner string
|
||||
UserID string
|
||||
VerificationCode string
|
||||
CurrentPassword string
|
||||
Password string
|
||||
EncodedPasswordHash string
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserPasswordExists(ctx context.Context, resourceOwner, id string) (*PasswordV3WriteModel, error) {
|
||||
return c.getSchemaUserPasswordWithVerification(ctx, &schemaUserPassword{ResourceOwner: resourceOwner, UserID: id})
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserPasswordWithVerification(ctx context.Context, user *schemaUserPassword) (*PasswordV3WriteModel, error) {
|
||||
if user.UserID == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-PoSU5BOZCi", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewPasswordV3WriteModel(resourceOwner, id)
|
||||
writeModel := NewPasswordV3WriteModel(user.ResourceOwner, user.UserID)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO permission through old password and password code
|
||||
if err := c.checkPermissionUpdateUser(ctx, writeModel.ResourceOwner, writeModel.UserID); err != nil {
|
||||
return nil, err
|
||||
// if no verification is set, the user must have the permission to change the password
|
||||
verification := c.setSchemaUserPasswordWithPermission(writeModel.UserID, writeModel.ResourceOwner)
|
||||
// otherwise check the password code...
|
||||
if user.VerificationCode != "" {
|
||||
verification = c.setSchemaUserPasswordWithVerifyCode(writeModel.CodeCreationDate, writeModel.CodeExpiry, writeModel.Code, user.VerificationCode)
|
||||
}
|
||||
// ...or old password
|
||||
if user.CurrentPassword != "" {
|
||||
verification = c.checkCurrentPassword(user.Password, user.EncodedPasswordHash, user.CurrentPassword, writeModel.EncodedHash)
|
||||
}
|
||||
|
||||
if verification != nil {
|
||||
newEncodedPassword, err := verification(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// use the new hash from the verification in case there is one (e.g. existing pw check)
|
||||
if newEncodedPassword != "" {
|
||||
user.EncodedPasswordHash = newEncodedPassword
|
||||
}
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
||||
// setSchemaUserPasswordWithPermission returns a permission check as [setPasswordVerification] implementation
|
||||
func (c *Commands) setSchemaUserPasswordWithPermission(orgID, userID string) setPasswordVerification {
|
||||
return func(ctx context.Context) (_ string, err error) {
|
||||
return "", c.checkPermissionUpdateUser(ctx, orgID, userID)
|
||||
}
|
||||
}
|
||||
|
||||
// setSchemaUserPasswordWithVerifyCode returns a password code check as [setPasswordVerification] implementation
|
||||
func (c *Commands) setSchemaUserPasswordWithVerifyCode(
|
||||
passwordCodeCreationDate time.Time,
|
||||
passwordCodeExpiry time.Duration,
|
||||
passwordCode *crypto.CryptoValue,
|
||||
code string,
|
||||
) setPasswordVerification {
|
||||
return func(ctx context.Context) (_ string, err error) {
|
||||
if passwordCode == nil {
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-TODO", "Errors.User.Code.NotFound")
|
||||
}
|
||||
_, spanCrypto := tracing.NewNamedSpan(ctx, "crypto.VerifyCode")
|
||||
defer func() {
|
||||
spanCrypto.EndWithError(err)
|
||||
}()
|
||||
return "", crypto.VerifyCode(passwordCodeCreationDate, passwordCodeExpiry, passwordCode, code, c.userEncryption)
|
||||
}
|
||||
}
|
||||
|
||||
// checkSchemaUserCurrentPassword returns a password check as [setPasswordVerification] implementation
|
||||
func (c *Commands) checkSchemaUserCurrentPassword(
|
||||
newPassword, newEncodedPassword, currentPassword, currentEncodePassword string,
|
||||
) setPasswordVerification {
|
||||
// in case the new password is already encoded, we only need to verify the current
|
||||
if newEncodedPassword != "" {
|
||||
return func(ctx context.Context) (_ string, err error) {
|
||||
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||
_, err = c.userPasswordHasher.Verify(currentEncodePassword, currentPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
return "", convertPasswapErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// otherwise let's directly verify and return the new generate hash, so we can reuse it in the event
|
||||
return func(ctx context.Context) (string, error) {
|
||||
return c.verifyAndUpdateSchemaUserPassword(ctx, currentEncodePassword, currentPassword, newPassword)
|
||||
}
|
||||
}
|
||||
|
||||
// verifyAndUpdateSchemaUserPassword verify if the old password is correct with the encoded hash and
|
||||
// returns the hash of the new password if so
|
||||
func (c *Commands) verifyAndUpdateSchemaUserPassword(ctx context.Context, encodedHash, oldPassword, newPassword string) (string, error) {
|
||||
if encodedHash == "" {
|
||||
return "", zerrors.ThrowPreconditionFailed(nil, "COMMAND-TODO", "Errors.User.Password.NotSet")
|
||||
}
|
||||
|
||||
_, spanPasswap := tracing.NewNamedSpan(ctx, "passwap.Verify")
|
||||
updated, err := c.userPasswordHasher.VerifyAndUpdate(encodedHash, oldPassword, newPassword)
|
||||
spanPasswap.EndWithError(err)
|
||||
return updated, convertPasswapErr(err)
|
||||
}
|
||||
|
@ -1,7 +1,11 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/authenticator"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
)
|
||||
@ -12,6 +16,10 @@ type PasswordV3WriteModel struct {
|
||||
|
||||
EncodedHash string
|
||||
ChangeRequired bool
|
||||
|
||||
Code *crypto.CryptoValue
|
||||
CodeCreationDate time.Time
|
||||
CodeExpiry time.Duration
|
||||
}
|
||||
|
||||
func NewPasswordV3WriteModel(resourceOwner, id string) *PasswordV3WriteModel {
|
||||
@ -31,10 +39,16 @@ func (wm *PasswordV3WriteModel) Reduce() error {
|
||||
wm.UserID = e.UserID
|
||||
wm.EncodedHash = e.EncodedHash
|
||||
wm.ChangeRequired = e.ChangeRequired
|
||||
wm.Code = nil
|
||||
case *authenticator.PasswordDeletedEvent:
|
||||
wm.UserID = ""
|
||||
wm.EncodedHash = ""
|
||||
wm.ChangeRequired = false
|
||||
wm.Code = nil
|
||||
case *user.HumanPasswordCodeAddedEvent:
|
||||
wm.Code = e.Code
|
||||
wm.CodeCreationDate = e.CreationDate()
|
||||
wm.CodeExpiry = e.Expiry
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
@ -49,5 +63,6 @@ func (wm *PasswordV3WriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
EventTypes(
|
||||
authenticator.PasswordCreatedType,
|
||||
authenticator.PasswordDeletedType,
|
||||
authenticator.PasswordCodeAddedType,
|
||||
).Builder()
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ func filterSchemaUserPasswordExisting() expect {
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"encoded",
|
||||
"$plain$x$password",
|
||||
false,
|
||||
),
|
||||
),
|
||||
@ -242,6 +242,73 @@ func TestCommands_SetSchemaUserPassword(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password set, current password, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
filterPasswordComplexityPolicyExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"$plain$x$password2",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password2",
|
||||
CurrentPassword: "password",
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"password set, code, ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
filterSchemaUserPasswordExisting(),
|
||||
filterPasswordComplexityPolicyExisting(),
|
||||
expectPush(
|
||||
authenticator.NewPasswordCreatedEvent(
|
||||
context.Background(),
|
||||
&authenticator.NewAggregate("user1", "org1").Aggregate,
|
||||
"user1",
|
||||
"$plain$x$password2",
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||
userPasswordHasher: mockPasswordHasher("x"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
user: &SetSchemaUserPassword{
|
||||
UserID: "user1",
|
||||
Password: "password2",
|
||||
ChangeRequired: false,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -7,4 +7,8 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, UsernameDeletedType, eventstore.GenericEventMapper[UsernameDeletedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PasswordCreatedType, eventstore.GenericEventMapper[PasswordCreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PasswordDeletedType, eventstore.GenericEventMapper[PasswordDeletedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, JWTCreatedType, eventstore.GenericEventMapper[JWTCreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, JWTDeletedType, eventstore.GenericEventMapper[JWTDeletedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PATCreatedType, eventstore.GenericEventMapper[PATCreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, PATDeletedType, eventstore.GenericEventMapper[PATDeletedEvent])
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ type JWTCreatedEvent struct {
|
||||
|
||||
UserID string `json:"userID"`
|
||||
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
TriggerOrigin string `json:"triggerOrigin,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *JWTCreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
@ -36,6 +36,10 @@ func (e *JWTCreatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *JWTCreatedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func NewJWTCreatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
@ -49,10 +53,10 @@ func NewJWTCreatedEvent(
|
||||
aggregate,
|
||||
JWTCreatedType,
|
||||
),
|
||||
UserID: userID,
|
||||
ExpirationDate: expirationDate,
|
||||
PublicKey: publicKey,
|
||||
TriggerOrigin: http.DomainContext(ctx).Origin(),
|
||||
UserID: userID,
|
||||
ExpirationDate: expirationDate,
|
||||
PublicKey: publicKey,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,24 +2,29 @@ package authenticator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
passwordPrefix = eventPrefix + "password."
|
||||
PasswordCreatedType = passwordPrefix + "created"
|
||||
PasswordDeletedType = passwordPrefix + "deleted"
|
||||
passwordPrefix = eventPrefix + "password."
|
||||
PasswordCreatedType = passwordPrefix + "created"
|
||||
PasswordDeletedType = passwordPrefix + "deleted"
|
||||
PasswordCodeAddedType = passwordPrefix + "code.added"
|
||||
)
|
||||
|
||||
type PasswordCreatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
UserID string `json:"userID"`
|
||||
EncodedHash string `json:"encodedHash,omitempty"`
|
||||
ChangeRequired bool `json:"changeRequired,omitempty"`
|
||||
TriggerOrigin string `json:"triggerOrigin,omitempty"`
|
||||
UserID string `json:"userID"`
|
||||
|
||||
EncodedHash string `json:"encodedHash,omitempty"`
|
||||
ChangeRequired bool `json:"changeRequired,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PasswordCreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
@ -34,6 +39,10 @@ func (e *PasswordCreatedEvent) UniqueConstraints() []*eventstore.UniqueConstrain
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PasswordCreatedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func NewPasswordCreatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
@ -47,10 +56,10 @@ func NewPasswordCreatedEvent(
|
||||
aggregate,
|
||||
PasswordCreatedType,
|
||||
),
|
||||
UserID: userID,
|
||||
EncodedHash: encodeHash,
|
||||
ChangeRequired: changeRequired,
|
||||
TriggerOrigin: http.DomainContext(ctx).Origin(),
|
||||
UserID: userID,
|
||||
EncodedHash: encodeHash,
|
||||
ChangeRequired: changeRequired,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,3 +91,54 @@ func NewPasswordDeletedEvent(
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type PasswordCodeAddedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
NotificationType domain.NotificationType `json:"notificationType,omitempty"`
|
||||
URLTemplate string `json:"url_template,omitempty"`
|
||||
CodeReturned bool `json:"code_returned,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PasswordCodeAddedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PasswordCodeAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PasswordCodeAddedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func NewPasswordCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
notificationType domain.NotificationType,
|
||||
urlTemplate string,
|
||||
codeReturned bool,
|
||||
) *PasswordCodeAddedEvent {
|
||||
return &PasswordCodeAddedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PasswordCodeAddedType,
|
||||
),
|
||||
Code: code,
|
||||
Expiry: expiry,
|
||||
NotificationType: notificationType,
|
||||
URLTemplate: urlTemplate,
|
||||
CodeReturned: codeReturned,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PasswordCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ type PATCreatedEvent struct {
|
||||
|
||||
UserID string `json:"userID"`
|
||||
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
Scopes []string `json:"scopes"`
|
||||
TriggerOrigin string `json:"triggerOrigin,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
Scopes []string `json:"scopes"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PATCreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
@ -49,10 +49,10 @@ func NewPATCreatedEvent(
|
||||
aggregate,
|
||||
PATCreatedType,
|
||||
),
|
||||
UserID: userID,
|
||||
ExpirationDate: expirationDate,
|
||||
Scopes: scopes,
|
||||
TriggerOrigin: http.DomainContext(ctx).Origin(),
|
||||
UserID: userID,
|
||||
ExpirationDate: expirationDate,
|
||||
Scopes: scopes,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,7 +570,7 @@ service ZITADELUsers {
|
||||
|
||||
// Set a password
|
||||
//
|
||||
// Add, update or reset a user's password with either a verification code or the current password.
|
||||
// Add or update a user's password with either a verification code, the current password or enough permissions.
|
||||
rpc SetPassword (SetPasswordRequest) returns (SetPasswordResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/resources/v3alpha/users/{id}/password"
|
||||
@ -618,6 +618,30 @@ service ZITADELUsers {
|
||||
};
|
||||
}
|
||||
|
||||
// Remove a password
|
||||
//
|
||||
// Remove a user's password.
|
||||
rpc RemovePassword (RemovePasswordRequest) returns (RemovePasswordResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/resources/v3alpha/users/{id}/password"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200"
|
||||
value: {
|
||||
description: "Password successfully removed";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Start a WebAuthN registration
|
||||
//
|
||||
// Start the registration of a new WebAuthN device (e.g. Passkeys) for a user.
|
||||
@ -1587,6 +1611,30 @@ message RequestPasswordResetResponse {
|
||||
];
|
||||
}
|
||||
|
||||
message RemovePasswordRequest {
|
||||
optional zitadel.object.v3alpha.Instance instance = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
default: "\"domain from HOST or :authority header\""
|
||||
}
|
||||
];
|
||||
// Optionally expect the user to be in this organization.
|
||||
optional zitadel.object.v3alpha.Organization organization = 2;
|
||||
// unique identifier of the user.
|
||||
string id = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
min_length: 1,
|
||||
max_length: 200,
|
||||
example: "\"69629026806489455\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message RemovePasswordResponse {
|
||||
zitadel.resources.object.v3alpha.Details details = 1;
|
||||
}
|
||||
|
||||
message StartWebAuthNRegistrationRequest {
|
||||
optional zitadel.object.v3alpha.Instance instance = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user