Documentation
¶
Overview ¶
Package user is the mandatory accounts core: user entities, their CRUD surface, and persistence. Every other feature hangs off user accounts; the composition root always passes it first to identity.New.
Index ¶
- Variables
- func ProtoView(u User) *identityv1.User
- type AdminUpdateParams
- type CreateParams
- type CredentialAdminPort
- type CredentialView
- type DefaultPictureFunc
- type GroupBinding
- type GroupBindingPort
- type ImageStore
- type ListParams
- type Page
- type PostgresStore
- func (s *PostgresStore) Create(ctx context.Context, params CreateParams) (User, error)
- func (s *PostgresStore) Delete(ctx context.Context, id UserID) error
- func (s *PostgresStore) GetByID(ctx context.Context, id UserID) (User, error)
- func (s *PostgresStore) HasAnyUser(ctx context.Context) (bool, error)
- func (s *PostgresStore) List(ctx context.Context, params ListParams) ([]User, int, error)
- func (s *PostgresStore) MarkEmailVerified(ctx context.Context, id UserID) error
- func (s *PostgresStore) MarkLogin(ctx context.Context, id UserID) error
- func (s *PostgresStore) SetProfilePicturePath(ctx context.Context, id UserID, picturePath *string) error
- func (s *PostgresStore) UpdateAdmin(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)
- func (s *PostgresStore) UpdateProfile(ctx context.Context, id UserID, params UpdateProfileParams) (User, error)
- type Service
- func (s *Service) APIRoutes(r chi.Router, _ identity.RouteGroups)
- func (s *Service) BindRPCPorts(groups GroupBindingPort, credentials CredentialAdminPort)
- func (s *Service) Create(ctx context.Context, params CreateParams) (User, error)
- func (s *Service) Delete(ctx context.Context, id UserID) error
- func (s *Service) GetByID(ctx context.Context, id UserID) (User, error)
- func (s *Service) List(ctx context.Context, params ListParams) ([]User, int, error)
- func (s *Service) Name() string
- func (s *Service) RPCService(auth kernel.AccessAuthenticator) (string, http.Handler)
- func (s *Service) Update(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)
- type ServiceOption
- type Store
- type UpdateProfileParams
- type User
- type UserID
- type UserPhoneID
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound is returned when no user matches the ID. ErrNotFound = errors.New("user not found") // ErrDuplicate is returned when username or email already exists. ErrDuplicate = errors.New("username or email already exists") // ErrInvalidUsername rejects usernames outside ^[a-zA-Z0-9_]{3,32}$. ErrInvalidUsername = errors.New("username must be 3-32 characters: letters, digits, underscores") // ErrInvalidEmail rejects emails missing a local part, domain, or TLD. ErrInvalidEmail = errors.New("email is not a valid address") )
Errors surfaced by stores and mapped to HTTP statuses by handlers.
var UsernamePattern = regexp.MustCompile(`^[a-zA-Z0-9_]{3,32}$`)
UsernamePattern mirrors the database CHECK constraint; admin DTOs reuse it for validation.
Functions ¶
Types ¶
type AdminUpdateParams ¶
type AdminUpdateParams struct {
Email *string
FirstName *string
LastName *string
DisplayName *string
IsAdmin *bool
Disabled *bool
}
AdminUpdateParams patches administrative fields. Nil pointers keep the current value.
type CreateParams ¶
type CreateParams struct {
Username string
Email string
FirstName string
LastName string
DisplayName string
IsAdmin bool
}
CreateParams carries the fields a caller supplies; the store fills the rest (ID, timestamps, defaults).
func (CreateParams) Validate ¶
func (p CreateParams) Validate() (displayName string, err error)
Validate normalizes params and enforces the same rules as the database constraints, so invalid writes fail before reaching Postgres. Returns the display name resolved from first/last name or the email local part.
type CredentialAdminPort ¶ added in v0.0.12
type CredentialAdminPort interface {
// CredentialsForUser lists the user's registered credentials.
CredentialsForUser(ctx context.Context, userID UserID) ([]CredentialView, error)
// RenameCredential updates one credential's display name;
// unknown IDs surface ErrNotFound.
RenameCredential(ctx context.Context, userID UserID, credentialID, name string) (*CredentialView, error)
// DeleteCredential removes one credential; unknown IDs surface
// ErrNotFound.
DeleteCredential(ctx context.Context, userID UserID, credentialID string) error
}
CredentialAdminPort serves the WebAuthn credential management surface; implemented by the passkey feature.
type CredentialView ¶ added in v0.0.12
type CredentialView struct {
ID string
Name string
CredentialID string // base64url of the raw credential id
AttestationType string
Transport []string
BackupEligible bool
BackupState bool
CreatedAt string
LastUsedAt *string
}
CredentialView is the neutral WebAuthn credential projection for the admin/self management surface.
type DefaultPictureFunc ¶
type DefaultPictureFunc func(ctx context.Context) (reader io.ReadCloser, size int64, mime string, ok bool)
DefaultPictureFunc serves the bundled default profile picture; ok=false when none is set.
type GroupBinding ¶ added in v0.0.12
type GroupBinding struct {
ID string
Name string
DisplayName string
CreatedAt string
UpdatedAt *string
}
GroupBinding is the neutral user↔group projection: the group fields the binding surface needs, without importing the usergroup package (which imports this one).
type GroupBindingPort ¶ added in v0.0.12
type GroupBindingPort interface {
// GroupsForUser lists the groups the user belongs to.
GroupsForUser(ctx context.Context, userID UserID) ([]GroupBinding, error)
// ReplaceGroupsForUser atomically rebinds the user to exactly
// the named group IDs.
ReplaceGroupsForUser(ctx context.Context, userID UserID, groupIDs []string) error
}
GroupBindingPort serves the user↔group bindings; implemented by the usergroup feature over its stores.
type ImageStore ¶
type ImageStore interface {
Save(ctx context.Context, path string, data io.Reader) error
Open(ctx context.Context, path string) (io.ReadCloser, int64, error)
Delete(ctx context.Context, path string) error
}
ImageStore is the consumer-side blob adapter (internal/storage backend): write, read, remove. Keeping it an interface stops the identity tree from importing internal/storage directly.
type ListParams ¶
ListParams narrows and pages the admin listing.
type Page ¶
Page is the store-level paging window: plain ints with no HTTP dependency. The handler converts the request query into it.
type PostgresStore ¶
type PostgresStore struct {
// contains filtered or unexported fields
}
PostgresStore persists users in public.users. It builds on the shared Executor (pool or transaction) — never owns connections. Queries are composed with go-sqlbuilder (PostgreSQL flavor).
func NewPostgresStore ¶
func NewPostgresStore(exec datastore.Executor) *PostgresStore
NewPostgresStore builds the production user store.
func (*PostgresStore) Create ¶
func (s *PostgresStore) Create(ctx context.Context, params CreateParams) (User, error)
Create inserts a user and returns the stored row. Optional names store as NULL (nil pointers); the display name falls back to the email local part before the insert.
func (*PostgresStore) Delete ¶
func (s *PostgresStore) Delete(ctx context.Context, id UserID) error
Delete removes the user row; the database fn_soft_delete trigger archives the row into public.deleted_records. Unknown IDs surface ErrNotFound.
func (*PostgresStore) HasAnyUser ¶
func (s *PostgresStore) HasAnyUser(ctx context.Context) (bool, error)
HasAnyUser reports whether any account row exists. The initial setup contract counts every user, not only admins.
func (*PostgresStore) List ¶
func (s *PostgresStore) List(ctx context.Context, params ListParams) ([]User, int, error)
List returns matching users newest first plus the total count. Query matches username, email, or display name (case-insensitive).
func (*PostgresStore) MarkEmailVerified ¶
func (s *PostgresStore) MarkEmailVerified(ctx context.Context, id UserID) error
MarkEmailVerified stamps the email-verified timestamp.
func (*PostgresStore) MarkLogin ¶
func (s *PostgresStore) MarkLogin(ctx context.Context, id UserID) error
MarkLogin records a successful sign-in timestamp.
func (*PostgresStore) SetProfilePicturePath ¶
func (s *PostgresStore) SetProfilePicturePath(ctx context.Context, id UserID, picturePath *string) error
SetProfilePicturePath stores or clears (nil) the picture blob path.
func (*PostgresStore) UpdateAdmin ¶
func (s *PostgresStore) UpdateAdmin(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)
UpdateAdmin patches administrative fields (email, names, flags) and returns the fresh row.
func (*PostgresStore) UpdateProfile ¶
func (s *PostgresStore) UpdateProfile(ctx context.Context, id UserID, params UpdateProfileParams) (User, error)
UpdateProfile patches profile columns and returns the fresh row. Nil params keep their column; named fields stay constraint-checked by the database (display_name > 0).
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service holds the user business rules and HTTP surface. It is the identity module's mandatory core feature. Guards are not stored: the mount call receives the route groups.
func NewService ¶
func NewService(store Store, recorder identity.Recorder, opts ...ServiceOption) *Service
NewService builds the user core on top of the given store. The optional recorder captures audit events.
func (*Service) APIRoutes ¶
func (s *Service) APIRoutes(r chi.Router, _ identity.RouteGroups)
APIRoutes mounts the retained user endpoints inside the shared /api group: the bare .png profile-picture read. All other user surfaces serve ConnectRPC below /rpc; email flows stay on their feature routes.
func (*Service) BindRPCPorts ¶ added in v0.0.12
func (s *Service) BindRPCPorts(groups GroupBindingPort, credentials CredentialAdminPort)
BindRPCPorts wires the cross-feature ports the Connect surface needs; called by the composition root after every feature exists.
func (*Service) Create ¶
Create validates the payload, persists the user, and records an audit event when a recorder is wired.
func (*Service) Delete ¶
Delete removes the account (the DB trigger archives it) and records the event.
func (*Service) RPCService ¶ added in v0.0.12
RPCService returns the Connect registration for the user surface: the procedure prefix and the handler. Admin CRUD and the self profile mix in one service, so the guard is per procedure; the access authenticator comes from the composition root. The self procedures refuse a machine credential — a leaked key must not edit its owner's profile.
type ServiceOption ¶
type ServiceOption func(*Service)
ServiceOption configures the user core.
func WithImages ¶
func WithImages(images ImageStore, defaults DefaultPictureFunc) ServiceOption
WithImages wires the blob backend and the bundled-default provider. Without both, the picture surface mounts read-only or not at all (see APIRoutes).
type Store ¶
type Store interface {
List(ctx context.Context, params ListParams) ([]User, int, error)
// HasAnyUser reports whether any account exists; the setup
// contract counts every user, not only admins.
HasAnyUser(ctx context.Context) (bool, error)
Create(ctx context.Context, params CreateParams) (User, error)
GetByID(ctx context.Context, id UserID) (User, error)
UpdateAdmin(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)
UpdateProfile(ctx context.Context, id UserID, params UpdateProfileParams) (User, error)
// SetProfilePicturePath stores or clears (nil) the blob path of the user's profile picture.
SetProfilePicturePath(ctx context.Context, id UserID, path *string) error
MarkLogin(ctx context.Context, id UserID) error
MarkEmailVerified(ctx context.Context, id UserID) error
Delete(ctx context.Context, id UserID) error
}
Store abstracts user persistence: the memory store backs tests, the Postgres store backs production. Implementations assign IDs and timestamps.
type UpdateProfileParams ¶
type UpdateProfileParams struct {
FirstName *string
LastName *string
DisplayName *string
AvatarURL *string
Locale *string
}
UpdateProfileParams patches the profile fields. Nil pointers keep the current value; a non-nil pointer replaces the column (an empty string clears nullable columns; display name must stay non-empty).
type User ¶
type User struct {
ID UserID `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
FirstName *string `json:"first_name,omitzero"`
LastName *string `json:"last_name,omitzero"`
AvatarURL *string `json:"avatar_url,omitzero"`
Locale *string `json:"locale,omitzero"`
DisplayName string `json:"display_name"`
IsAdmin bool `json:"is_admin"`
Disabled bool `json:"disabled"`
EmailVerifiedAt *time.Time `json:"email_verified_at,omitzero"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at,omitzero"`
LastLoginAt *time.Time `json:"last_login_at,omitzero"`
// ProfilePicturePath is the blob-store path of the custom picture;
// nil falls back to the bundled default.
// Internal: never serialized (the .png route serves bytes).
ProfilePicturePath *string `json:"-"`
}
User is the core identity entity, shared by all features. Nullable columns use pointers; empty string is never stored for them.
type UserID ¶
Typed IDs for the user tables: UUIDv7 suffix, snake_case prefix matching the singular table name.
type UserPhoneID ¶
Typed IDs for the user tables: UUIDv7 suffix, snake_case prefix matching the singular table name.