feat: integrate passwap for human user password hashing (#6196)

* feat: use passwap for human user passwords

* fix tests

* passwap config

* add the event mapper

* cleanup query side and api

* solve linting errors

* regression test

* try to fix linter errors again

* pass systemdefaults into externalConfigChange migration

* fix: user password set in auth view

* pin passwap v0.2.0

* v2: validate hashed password hash based on prefix

* resolve remaining comments

* add error tag and translation for unsupported hash encoding

* fix unit test

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Tim Möhlmann
2023-07-14 09:49:57 +03:00
committed by GitHub
parent 6fcfa63f54
commit 4589ddad4a
56 changed files with 1853 additions and 775 deletions

View File

@@ -17,6 +17,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, UserV1PasswordCodeSentType, HumanPasswordCodeSentEventMapper).
RegisterFilterEventMapper(AggregateType, UserV1PasswordCheckSucceededType, HumanPasswordCheckSucceededEventMapper).
RegisterFilterEventMapper(AggregateType, UserV1PasswordCheckFailedType, HumanPasswordCheckFailedEventMapper).
RegisterFilterEventMapper(AggregateType, UserV1PasswordHashUpdatedType, eventstore.GenericEventMapper[HumanPasswordHashUpdatedEvent]).
RegisterFilterEventMapper(AggregateType, UserV1EmailChangedType, HumanEmailChangedEventMapper).
RegisterFilterEventMapper(AggregateType, UserV1EmailVerifiedType, HumanEmailVerifiedEventMapper).
RegisterFilterEventMapper(AggregateType, UserV1EmailVerificationFailedType, HumanEmailVerificationFailedEventMapper).

View File

@@ -48,7 +48,10 @@ type HumanAddedEvent struct {
Region string `json:"region,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
// New events only use EncodedHash. However, the secret field
// is preserved to handle events older than the switch to Passwap.
Secret *crypto.CryptoValue `json:"secret,omitempty"`
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
}
@@ -81,10 +84,10 @@ func (e *HumanAddedEvent) AddPhoneData(
}
func (e *HumanAddedEvent) AddPasswordData(
secret *crypto.CryptoValue,
encoded string,
changeRequired bool,
) {
e.Secret = secret
e.EncodedHash = encoded
e.ChangeRequired = changeRequired
}
@@ -149,8 +152,12 @@ type HumanRegisteredEvent struct {
PostalCode string `json:"postalCode,omitempty"`
Region string `json:"region,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
Secret *crypto.CryptoValue `json:"secret,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
// New events only use EncodedHash. However, the secret field
// is preserved to handle events older than the switch to Passwap.
Secret *crypto.CryptoValue `json:"secret,omitempty"` // legacy
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
}
func (e *HumanRegisteredEvent) Data() interface{} {
@@ -182,10 +189,10 @@ func (e *HumanRegisteredEvent) AddPhoneData(
}
func (e *HumanRegisteredEvent) AddPasswordData(
secret *crypto.CryptoValue,
encoded string,
changeRequired bool,
) {
e.Secret = secret
e.EncodedHash = encoded
e.ChangeRequired = changeRequired
}

View File

@@ -26,7 +26,10 @@ const (
type HumanPasswordChangedEvent struct {
eventstore.BaseEvent `json:"-"`
// New events only use EncodedHash. However, the secret field
// is preserved to handle events older than the switch to Passwap.
Secret *crypto.CryptoValue `json:"secret,omitempty"`
EncodedHash string `json:"encodedHash,omitempty"`
ChangeRequired bool `json:"changeRequired"`
UserAgentID string `json:"userAgentID,omitempty"`
}
@@ -42,7 +45,7 @@ func (e *HumanPasswordChangedEvent) UniqueConstraints() []*eventstore.EventUniqu
func NewHumanPasswordChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
secret *crypto.CryptoValue,
encodeHash string,
changeRequired bool,
userAgentID string,
) *HumanPasswordChangedEvent {
@@ -52,7 +55,7 @@ func NewHumanPasswordChangedEvent(
aggregate,
HumanPasswordChangedType,
),
Secret: secret,
EncodedHash: encodeHash,
ChangeRequired: changeRequired,
UserAgentID: userAgentID,
}
@@ -268,3 +271,44 @@ func HumanPasswordCheckFailedEventMapper(event *repository.Event) (eventstore.Ev
return humanAdded, nil
}
type HumanPasswordHashUpdatedEvent struct {
eventstore.BaseEvent `json:"-"`
EncodedHash string `json:"encodedHash,omitempty"`
}
func (e *HumanPasswordHashUpdatedEvent) Data() interface{} {
return e
}
func (e *HumanPasswordHashUpdatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func (e *HumanPasswordHashUpdatedEvent) SetBaseEvent(base *eventstore.BaseEvent) {
e.BaseEvent = *base
}
func NewHumanPasswordHashUpdatedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
encoded string,
) *HumanPasswordHashUpdatedEvent {
return &HumanPasswordHashUpdatedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanPasswordCheckFailedType,
),
EncodedHash: encoded,
}
}
// SecretOrEncodedHash returns the legacy *crypto.CryptoValue if it is not nil.
// orherwise it will returns the encoded hash string.
func SecretOrEncodedHash(secret *crypto.CryptoValue, encoded string) string {
if secret != nil {
return string(secret.Crypted)
}
return encoded
}

View File

@@ -15,6 +15,7 @@ const (
UserV1PasswordCodeSentType = userV1PasswordEventTypePrefix + "code.sent"
UserV1PasswordCheckSucceededType = userV1PasswordEventTypePrefix + "check.succeeded"
UserV1PasswordCheckFailedType = userV1PasswordEventTypePrefix + "check.failed"
UserV1PasswordHashUpdatedType = userV1PasswordEventTypePrefix + "hash.updated"
userV1EmailEventTypePrefix = userEventTypePrefix + "email."
UserV1EmailChangedType = userV1EmailEventTypePrefix + "changed"