Documentation
¶
Index ¶
- Variables
- type AuthIdentity
- type EntityType
- type Session
- type SessionRefreshToken
- type User
- func (u *User) ChangeEmail(email valueobjects.Email, at time.Time) error
- func (u *User) ChangePhone(phone *string, at time.Time) error
- func (u *User) ChangeStatus(status enums.UserStatus, at time.Time) error
- func (u User) ProfileComplete() bool
- func (u *User) Rename(name string) error
- func (u User) Validate() error
- func (u *User) VerifyEmail(email valueobjects.Email, at time.Time) error
- func (u *User) VerifyPhone(phone string, at time.Time) error
- type VerificationChallenge
Constants ¶
This section is empty.
Variables ¶
var ( ErrInvalidUserStatus = apperror.Validation("Unsupported account status.") ErrUserTransition = apperror.Conflict("The account cannot make this status transition.") ErrUserInactive = apperror.Forbidden("The account is not active.") ErrInvalidPhone = apperror.InvalidFields(map[string][]string{"phone": {"Provide a phone number in international E.164 format."}}) ErrInvalidContactState = apperror.Internal("identity: inconsistent contact verification state") ErrContactChanged = apperror.Conflict("The contact has changed. Start verification again.") ErrInvalidUserName = apperror.InvalidFields(map[string][]string{"name": {"Name must contain between 1 and 100 characters."}}) ErrInvalidEmail = apperror.InvalidFields(map[string][]string{"email": {"Provide a valid email address of at most 254 characters."}}) )
Identity entity failures have stable identity for errors.Is, including through wrapping. Shared entities cannot import the Identity module's domain package. Invalid server-managed IDs, credentials, and lifecycle state remain internal failures; public validation failures describe unsupported caller choices.
var ( ErrInvalidAuthIdentityIDs = apperror.Internal("identity: identity and user IDs are required") ErrUnsupportedAuthProvider = apperror.Validation("identity: unsupported provider") ErrMissingProviderSubject = apperror.Internal("identity: provider subject is required") ErrInvalidAuthIdentityTimestamps = apperror.Internal("identity: invalid identity timestamps") ErrInvalidChallengeIDs = apperror.Internal("identity: invalid challenge IDs") ErrChallengeAccountRequired = apperror.Internal("identity: challenge requires an account") ErrUnsupportedVerificationPurpose = apperror.Validation("identity: unsupported verification purpose") ErrUnsupportedVerificationChannel = apperror.Validation("identity: unsupported verification channel") ErrInvalidChallengeCredential = apperror.Internal("identity: destination and SHA-256 code digest are required") ErrInvalidVerificationAttempts = apperror.Internal("identity: invalid verification attempts") ErrInvalidChallengeLifetime = apperror.Internal("identity: invalid challenge lifetime") ErrInvalidChallengeConsumption = apperror.Internal("identity: invalid challenge consumption time") ErrInvalidChallengeInvalidation = apperror.Internal("identity: invalid challenge invalidation time") ErrConflictingChallengeState = apperror.Internal("identity: challenge cannot be both consumed and invalidated") ErrInvalidSessionIDs = apperror.Internal("identity: session and user IDs are required") ErrUnsupportedSessionPlatform = apperror.Validation("identity: unsupported session platform") ErrInvalidSessionLifetime = apperror.Internal("identity: invalid session lifetime") ErrInvalidSessionRevocation = apperror.Internal("identity: invalid session revocation time") ErrInvalidRefreshTokenCredential = apperror.Internal("identity: refresh token IDs and SHA-256 hash are required") ErrInvalidRefreshTokenLifetime = apperror.Internal("identity: invalid refresh token lifetime") ErrInvalidRefreshTokenConsumption = apperror.Internal("identity: invalid refresh token consumption time") ErrInvalidUserState = apperror.Internal("identity: invalid account identity or timestamps") )
Functions ¶
This section is empty.
Types ¶
type AuthIdentity ¶
type AuthIdentity struct {
bun.BaseModel `bun:"table:auth_identities,alias:ai"`
ID uuid.UUID `bun:"id,pk,type:uuid"`
UserID uuid.UUID `bun:"user_id,type:uuid,notnull"`
Provider enums.AuthProvider `bun:"provider,notnull"`
ProviderSubject string `bun:"provider_subject,notnull" json:"-"`
CreatedAt time.Time `bun:"created_at,notnull"`
LastAuthenticatedAt *time.Time `bun:"last_authenticated_at"`
}
AuthIdentity links a provider subject to an account. Identity owns writes. Provider subjects are opaque and must never be normalized as email addresses.
func (AuthIdentity) Validate ¶
func (i AuthIdentity) Validate() error
Validate checks stored values; it does not verify provider credentials or authorize linking.
type EntityType ¶
type EntityType string
EntityType is a stable model discriminator for polymorphic ownership and routing. Persisted values must remain stable even when Go packages or structs are renamed.
const ( // UserEntityType identifies Identity users across media and notifications. UserEntityType EntityType = "user" )
type Session ¶
type Session struct {
bun.BaseModel `bun:"table:sessions,alias:s"`
ID uuid.UUID `bun:"id,pk,type:uuid"`
UserID uuid.UUID `bun:"user_id,type:uuid,notnull"`
Platform enums.SessionPlatform `bun:"platform,notnull"`
DeviceName *string `bun:"device_name"`
CreatedAt time.Time `bun:"created_at,notnull"`
LastUsedAt time.Time `bun:"last_used_at,notnull"`
ExpiresAt *time.Time `bun:"expires_at"` // Nil means no absolute session deadline.
RevokedAt *time.Time `bun:"revoked_at"`
}
Session is an authenticated login owned by Identity, independent of device identity. Refresh credentials live exclusively in SessionRefreshToken; this ID is their family.
type SessionRefreshToken ¶
type SessionRefreshToken struct {
bun.BaseModel `bun:"table:session_refresh_tokens,alias:rt"`
ID uuid.UUID `bun:"id,pk,type:uuid"`
SessionID uuid.UUID `bun:"session_id,type:uuid,notnull"`
TokenHash []byte `bun:"token_hash,notnull" json:"-"`
CreatedAt time.Time `bun:"created_at,notnull"`
ExpiresAt *time.Time `bun:"expires_at"` // Nil means valid until consumed or the session ends.
ConsumedAt *time.Time `bun:"consumed_at"`
}
SessionRefreshToken is owned by Identity. TokenHash stores a SHA-256 digest of a cryptographically random high-entropy credential, never a password or OTP. Consumed rows are retained to detect reuse; rotation must lock the owning session.
func (SessionRefreshToken) Validate ¶
func (t SessionRefreshToken) Validate() error
Validate checks the record; the session service must also enforce the parent session's absolute expiry and revocation state in the rotation transaction.
type User ¶
type User struct {
bun.BaseModel `bun:"table:users,alias:u"`
domain.Entity `bun:",embed"`
Email valueobjects.Email `json:"email" bun:"email,type:text,notnull"`
EmailVerifiedAt *time.Time `json:"email_verified_at" bun:"email_verified_at"`
Phone *string `json:"phone" bun:"phone"`
PhoneVerifiedAt *time.Time `json:"phone_verified_at" bun:"phone_verified_at"`
Status enums.UserStatus `json:"status" bun:"status,notnull"`
LastActiveAt *time.Time `json:"last_active_at" bun:"last_active_at"`
DeletedAt *time.Time `json:"deleted_at" bun:"deleted_at"`
Name string `json:"name" bun:"name,notnull"`
}
User is owned by Identity. It is both the domain entity and Bun model for users; persistence and domain shape intentionally evolve together. HTTP uses an explicit response DTO.
func NewUser ¶
func NewUser( name string, email valueobjects.Email, ) (*User, error)
NewUser permits an empty initial name for passwordless onboarding. Rename requires a nonempty name when the user completes their profile.
func (*User) ChangeEmail ¶
ChangeEmail clears proof for the old email. It does not itself authorize a change.
func (*User) ChangePhone ¶
ChangePhone accepts an already canonical international number, or nil to remove it. It copies the input and resets verification only when the number changes.
func (*User) ChangeStatus ¶
ChangeStatus applies the lifecycle transition. Authorization and session revocation belong to the service. Deleted is terminal; contacts remain reserved after deletion.
func (User) ProfileComplete ¶
ProfileComplete reports whether the required profile fields have been supplied. New passwordless accounts may have no name until the user updates their profile.
func (User) Validate ¶
Validate checks persisted account values before writes. It does not establish contact ownership. Verification is recorded only after proof by the auth service.
func (*User) VerifyEmail ¶
VerifyEmail must only follow successful challenge verification. Matching the destination prevents an old challenge from verifying a replacement contact.
type VerificationChallenge ¶
type VerificationChallenge struct {
bun.BaseModel `bun:"table:verification_challenges,alias:vc"`
ID uuid.UUID `bun:"id,pk,type:uuid"`
UserID *uuid.UUID `bun:"user_id,type:uuid"`
Purpose enums.VerificationPurpose `bun:"purpose,notnull"`
Channel enums.VerificationChannel `bun:"channel,notnull"`
Destination string `bun:"destination,notnull" json:"-"`
CodeDigest []byte `bun:"code_digest,notnull" json:"-"`
AttemptCount int `bun:"attempt_count,notnull"`
MaxAttempts int `bun:"max_attempts,notnull"`
ExpiresAt time.Time `bun:"expires_at,notnull"`
ConsumedAt *time.Time `bun:"consumed_at"`
InvalidatedAt *time.Time `bun:"invalidated_at"`
CreatedAt time.Time `bun:"created_at,notnull"`
}
VerificationChallenge is owned by Identity. Destination is private contact data. CodeDigest holds an HMAC-SHA256 digest, never the original code. The verification service must bind it to this challenge and enforce consumption under a transaction.
func (VerificationChallenge) Validate ¶
func (c VerificationChallenge) Validate() error
Validate checks structural storage invariants. Destination normalization, abuse limits, authorization, and atomic verification belong to the operation boundary.