user

package
v0.0.0-...-1db7b98 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2023 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package user specifies:

  • user-specific domain types;
  • a service interface and corresponding implementation, for injection into inbound methods;
  • an outbound interface describing a store of user data.

nolint:gosec

Index

Constants

View Source
const (
	// UsernameMinLen is the minimum length of a username in bytes.
	UsernameMinLen = 3

	// UsernameMaxLen is the maximum length of a username in bytes.
	UsernameMaxLen = 16

	UsernamePatternTemplate = "^[a-zA-Z0-9_]{%d,%d}$"
)
View Source
const (
	PasswordMinLen = 8
	PasswordMaxLen = 72
)

Variables

This section is empty.

Functions

func NewDuplicateEmailError

func NewDuplicateEmailError(email EmailAddress) error

func NewDuplicateUsernameError

func NewDuplicateUsernameError(username Username) error

func NewEmailAddressFormatError

func NewEmailAddressFormatError(candidate string) error

func NewInvalidURLError

func NewInvalidURLError() error

func NewNotFoundByEmailError

func NewNotFoundByEmailError(email EmailAddress) error

func NewNotFoundByIDError

func NewNotFoundByIDError(id uuid.UUID) error

func NewPasswordTooLongError

func NewPasswordTooLongError() error

func NewPasswordTooShortError

func NewPasswordTooShortError() error

func NewUsernameFormatError

func NewUsernameFormatError() error

func NewUsernameTooLongError

func NewUsernameTooLongError() error

func NewUsernameTooShortError

func NewUsernameTooShortError() error

func RandomEmailAddressCandidate

func RandomEmailAddressCandidate() string

func RandomOption

func RandomOption[T any](t *testing.T) option.Option[T]

func RandomOptionFromInstance

func RandomOptionFromInstance[T any](instance T) option.Option[T]

func RandomPasswordCandidate

func RandomPasswordCandidate() string

func RandomURLCandidate

func RandomURLCandidate() string

func RandomUsernameCandidate

func RandomUsernameCandidate() string

Types

type AuthError

type AuthError struct {
	Cause error
}

AuthError is a wrapper for an authentication error result, which may include errors that would be considered validation errors by other endpoints. This reinforces the security convention that an end user should not receive the specifics of why an authentication request failed.

func (*AuthError) Error

func (e *AuthError) Error() string

func (*AuthError) Unwrap

func (e *AuthError) Unwrap() error

type AuthRequest

type AuthRequest struct {
	// contains filtered or unexported fields
}

AuthRequest describes the data required to authenticate a user.

func NewAuthRequest

func NewAuthRequest(email EmailAddress, passwordCandidate string) *AuthRequest

func ParseAuthRequest

func ParseAuthRequest(emailCandidate string, passwordCandidate string) (*AuthRequest, error)

ParseAuthRequest returns a new AuthRequest from raw inputs.

Errors

func RandomAuthRequest

func RandomAuthRequest(t *testing.T) *AuthRequest

func (*AuthRequest) Email

func (ar *AuthRequest) Email() EmailAddress

func (AuthRequest) GoString

func (ar AuthRequest) GoString() string

GoString ensures that `passwordCandidate` is obfuscated when the request is printed with the %#v verb. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*AuthRequest) PasswordCandidate

func (ar *AuthRequest) PasswordCandidate() string

func (AuthRequest) String

func (ar AuthRequest) String() string

GoString ensures that `passwordCandidate` is obfuscated when the request is printed with the %s or %v verbs. Unexported fields are otherwise printed reflectively, which would expose the hash.

type Bio

type Bio string

Bio represents a user's bio.

func ParseBio

func ParseBio(raw string) (Bio, error)

ParseBio is a convenience function for use with option.Map.

func RandomBio

func RandomBio() Bio

type ConcurrentModificationError

type ConcurrentModificationError struct {
	ID   uuid.UUID
	ETag etag.ETag
}

ConcurrentModificationError indicates that a resource was modified prior to processing the current request, making the current request stale.

