Help us shape the future of Go! Take the 2026 Go Developer Survey

entities

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

func (Session) Validate

func (s Session) Validate() error

Validate checks stored state. Authorization must also check account/session status and expiry at request time; this method does not establish an authenticated session.

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

func (u *User) ChangeEmail(email valueobjects.Email, at time.Time) error

ChangeEmail clears proof for the old email. It does not itself authorize a change.

func (*User) ChangePhone

func (u *User) ChangePhone(phone *string, at time.Time) error

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

func (u *User) ChangeStatus(status enums.UserStatus, at time.Time) error

ChangeStatus applies the lifecycle transition. Authorization and session revocation belong to the service. Deleted is terminal; contacts remain reserved after deletion.

func (User) ProfileComplete

func (u User) ProfileComplete() bool

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) Rename

func (u *User) Rename(name string) error

func (User) Validate

func (u User) Validate() error

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

func (u *User) VerifyEmail(email valueobjects.Email, at time.Time) error

VerifyEmail must only follow successful challenge verification. Matching the destination prevents an old challenge from verifying a replacement contact.

func (*User) VerifyPhone

func (u *User) VerifyPhone(phone string, at time.Time) error

VerifyPhone must only follow proof of control of the current number.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL