mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
This pull request introduces a new set of domain models and repositories for managing users within the new v3 structure. The goal is to establish a clean, robust, and extensible foundation for user management. ### Summary This PR accomplishes the following: - **Polymorphic User Model**: Introduces a `User` model in user.go that can represent either a `Human` or a `Machine` user. This provides a unified way to handle different user types while allowing for specific attributes and logic for each. - **Repository Pattern**: Implements the repository pattern for accessing and manipulating user data. This decouples the business logic from the data storage implementation. The main repository interfaces (`UserRepository`, `HumanUserRepository`, `MachineUserRepository`) are now defined in `backend/v3/domain/user_repository.go`. - **Comprehensive User Models**: The user.go file now also defines the domain models for associated user data, including `UserMetadata`, `IdentityProviderLink`, `Passkey`, `PersonalAccessToken`, and `MachineKey`. - **Generic Verification and Check System**: Introduces verification.go and check.go to provide a standardized way of handling various verification flows (e.g., email verification, OTP checks). - **Generated Code**: Adds new generated files for enums (`humangender_enumer.go`, `machinekeytype_enumer.go`, `passkeytype_enumer.go`, `personalaccesstokentype_enumer.go`, `userstate_enumer.go`, `usertype_enumer.go`) and mocks for the new repositories. - **Session Domain Refactoring**: Updates the session domain (`backend/v3/domain/session*.go`) to align with the new user model structure. This foundational work paves the way for migrating user management logic to the new `v3` backend, with a more maintainable and testable architecture. part of #10224 --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Marco A. <[email protected]>
315 lines
14 KiB
Go
315 lines
14 KiB
Go
package domain
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
)
|
|
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/user.mock.go . UserRepository
|
|
|
|
type UserRepository interface {
|
|
Repository
|
|
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*User, error)
|
|
List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*User, error)
|
|
Create(ctx context.Context, client database.QueryExecutor, user *User) error
|
|
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error)
|
|
Delete(ctx context.Context, client database.QueryExecutor, condition database.Condition) (int64, error)
|
|
|
|
Human() HumanUserRepository
|
|
Machine() MachineUserRepository
|
|
|
|
LoadMetadata() UserRepository
|
|
LoadPasskeys() UserRepository
|
|
LoadIdentityProviderLinks() UserRepository
|
|
LoadVerifications() UserRepository
|
|
LoadPATs() UserRepository
|
|
LoadKeys() UserRepository
|
|
|
|
userConditions
|
|
userChanges
|
|
userColumns
|
|
}
|
|
|
|
type userConditions interface {
|
|
PrimaryKeyCondition(instanceID, userID string) database.Condition
|
|
InstanceIDCondition(instanceID string) database.Condition
|
|
OrgIDCondition(orgID string) database.Condition
|
|
IDCondition(userID string) database.Condition
|
|
UsernameCondition(op database.TextOperation, username string) database.Condition
|
|
StateCondition(state UserState) database.Condition
|
|
TypeCondition(userType UserType) database.Condition
|
|
LoginNameCondition(op database.TextOperation, loginName string) database.Condition
|
|
|
|
userMetadataConditions
|
|
}
|
|
|
|
type userMetadataConditions interface {
|
|
ExistsMetadata(condition database.Condition) database.Condition
|
|
MetadataKeyCondition(op database.TextOperation, key string) database.Condition
|
|
MetadataValueCondition(op database.BytesOperation, value []byte) database.Condition
|
|
}
|
|
|
|
type userChanges interface {
|
|
// SetUsername sets the username field
|
|
SetUsername(username string) database.Change
|
|
// SetState sets the state field
|
|
// [UserStateUnspecified] is not allowed and will result in no change
|
|
SetState(state UserState) database.Change
|
|
// SetUpdatedAt sets the updated at field
|
|
// This is used to replay events
|
|
SetUpdatedAt(updatedAt time.Time) database.Change
|
|
// AddMetadata adds metadata for the user
|
|
AddMetadata(metadata ...*Metadata) database.Change
|
|
// RemoveMetadata removes the metadata for the user
|
|
// Use one of the following conditions to specify which metadata to remove:
|
|
// - [userMetadataConditions.MetadataKeyCondition]
|
|
// - [userMetadataConditions.MetadataValueCondition]
|
|
// - nil to remove all metadata for the user
|
|
RemoveMetadata(condition database.Condition) database.Change
|
|
}
|
|
|
|
type userColumns interface {
|
|
PrimaryKeyColumns() []database.Column
|
|
UsernameColumn() database.Column
|
|
StateColumn() database.Column
|
|
TypeColumn() database.Column
|
|
CreatedAtColumn() database.Column
|
|
}
|
|
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/human_user.mock.go . HumanUserRepository
|
|
|
|
type HumanUserRepository interface {
|
|
// Update updates the human user
|
|
// It ensures that updates are only applied to human users
|
|
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error)
|
|
humanConditions
|
|
humanChanges
|
|
humanColumns
|
|
}
|
|
|
|
type humanConditions interface {
|
|
userConditions
|
|
FirstNameCondition(op database.TextOperation, firstName string) database.Condition
|
|
LastNameCondition(op database.TextOperation, lastName string) database.Condition
|
|
NicknameCondition(op database.TextOperation, nickname string) database.Condition
|
|
DisplayNameCondition(op database.TextOperation, displayName string) database.Condition
|
|
PreferredLanguageCondition(lang language.Tag) database.Condition
|
|
EmailCondition(op database.TextOperation, email string) database.Condition
|
|
PhoneCondition(op database.TextOperation, phone string) database.Condition
|
|
PasskeyIDCondition(passkeyID string) database.Condition
|
|
IdentityProviderIDCondition(idpID string) database.Condition
|
|
ProvidedUserIDCondition(providedUserID string) database.Condition
|
|
ProvidedUsernameCondition(username string) database.Condition
|
|
|
|
humanPasskeyConditions
|
|
}
|
|
|
|
type humanChanges interface {
|
|
userChanges
|
|
// SetFirstName sets the first name field
|
|
SetFirstName(firstName string) database.Change
|
|
// SetLastName sets the last name field
|
|
SetLastName(lastName string) database.Change
|
|
// SetNickname sets the nickname field
|
|
SetNickname(nickname string) database.Change
|
|
// SetDisplayName sets the display name field
|
|
SetDisplayName(displayName string) database.Change
|
|
// SetPreferredLanguage sets the preferred language field
|
|
// If preferredLanguage is [language.Und], it will be set to NULL in the database
|
|
SetPreferredLanguage(preferredLanguage language.Tag) database.Change
|
|
// SetGender sets the gender field
|
|
// If gender is [HumanGenderUnspecified], it will be set to NULL in the database
|
|
SetGender(gender HumanGender) database.Change
|
|
// SetAvatarKey sets the avatar key field
|
|
// If avatarKey is nil, it will be set to NULL in the database
|
|
SetAvatarKey(avatarKey *string) database.Change
|
|
// SetMultifactorInitializationSkippedAt sets the multifactor initialization skipped at field
|
|
SetMultifactorInitializationSkippedAt(skippedAt time.Time) database.Change
|
|
|
|
// SetPassword sets the password based on the verification
|
|
SetPassword(verification VerificationType) database.Change
|
|
// SetPasswordChangeRequired sets whether a password change is required
|
|
SetPasswordChangeRequired(required bool) database.Change
|
|
// CheckPassword sets the password check based on the check type
|
|
// * [CheckTypeFailed] increments failed attempts
|
|
// * [CheckTypeSucceeded] to mark the check as succeeded, removes the check and updates verified at time
|
|
CheckPassword(check PasswordCheckType) database.Change
|
|
|
|
// SetVerification sets the verification based on the verification type
|
|
SetVerification(id string, verification VerificationType) database.Change
|
|
|
|
humanEmailChanges
|
|
humanPhoneChanges
|
|
humanTOTPChanges
|
|
identityProviderLinkChanges
|
|
humanPasskeyChanges
|
|
}
|
|
|
|
type humanEmailChanges interface {
|
|
// SetEmail sets the email based on the verification
|
|
// * [VerificationTypeInit] to initialize email verification, previously verified email remains verified
|
|
// * [VerificationTypeVerified] to mark email as verified, a verification must exist
|
|
// * [VerificationTypeUpdate] to update email verification, a verification must exist (e.g. resend code)
|
|
// * [VerificationTypeSkipped] to skip email verification, existing verification is removed (e.g. admin set email)
|
|
SetEmail(verification VerificationType) database.Change
|
|
// CheckEmailOTP sets the email OTP based on the check
|
|
// * [CheckTypeInit] to initialize a new check, previous check is overwritten
|
|
// * [CheckTypeFailed] increments failed attempts
|
|
// * [CheckTypeSucceeded] to mark the check as succeeded, removes the check and updates verified at time
|
|
CheckEmailOTP(check CheckType) database.Change
|
|
// EnableEmailOTPAt enables the email OTP
|
|
// If enabledAt is zero, it will be set to NOW()
|
|
EnableEmailOTPAt(enabledAt time.Time) database.Change
|
|
// EnableEmailOTP sets the enabled at time to NOW()
|
|
EnableEmailOTP() database.Change
|
|
// DisableEmailOTP clears the enabled at time
|
|
DisableEmailOTP() database.Change
|
|
}
|
|
|
|
type humanPhoneChanges interface {
|
|
// SetPhone sets the phone based on the verification
|
|
// * [VerificationTypeInit] to initialize phone verification, previously verified phone remains verified
|
|
// * [VerificationTypeVerified] to mark phone as verified, a verification must exist
|
|
// * [VerificationTypeUpdate] to update phone verification, a verification must exist (e.g. resend code)
|
|
// * [VerificationTypeSkipped] to skip phone verification, existing verification is removed (e.g. admin set phone)
|
|
SetPhone(verification VerificationType) database.Change
|
|
// RemovePhone removes the phone number
|
|
RemovePhone() database.Change
|
|
// CheckSMSOTP sets the SMS OTP based on the check
|
|
// * [CheckTypeInit] to initialize a new check, previous check is overwritten
|
|
// * [CheckTypeFailed] increments failed attempts
|
|
// * [CheckTypeSucceeded] to mark the check as succeeded, removes the check and updates verified at time
|
|
CheckSMSOTP(check CheckType) database.Change
|
|
// EnableSMSOTPAt enables the SMS OTP
|
|
// If enabledAt is zero, it will be set to NOW()
|
|
EnableSMSOTPAt(enabledAt time.Time) database.Change
|
|
// EnableSMSOTP sets the enabled at time to NOW()
|
|
EnableSMSOTP() database.Change
|
|
// DisableSMSOTP clears the enabled at time
|
|
DisableSMSOTP() database.Change
|
|
}
|
|
|
|
type humanTOTPChanges interface {
|
|
// SetTOTP changes the TOTP secret based on the verification
|
|
// * [VerificationTypeInit] to initialize totp verification, previously verified totp remains verified
|
|
// * [VerificationTypeVerified] to mark totp as verified, a verification must exist
|
|
// * [VerificationTypeUpdate] to update totp verification, a verification must exist (e.g. resend code)
|
|
// * [VerificationTypeSkipped] to skip totp verification, existing verification is removed (e.g. admin set totp)
|
|
SetTOTP(verification VerificationType) database.Change
|
|
// SetTOTPCheck sets the TOTP check based on the check type
|
|
// * [CheckTypeFailed] increments failed attempts
|
|
// * [CheckTypeSucceeded] to mark the check as succeeded, removes the check and updates verified at time
|
|
// * [CheckTypeInit] is not allowed for TOTP
|
|
CheckTOTP(check CheckType) database.Change
|
|
// RemoveTOTP removes the TOTP
|
|
RemoveTOTP() database.Change
|
|
}
|
|
|
|
type identityProviderLinkChanges interface {
|
|
// AddIdentityProviderLink adds or updates an identity provider link
|
|
AddIdentityProviderLink(link *IdentityProviderLink) database.Change
|
|
UpdateIdentityProviderLink(changes ...database.Change) database.Change
|
|
// RemoveIdentityProviderLink removes an identity provider link based on the condition
|
|
RemoveIdentityProviderLink(providerID, providedUserID string) database.Change
|
|
|
|
// SetIdentityProviderLinkUsername sets the username for an identity provider link
|
|
SetIdentityProviderLinkUsername(providerID, providedUserID, username string) database.Change
|
|
// SetIdentityProviderLinkProvidedID sets the provided user ID for an identity provider link
|
|
SetIdentityProviderLinkProvidedID(providerID, currentProvidedUserID, newProvidedUserID string) database.Change
|
|
}
|
|
|
|
type humanColumns interface {
|
|
userColumns
|
|
FirstNameColumn() database.Column
|
|
LastNameColumn() database.Column
|
|
NicknameColumn() database.Column
|
|
DisplayNameColumn() database.Column
|
|
EmailColumn() database.Column
|
|
}
|
|
|
|
//go:generate mockgen -typed -package domainmock -destination ./mock/machine_user.mock.go . MachineUserRepository
|
|
|
|
type MachineUserRepository interface {
|
|
// Update updates the machine user
|
|
// It ensures that updates are only applied to machine users
|
|
Update(ctx context.Context, client database.QueryExecutor, condition database.Condition, changes ...database.Change) (int64, error)
|
|
machineConditions
|
|
machineChanges
|
|
machineColumns
|
|
}
|
|
|
|
type machineConditions interface {
|
|
userConditions
|
|
}
|
|
|
|
type machineChanges interface {
|
|
userChanges
|
|
// SetName sets the name field
|
|
SetName(name string) database.Change
|
|
// SetDescription sets the description field
|
|
SetDescription(description string) database.Change
|
|
// SetSecret sets the secret field
|
|
// nil will set the secret to NULL in the database
|
|
SetSecret(secret *string) database.Change
|
|
// SetAccessTokenType sets the personal access token type field
|
|
SetAccessTokenType(tokenType PersonalAccessTokenType) database.Change
|
|
|
|
// AddKey adds a key for the machine user
|
|
AddKey(key *MachineKey) database.Change
|
|
// RemoveKey removes a key for the machine user
|
|
RemoveKey(id string) database.Change
|
|
|
|
// AddPersonalAccessToken adds a personal access token for the machine user
|
|
AddPersonalAccessToken(pat *PersonalAccessToken) database.Change
|
|
// RemovePersonalAccessToken removes a personal access token for the machine user
|
|
RemovePersonalAccessToken(id string) database.Change
|
|
}
|
|
|
|
type machineColumns interface {
|
|
userColumns
|
|
}
|
|
|
|
type humanPasskeyConditions interface {
|
|
PasskeyIDCondition(passkeyID string) database.Condition
|
|
PasskeyKeyIDCondition(keyID string) database.Condition
|
|
PasskeyChallengeCondition(challenge string) database.Condition
|
|
}
|
|
|
|
type humanPasskeyChanges interface {
|
|
// AddPasskey adds a passkey for the user
|
|
AddPasskey(passkey *Passkey) database.Change
|
|
// UpdatePasskey updates a passkey for the user.
|
|
// The condition must specify which passkey to update, use [humanPasskeyConditions].
|
|
// Use [humanPasskeyFieldChanges] to specify the fields to update.
|
|
UpdatePasskey(condition database.Condition, changes ...database.Change) database.Change
|
|
// RemovePasskey removes a passkey for the user
|
|
// The condition must specify which passkey to update, use [humanPasskeyConditions].
|
|
RemovePasskey(condition database.Condition) database.Change
|
|
|
|
passkeyFieldChanges
|
|
}
|
|
|
|
type passkeyFieldChanges interface {
|
|
// SetPasskeyKeyID sets the key ID field
|
|
SetPasskeyKeyID(keyID []byte) database.Change
|
|
// SetPasskeyPublicKey sets the public key field
|
|
SetPasskeyPublicKey(publicKey []byte) database.Change
|
|
// SetPasskeyAttestationType sets the attestation type field
|
|
SetPasskeyAttestationType(attestationType string) database.Change
|
|
// SetPasskeyAuthenticatorAttestationGUID sets the AAGUID field
|
|
SetPasskeyAuthenticatorAttestationGUID(aaguid []byte) database.Change
|
|
// SetPasskeyName sets the name field
|
|
SetPasskeyName(name string) database.Change
|
|
// SetPasskeySignCount sets the sign count field
|
|
SetPasskeySignCount(signCount uint32) database.Change
|
|
// SetPasskeyUpdatedAt sets the updated at field
|
|
SetPasskeyUpdatedAt(updatedAt time.Time) database.Change
|
|
// SetPasskeyVerifiedAt sets the verified at field
|
|
// If verifiedAt is zero, it will be set to NOW()
|
|
SetPasskeyVerifiedAt(verifiedAt time.Time) database.Change
|
|
}
|