func (*ConcurrentModificationError) Error

type EmailAddress

type EmailAddress struct {
	// contains filtered or unexported fields
}

EmailAddress is a dedicated usernameCandidate type for valid email addresses. New instances are validated for RFC5332 compliance.

func ParseEmailAddress

func ParseEmailAddress(candidate string) (EmailAddress, error)

ParseEmailAddress returns a new email address from `candidate`, validating that the email address conforms to RFC5332 standards (with the minor divergences introduce by the Go standard library, documented in net/mail).

The formats that constitute a valid email address may surprise you. For example, single-value domain names like `angus@com` are valid.

func RandomEmailAddress

func RandomEmailAddress(t *testing.T) EmailAddress

func (EmailAddress) String

func (ea EmailAddress) String() string

String returns the raw email address.

type FieldType

type FieldType int

FieldType identifies the type of field implicated in an error.

const (
	UUIDFieldType FieldType = iota + 1
	UsernameFieldType
	EmailFieldType
	PasswordFieldType
	URLFieldType
)

func (FieldType) String

func (f FieldType) String() string

type NotFoundError

type NotFoundError struct {
	IDType  FieldType
	IDValue string
}

NotFoundError should be returned by a Repository when the specified user does not exist.

func (*NotFoundError) Error

func (e *NotFoundError) Error() string

func (*NotFoundError) Is

func (e *NotFoundError) Is(target error) bool

type PasswordHash

type PasswordHash struct {
	// contains filtered or unexported fields
}

PasswordHash represents a validated and hashed password.

The hash is obfuscated when printed with the %s, %v and %#v verbs.

CAUTION: The fmt package uses reflection to print unexported fields without invoking their String or GoString methods. Printing structs containing unexported PasswordHashes will result in the hash bytes being exposed.

func NewPasswordHashFromTrustedSource

func NewPasswordHashFromTrustedSource(raw []byte) PasswordHash

NewPasswordHashFromTrustedSource wraps a hashed password in a PasswordHash.

func ParsePassword

func ParsePassword(candidate string) (PasswordHash, error)

func RandomPasswordHash

func RandomPasswordHash(t *testing.T) PasswordHash

func (PasswordHash) Bytes

func (ph PasswordHash) Bytes() []byte

func (PasswordHash) GoString

func (ph PasswordHash) GoString() string

GoString obfuscates the hash bytes when the hash is printed with the %#v verb.

func (PasswordHash) String

func (ph PasswordHash) String() string

String obfuscates the hash bytes when the hash is printed with the %s and %v verbs.

type RegistrationRequest

type RegistrationRequest struct {
	// contains filtered or unexported fields
}

RegistrationRequest carries validated data required to register a new user.

func NewRegistrationRequest

func NewRegistrationRequest(
	username Username, email EmailAddress, passwordHash PasswordHash,
) *RegistrationRequest

func ParseRegistrationRequest

func ParseRegistrationRequest(
	usernameCandidate string,
	emailCandidate string,
	passwordCandidate string,
) (*RegistrationRequest, error)

ParseRegistrationRequest returns a new RegistrationRequest from raw inputs.

Errors

  • ValidationErrors, if one or more inputs are invalid.
  • Unexpected internal response.

func RandomRegistrationRequest

func RandomRegistrationRequest(t *testing.T) *RegistrationRequest

func (*RegistrationRequest) Email

func (r *RegistrationRequest) Email() EmailAddress

func (*RegistrationRequest) Equal

func (r *RegistrationRequest) Equal(other *RegistrationRequest, password string) bool

Equal returns true if `r.passwordHash` can be obtained from `password`, and the two requests are equal in all other fields.

Direct comparison of password hashes is impossible by design.

func (RegistrationRequest) GoString

func (r RegistrationRequest) GoString() string

GoString ensures that the PasswordHash's GoString method is invoked when the request is printed with the %#v verb. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*RegistrationRequest) PasswordHash

func (r *RegistrationRequest) PasswordHash() PasswordHash

