package ports import ( "context" "errors" "fmt" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/userresolution" ) // UserDirectory provides the auth/session boundary to user ownership, // registration, and block-policy decisions. type UserDirectory interface { // ResolveByEmail returns the current resolution state for email without // creating any new user record. ResolveByEmail(ctx context.Context, email common.Email) (userresolution.Result, error) // ExistsByUserID reports whether userID currently identifies a stored user // record. ExistsByUserID(ctx context.Context, userID common.UserID) (bool, error) // EnsureUserByEmail returns an existing user for email, creates a new user // when registration is allowed, or reports a blocked outcome when the // address may not continue through confirm flow. EnsureUserByEmail(ctx context.Context, email common.Email) (EnsureUserResult, error) // BlockByUserID applies a block state to the user identified by // input.UserID. Implementations must wrap ErrNotFound when input.UserID does // not exist. BlockByUserID(ctx context.Context, input BlockUserByIDInput) (BlockUserResult, error) // BlockByEmail applies a block state to input.Email, even when no user // record currently exists for that e-mail address. BlockByEmail(ctx context.Context, input BlockUserByEmailInput) (BlockUserResult, error) } // EnsureUserOutcome identifies the coarse outcome of ensuring a user record // for one normalized e-mail address. type EnsureUserOutcome string const ( // EnsureUserOutcomeExisting reports that the e-mail already belonged to a // stored user. EnsureUserOutcomeExisting EnsureUserOutcome = "existing" // EnsureUserOutcomeCreated reports that a new user was created for the // e-mail address. EnsureUserOutcomeCreated EnsureUserOutcome = "created" // EnsureUserOutcomeBlocked reports that the e-mail cannot be used for login // or registration. EnsureUserOutcomeBlocked EnsureUserOutcome = "blocked" ) // IsKnown reports whether EnsureUserOutcome is supported by the current // user-directory contract. func (o EnsureUserOutcome) IsKnown() bool { switch o { case EnsureUserOutcomeExisting, EnsureUserOutcomeCreated, EnsureUserOutcomeBlocked: return true default: return false } } // EnsureUserResult describes the stable outcome returned by UserDirectory // after one ensure-user attempt. type EnsureUserResult struct { // Outcome reports whether the user already existed, was created, or is // blocked by policy. Outcome EnsureUserOutcome // UserID is present when Outcome is EnsureUserOutcomeExisting or // EnsureUserOutcomeCreated. UserID common.UserID // BlockReasonCode is present only when Outcome is EnsureUserOutcomeBlocked. BlockReasonCode userresolution.BlockReasonCode } // Validate reports whether EnsureUserResult satisfies the user-directory // contract invariants. func (r EnsureUserResult) Validate() error { if !r.Outcome.IsKnown() { return fmt.Errorf("ensure user result outcome %q is unsupported", r.Outcome) } switch r.Outcome { case EnsureUserOutcomeExisting, EnsureUserOutcomeCreated: if err := r.UserID.Validate(); err != nil { return fmt.Errorf("ensure user result user id: %w", err) } if !r.BlockReasonCode.IsZero() { return errors.New("ensure user result must not contain block reason code for existing or created outcomes") } case EnsureUserOutcomeBlocked: if !r.UserID.IsZero() { return errors.New("ensure user result must not contain user id for blocked outcome") } if err := r.BlockReasonCode.Validate(); err != nil { return fmt.Errorf("ensure user result block reason code: %w", err) } } return nil } // BlockUserByIDInput describes one block mutation targeted by stable user id. type BlockUserByIDInput struct { // UserID identifies the user that should be blocked. UserID common.UserID // ReasonCode stores the machine-readable block reason to apply. ReasonCode userresolution.BlockReasonCode } // Validate reports whether BlockUserByIDInput contains a complete block // request. func (i BlockUserByIDInput) Validate() error { if err := i.UserID.Validate(); err != nil { return fmt.Errorf("block user by id input user id: %w", err) } if err := i.ReasonCode.Validate(); err != nil { return fmt.Errorf("block user by id input reason code: %w", err) } return nil } // BlockUserByEmailInput describes one block mutation targeted by normalized // e-mail address. type BlockUserByEmailInput struct { // Email identifies the e-mail address that should be blocked. Email common.Email // ReasonCode stores the machine-readable block reason to apply. ReasonCode userresolution.BlockReasonCode } // Validate reports whether BlockUserByEmailInput contains a complete block // request. func (i BlockUserByEmailInput) Validate() error { if err := i.Email.Validate(); err != nil { return fmt.Errorf("block user by email input email: %w", err) } if err := i.ReasonCode.Validate(); err != nil { return fmt.Errorf("block user by email input reason code: %w", err) } return nil } // BlockUserOutcome identifies the coarse outcome of blocking one user or // e-mail subject. type BlockUserOutcome string const ( // BlockUserOutcomeBlocked reports that the current mutation applied a new // block state. BlockUserOutcomeBlocked BlockUserOutcome = "blocked" // BlockUserOutcomeAlreadyBlocked reports that the target subject had already // been blocked before the current mutation. BlockUserOutcomeAlreadyBlocked BlockUserOutcome = "already_blocked" ) // IsKnown reports whether BlockUserOutcome is supported by the current // user-directory contract. func (o BlockUserOutcome) IsKnown() bool { switch o { case BlockUserOutcomeBlocked, BlockUserOutcomeAlreadyBlocked: return true default: return false } } // BlockUserResult describes the stable outcome returned by UserDirectory after // one block attempt. type BlockUserResult struct { // Outcome reports whether the current mutation applied a new block state. Outcome BlockUserOutcome // UserID optionally stores the stable user identifier resolved for the // blocked subject when one exists. UserID common.UserID } // Validate reports whether BlockUserResult satisfies the user-directory // contract invariants. func (r BlockUserResult) Validate() error { if !r.Outcome.IsKnown() { return fmt.Errorf("block user result outcome %q is unsupported", r.Outcome) } if !r.UserID.IsZero() { if err := r.UserID.Validate(); err != nil { return fmt.Errorf("block user result user id: %w", err) } } return nil }