func (RegistrationRequest) String

func (r RegistrationRequest) String() string

String ensures that the PasswordHash's String method is invoked when the request is printed with the %s or %v verbs. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*RegistrationRequest) Username

func (r *RegistrationRequest) Username() Username

type Repository

type Repository interface {
	// GetUserByID retrieves the [User] with `id`.
	//
	// # Errors
	// 	- [NotFoundError] if no such User exists.
	GetUserByID(ctx context.Context, id uuid.UUID) (*User, error)

	// GetUserByEmail returns a user by email.
	//
	// # Errors
	// 	- [NotFoundError] if no such User exists.
	GetUserByEmail(ctx context.Context, email EmailAddress) (*User, error)

	// CreateUser persists a new user.
	//
	// # Errors
	// 	- [ValidationError] if email or username is already taken.
	CreateUser(ctx context.Context, req *RegistrationRequest) (*User, error)

	// UpdateUser updates an existing user.
	//
	// # Errors
	// 	- [NotFoundError] if no such User exists.
	// 	- [ValidationError] if email is already taken.
	//  - [ConcurrentModificationError] if the user has been modified since the last
	//    read.
	UpdateUser(ctx context.Context, req *UpdateRequest) (*User, error)
}

Repository is a store of user data.

type Service

type Service interface {
	// Register a new user.
	//
	// # Errors
	// 	- [ValidationError] if email or username is already taken.
	Register(ctx context.Context, req *RegistrationRequest) (*User, error)

	// Authenticate a user, returning the authenticated [User] if successful.
	//
	// # Errors
	//	- [AuthError].
	Authenticate(ctx context.Context, req *AuthRequest) (*User, error)

	// GetUser a user by ID.
	//
	// # Errors
	// 	- [NotFoundError] if no such User exists.
	GetUser(ctx context.Context, id uuid.UUID) (*User, error)

	// UpdateUser updates an existing user.
	//
	// # Errors
	// 	- [NotFoundError] if no such User exists.
	// 	- [ValidationError] if email is already taken.
	//  - [ConcurrentModificationError] if the user has been modified since the last
	//    read.
	UpdateUser(ctx context.Context, req *UpdateRequest) (*User, error)
}

Service describes the business API accessible to inbound methods.

func NewService

func NewService(repo Repository) Service

type URL

type URL struct {
	// contains filtered or unexported fields
}

URL represents a valid, immutable URL.

func ParseURL

func ParseURL(candidate string) (URL, error)

ParseURL returns a valid URL if successful, and an error otherwise.

func RandomURL

func RandomURL(t *testing.T) URL

func (URL) Equal

func (u URL) Equal(other URL) bool

func (URL) String

func (u URL) String() string

type UpdateRequest

type UpdateRequest struct {
	// contains filtered or unexported fields
}

UpdateRequest describes the data required to update a user. All fields but userID are optional. To unset a FieldType, pass [Option] containing the zero value of the FieldType's type.

func NewUpdateRequest

func NewUpdateRequest(
	userID uuid.UUID,
	eTag etag.ETag,
	email option.Option[EmailAddress],
	passwordHash option.Option[PasswordHash],
	bio option.Option[Bio],
	imageURL option.Option[URL],
) *UpdateRequest

func ParseUpdateRequest

func ParseUpdateRequest(
	userID uuid.UUID,
	eTag etag.ETag,
	emailCandidate option.Option[string],
	passwordCandidate option.Option[string],
	rawBio option.Option[string],
	imageURLCandidate option.Option[string],
) (*UpdateRequest, error)

ParseUpdateRequest returns a new UpdateRequest from raw inputs.

Errors

  • ValidationErrors, if one or more inputs are invalid.
  • Unexpected internal response.

func RandomUpdateRequest

func RandomUpdateRequest(t *testing.T) *UpdateRequest

func (*UpdateRequest) Bio

func (ur *UpdateRequest) Bio() option.Option[Bio]

func (*UpdateRequest) ETag

func (ur *UpdateRequest) ETag() etag.ETag

func (*UpdateRequest) Email

func (ur *UpdateRequest) Email() option.Option[EmailAddress]

func (*UpdateRequest) Equal

func (ur *UpdateRequest) Equal(other *UpdateRequest, password option.Option[string]) bool

Equal returns true if `ur.passwordHash` can be obtained from `password`, and the two requests are equal in all other fields.

Direct comparison of password hashes is impossible by design.

func (UpdateRequest) GoString

func (ur UpdateRequest) GoString() string

GoString ensures that the PasswordHash option.Option's GoString method is invoked when the request is printed with the %#v verb, which in turn calls GoString on the PasswordHash, if present. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*UpdateRequest) ImageURL

func (ur *UpdateRequest) ImageURL() option.Option[URL]

func (*UpdateRequest) PasswordHash

func (ur *UpdateRequest) PasswordHash() option.Option[PasswordHash]

func (UpdateRequest) String

func (ur UpdateRequest) String() string

GoString ensures that the PasswordHash option.Option's GoString method is invoked when the request is printed with the %s or %v verbs, which in turn calls GoString on the PasswordHash, if present. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*UpdateRequest) UserID

func (ur *UpdateRequest) UserID() uuid.UUID

type User

type User struct {
	// contains filtered or unexported fields
}

User is the central domain type for this package.

func NewUser

func NewUser(
	id uuid.UUID,
	eTag etag.ETag,
	username Username,
	email EmailAddress,
	passwordHash PasswordHash,
	bio option.Option[Bio],
	imageURL option.Option[URL],
) *User

func RandomUser

func RandomUser(t *testing.T) *User

func (*User) Bio

func (u *User) Bio() option.Option[Bio]

func (*User) ETag

func (u *User) ETag() etag.ETag

func (*User) Email

func (u *User) Email() EmailAddress

func (User) GoString

func (u User) GoString() string

GoString ensures that the PasswordHash's GoString method is invoked when the User is printed with the %#v verb. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*User) ID

func (u *User) ID() uuid.UUID

func (*User) ImageURL

func (u *User) ImageURL() option.Option[URL]

func (*User) PasswordHash

func (u *User) PasswordHash() PasswordHash

func (User) String

func (u User) String() string

GoString ensures that the PasswordHash's GoString method is invoked when the User is printed with the %s or %v verbs. Unexported fields are otherwise printed reflectively, which would expose the hash.

func (*User) Username

func (u *User) Username() Username

type Username

type Username struct {
	// contains filtered or unexported fields
}

Username represents a valid Username.

func ParseUsername

func ParseUsername(candidate string) (Username, error)

ParseUsername returns either a valid Username or an error indicating why the raw username was invalid.

func RandomUsername

func RandomUsername(t *testing.T) Username

func (Username) String

func (u Username) String() string

type ValidationError

type ValidationError struct {
	Field   FieldType
	Message string
}

ValidationError represents a single error encountered when validation a field of the given FieldType.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Is

func (e *ValidationError) Is(target error) bool

type ValidationErrors

type ValidationErrors []*ValidationError

ValidationErrors is a collection of ValidationError, allowing us to report on the validation of several inputs rather than returning the first validation error encountered.

func (ValidationErrors) Any

func (e ValidationErrors) Any() bool

Any returns true if the collection contains any response, and false otherwise.

func (ValidationErrors) Error

func (e ValidationErrors) Error() string

func (*ValidationErrors) PushValidationError

func (e *ValidationErrors) PushValidationError(err error) error

PushValidationError adds a ValidationError to the collection. If the error is not a ValidationError (including nil response), it is returned as-is. The supports the following pattern for successive validations:

	var validationErrs ValidationErrors
	field1, err := parseField1()
	if err := validationErrs.PushValidationError(err); err != nil {
     	return nil, err // not a ValidationError
	}
 	field2, err := parseField2()
 	...

Jump to

Keyboard shortcuts

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