domain

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ServiceName = "auth-service"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type APIKeyAccountAccess

type APIKeyAccountAccess struct {
	APIKeyID    string
	AccountID   string
	RoleID      *string
	RoleType    *string
	Permissions map[string]bool
}

type APIKeyCreateInput

type APIKeyCreateInput struct {
	AccountMode    constants.AccountMode
	OwnerAccountID string
	RoleID         string
	Name           string
	ExpiresAt      *time.Time
}

type APIKeyGetAccountAccessInput

type APIKeyGetAccountAccessInput struct {
	AccountMode     constants.AccountMode
	APIKeyID        int64
	TargetAccountID string
}

type APIKeyListInput

type APIKeyListInput struct {
	OwnerAccountID string
	Cursor         *string
	Limit          int32
	Query          *string
	Statuses       []constants.APIKeyStatus
	Includes       []string
}

type APIKeyListRepoInput

type APIKeyListRepoInput struct {
	OwnerAccountID string
	Cursor         *string
	Limit          int32
	Query          *string
	Statuses       []constants.APIKeyStatus
	Includes       []string
}

type APIKeyListRepoResult

type APIKeyListRepoResult struct {
	APIKeys  []*apikey.APIKey
	PageInfo pagination.PageInfo
}

type APIKeyMed

type APIKeyMed interface {
	// FindAndValidate validates a raw API key string and returns the corresponding API key model.
	//
	// 1. Parse the raw API key string to extract the key ID and secret.
	// 2. Look up the API key by its parsed ID.
	// 3. Verify the secret HMAC against the stored hash using the pepper.
	// 4. Check that the key is not expired or revoked.
	FindAndValidate(ctx context.Context, apiKey string) (*apikey.APIKey, *apierror.APIError)

	// ParseKey parses a raw API key string into its component parts.
	//
	// 1. Delegate to apikey.ParseAPIKey to extract the prefix, ID, and secret.
	ParseKey(ctx context.Context, apiKey string) (*apikey.ParsedAPIKey, *apierror.APIError)

	// TouchIfNotRecent touches a given API key if it has not been used in the last 24 hours.
	TouchIfNotRecent(ctx context.Context, apiKeyModel *apikey.APIKey) *apierror.APIError

	// Create creates a new API key for the requested account mode and persists it.
	//
	// 1. Generate a new parsed API key with a random secret for the given account mode.
	// 2. Compute the HMAC hash of the secret using the pepper.
	// 3. Generate a unique type ID and build the API key model.
	// 4. Persist the API key in the repository.
	// 5. Re-fetch the key to populate joined fields (role name, type code).
	Create(ctx context.Context, input APIKeyCreateInput) (string, *apikey.APIKey, *apierror.APIError)

	// Rotate revokes the specified API key and creates a replacement with the same name, owner account, and role.
	//
	// 1. Look up the existing API key by type ID.
	// 2. Revoke the existing key. By default revocation is immediate; a future RevokeAt schedules it (the old key keeps working until then) and is rejected with a validation error if more than 30 days out, while a past/now RevokeAt collapses to immediate.
	// 3. Create a new key using the old key's properties, with an optionally overridden expiration.
	//
	// Scoped to OwnerAccountID: returns a not-found error if the key does not exist for the requested owner.
	Rotate(ctx context.Context, input APIKeyRotateInput) (string, *apikey.APIKey, *apierror.APIError)

	// Revoke revokes an API key by its type ID.
	//
	// Scoped to ownerAccountID: returns a not-found error if the key does not exist for the given owner. This enforces tenant boundaries at the persistence layer as a backstop to service-layer ownership checks.
	Revoke(ctx context.Context, apiKeyTypeID string, ownerAccountID string) *apierror.APIError

	// List returns a paginated list of API keys for the given owner account and filters.
	//
	// 1. Query the API key repository with the provided filters and pagination parameters.
	// 2. Return the list of API keys and page info.
	List(ctx context.Context, input APIKeyListInput) (*ListAPIKeysResult, *apierror.APIError)

	// GetKeyAccountAccess returns the resolved account access for an API key targeting a specific account.
	//
	// 1. Look up the API key by its database ID.
	// 2. Verify the key's owner account matches the target account.
	// 3. Fetch the role permissions from core-service if a role is assigned.
	// 4. Return the access record with role and permission details.
	GetKeyAccountAccess(ctx context.Context, input APIKeyGetAccountAccessInput) (*APIKeyAccountAccess, *apierror.APIError)
}

type APIKeyRepo

type APIKeyRepo interface {
	Find(ctx context.Context, apiKeyID string) (*apikey.APIKey, *apierror.APIError)
	FindByDatabaseID(ctx context.Context, id int64, includes []string) (*apikey.APIKey, *apierror.APIError)
	FindByTypeID(ctx context.Context, typeID string, includes []string) (*apikey.APIKey, *apierror.APIError)
	Touch(ctx context.Context, apiKeyID int64) *apierror.APIError
	Create(ctx context.Context, apiKey *apikey.APIKey) (int64, *apierror.APIError)
	// CountRoleForOwner returns the number of roles matching roleID that are visible to ownerAccountID (system roles with a NULL account_id, or roles owned by the account). Used to validate a referenced role_id before persisting an API key.
	CountRoleForOwner(ctx context.Context, roleID string, ownerAccountID string) (int64, *apierror.APIError)
	GetByIDs(ctx context.Context, ownerAccountID string, ids []string) ([]*apikey.APIKey, *apierror.APIError)
	// Revoke marks an API key as revoked. A nil revokeAt revokes immediately using the database clock; a non-nil revokeAt schedules a future revocation. Scoped to ownerAccountID; returns a not-found error if the key does not exist for the given owner.
	Revoke(ctx context.Context, typeID string, ownerAccountID string, revokeAt *time.Time) *apierror.APIError
	List(ctx context.Context, input APIKeyListRepoInput) (*APIKeyListRepoResult, *apierror.APIError)
}

type APIKeyRevokeInput

type APIKeyRevokeInput struct {
	APIKeyTypeID   string
	OwnerAccountID string
}

type APIKeyRotateInput

type APIKeyRotateInput struct {
	AccountMode    constants.AccountMode
	APIKeyTypeID   string
	OwnerAccountID string
	ExpiresAt      *time.Time
	// RevokeAt schedules when the old key is revoked. Nil or past/now means immediate revocation; a future instant keeps the old key valid until then.
	RevokeAt *time.Time
}

type APIKeySvc

type APIKeySvc interface {
	// GetAPIKey returns a single API key's metadata by its type ID.
	//
	// Authorization:
	//   - Requires an internal admin identity with a target account in context.
	//   - The key must belong to the caller's target account.
	GetAPIKey(ctx context.Context, apiKeyID string, includes []string) (*apikey.APIKey, *apierror.APIError)

	// CreateAPIKey creates a new API key for the caller's target account.
	//
	// Authorization:
	//   - Requires an internal admin identity with a target account in context.
	CreateAPIKey(ctx context.Context, input CreateAPIKeyInput) (*CreateAPIKeyResult, *apierror.APIError)

	// RotateAPIKey rotates (revokes and replaces) an API key.
	//
	// Authorization:
	//   - Requires an internal admin identity with a target account in context.
	//
	// Side effects:
	//   - Revokes the prior API key.
	RotateAPIKey(ctx context.Context, input RotateAPIKeyInput) (*CreateAPIKeyResult, *apierror.APIError)

	// RevokeAPIKey revokes an API key without creating a replacement.
	//
	// Authorization:
	//   - Requires an internal admin identity with a target account in context.
	RevokeAPIKey(ctx context.Context, input RevokeAPIKeyInput) *apierror.APIError

	// ListAPIKeys returns a paginated list of API keys for the caller's target account.
	//
	// Authorization:
	//   - Requires an internal admin identity with a target account in context.
	//
	// Pagination:
	//   - If cursor is non-nil, results begin after the provided cursor.
	//   - limit controls the maximum number of results returned.
	ListAPIKeys(ctx context.Context, cursor *string, limit int32, query *string, statuses []constants.APIKeyStatus, includes []string) (*ListAPIKeysResult, *apierror.APIError)

	BatchGetAPIKeysByIDs(ctx context.Context, ids []string) ([]*apikey.APIKey, *apierror.APIError)
}

type AccountContext

type AccountContext struct {
	AccountID          string
	OwnerAccountID     *string
	AccountMode        constants.AccountMode
	SubscriptionStatus *string
}

AccountContext represents the context of an account (sandbox status, mode, etc.)

type AccountUserAccess

type AccountUserAccess struct {
	AccountUserID string
	AccountID     string
	RoleID        *string
	RoleType      *string
	RoleName      *string
	Permissions   map[string]bool
}

AccountUserAccess represents a user's access to an account

type AuthAccountRelation

type AuthAccountRelation struct {
	ID                      string
	CounterpartyAccountID   string
	AccountRelationRoleCode types.IdentityRelationType
	IsOwnerSide             bool
}

type AuthBillingClient

type AuthBillingClient interface {
	GetPlanByCode(ctx context.Context, planCode string) (*PlanInfo, *apierror.APIError)
	CreateCustomer(ctx context.Context, email, name, idempotencyKey string, metadata map[string]string) (*StripeCustomer, *apierror.APIError)
	SetupBillingProfile(ctx context.Context, accountID string) (*BillingProfileResult, *apierror.APIError)
	SubscribeToPricingPlan(ctx context.Context, stripeCustomerID, planCode string) *apierror.APIError
	CreateSetupIntent(ctx context.Context, customerID, idempotencyKey string) (*SetupIntentResult, *apierror.APIError)
	GetSetupIntentStatus(ctx context.Context, setupIntentID string) (*SetupIntentResult, *apierror.APIError)
	ValidateStripePricingPlan(ctx context.Context, planCode string) *apierror.APIError
	Close() error
	WaitForReady(ctx context.Context) error
}

AuthBillingClient is the interface for billing-service operations needed by auth-service.

type AuthCoreClient

type AuthCoreClient interface {
	// GetAccountContext returns whether an account is a sandbox and its mode
	GetAccountContext(ctx context.Context, accountID string) (*AccountContext, *apierror.APIError)

	// GetUserAccountAccess returns the user's role/permissions for an account
	GetUserAccountAccess(ctx context.Context, userID, accountID string) (*AccountUserAccess, bool, *apierror.APIError)

	// GetAccountRelationByUserID returns the relationship between accounts based on user. actorAccountID is required to unlock owner-side matches (the relation's owner_account_id must equal it); pass "" when no actor account has been validated to skip owner-side.
	GetAccountRelationByUserID(ctx context.Context, targetAccountID, actorAccountID, userID string) (*AuthAccountRelation, bool, *apierror.APIError)

	// GetAccountRelationByAPIKeyID returns the relationship between accounts based on API key
	GetAccountRelationByAPIKeyID(ctx context.Context, ownerAccountID string, apiKeyID int64) (*AuthAccountRelation, bool, *apierror.APIError)

	// MarkAccountUserUsed marks an account user as recently used
	MarkAccountUserUsed(ctx context.Context, accountUserID string) *apierror.APIError

	// GetRolePermissions returns the permissions for a role
	GetRolePermissions(ctx context.Context, roleID string) (map[string]bool, *apierror.APIError)

	// GetSandboxAccountByOwner returns the sandbox account ID for a given owner account
	GetSandboxAccountByOwner(ctx context.Context, ownerAccountID string) (string, *apierror.APIError)

	// GetAdminRole returns the admin role ID
	GetAdminRole(ctx context.Context) (string, *apierror.APIError)

	// CompleteRegistration creates the production account, sandbox, roles, and permissions via core-service.
	CompleteRegistration(ctx context.Context, input CompleteAccountRegistrationInput) (*CompleteRegistrationOutput, *apierror.APIError)
}

AuthCoreClient is the interface for core-service operations needed by auth-service

type AuthSvc

type AuthSvc interface {
	// ValidateCredential validates an auth token and returns the resulting identity.
	//
	// Behavior:
	//   - If authToken is empty, returns an unauthenticated identity.
	//   - If authToken is an API key credential, validates it as an API key.
	//   - Otherwise, validates it as a user credential.
	ValidateCredential(ctx context.Context, authToken string, targetAccountID *string, actorAccountID *string) (*types.Identity, *apierror.APIError)
}

type BillingProfileResult

type BillingProfileResult struct {
	ProfileID string
	CadenceID string
}

BillingProfileResult holds the IDs of the created billing profile and cadence.

type CompleteAccountRegistrationInput

type CompleteAccountRegistrationInput struct {
	UserID           string
	PlanCode         string
	StripeCustomerID string
	AccountName      string
	BusinessAddress  *RegistrationAddress
}

CompleteAccountRegistrationInput carries the data sent to core-service to create the account and sandbox.

type CompleteRegistrationOutput

type CompleteRegistrationOutput struct {
	AccountID string
	SandboxID string
}

CompleteRegistrationOutput holds the IDs of the newly created accounts.

type ConfirmPaymentInput

type ConfirmPaymentInput struct {
	SessionID     string
	SetupIntentID string
}

type ConfirmPaymentOutput

type ConfirmPaymentOutput struct {
	Status          string
	PaymentMethodID *string
}

type CreateAPIKeyInput

type CreateAPIKeyInput struct {
	RoleID    string
	Name      string
	ExpiresAt *time.Time
}

type CreateAPIKeyResult

type CreateAPIKeyResult struct {
	APIKeySecret string
	APIKey       *apikey.APIKey
}

type CreateRegistrationSessionInput

type CreateRegistrationSessionInput struct {
	Email    string
	PlanCode string
}

type CreateRegistrationSessionResult

type CreateRegistrationSessionResult struct {
	SessionID string
}

type CreateUserForRegistrationInput

type CreateUserForRegistrationInput struct {
	SessionID string
	Name      string
	Password  string // #nosec G117 - Struct field, not a hardcoded credential
}

type CreateUserForRegistrationOutput

type CreateUserForRegistrationOutput struct {
	UserID       string
	AccessToken  string // #nosec G117 - Struct field, not a hardcoded credential
	RefreshToken string // #nosec G117 - Struct field, not a hardcoded credential
}

type DocAPIKeyMed

type DocAPIKeyMed interface {
	// Resolve returns an existing doc API key for the given sandbox account, or creates one if needed.
	//
	// 1. Look up an existing doc API key for the sandbox account.
	// 2. If none exists, create a new doc API key with the system admin role.
	// 3. If the existing key is revoked, return an error indicating manual rotation is required.
	// 4. If the existing key is expired, rotate it and return the new key.
	// 5. Otherwise, decrypt and return the existing key's secret.
	//
	// Behavior:
	//   - If a non-revoked, non-expired doc API key exists, it is returned.
	//   - If the existing key is expired, a new key is created via rotation.
	//   - If the existing key is revoked, returns an error indicating rotation is required.
	Resolve(ctx context.Context, sandboxAccountID string) (*GetOrCreateDocAPIKeyResult, *apierror.APIError)

	// SyncRotatedAPIKey updates doc API key state after the underlying API key has been rotated.
	//
	// 1. Look up the existing doc API key by the old API key ID.
	// 2. If no doc API key exists for the old key, return without error (no-op).
	// 3. Delete the old doc API key record.
	// 4. Encrypt the new secret using AES-GCM.
	// 5. Create a new doc API key record pointing to the rotated API key.
	//
	// Behavior:
	//   - No-op if no doc API key exists for the old API key.
	//
	// Side effects:
	//   - Deletes the old doc API key record (if present).
	//   - Creates a new doc API key record using the rotated API key and secret.
	SyncRotatedAPIKey(ctx context.Context, input DocAPIKeySyncInput) *apierror.APIError
}

type DocAPIKeyRepo

type DocAPIKeyRepo interface {
	FindBySandboxAccountID(ctx context.Context, sandboxAccountID string) (*apikey.DocAPIKey, *apierror.APIError)
	FindByAPIKeyID(ctx context.Context, apiKeyID string) (*apikey.DocAPIKey, *apierror.APIError)
	Create(ctx context.Context, docAPIKey *apikey.DocAPIKey) (int64, *apierror.APIError)
	Update(ctx context.Context, docAPIKey *apikey.DocAPIKey) *apierror.APIError
	Delete(ctx context.Context, id int64) *apierror.APIError
}

type DocAPIKeySvc

type DocAPIKeySvc interface {
	// GetOrCreateDocAPIKey returns a documentation API key for the caller's target account.
	//
	// Authorization:
	//   - Requires an internal identity with a target account in context.
	//
	// Behavior:
	//   - Reuses an existing valid key.
	//   - Rotates and replaces an expired key.
	//
	// Side effects:
	//   - May rotate (revoke and replace) an existing doc API key.
	GetOrCreateDocAPIKey(ctx context.Context) (*GetOrCreateDocAPIKeyResult, *apierror.APIError)
}

type DocAPIKeySyncInput

type DocAPIKeySyncInput struct {
	OldAPIKeyID string
	NewSecret   string
	NewAPIKey   *apikey.APIKey
}

type GetOrCreateDocAPIKeyResult

type GetOrCreateDocAPIKeyResult struct {
	APIKeySecret string
	APIKey       *apikey.APIKey
}

type IdempotencyKey

type IdempotencyKey struct {
	ID             int64
	TypeID         string
	ServiceName    string
	Handler        string
	IdempotencyKey string
	ActorID        *string
	IdentityType   string
	ScopeHash      string
	ResponseCode   *int
	ResponseBody   json.RawMessage
	RecoveryPoint  RecoveryPoint
}

func (*IdempotencyKey) HasResponse

func (k *IdempotencyKey) HasResponse() bool

func (*IdempotencyKey) IsFinished

func (k *IdempotencyKey) IsFinished() bool

type IdempotencyKeyRepo

type IdempotencyKeyRepo interface {
	GetByScopeHash(ctx context.Context, scopeHash string) (*IdempotencyKey, *apierror.APIError)
	Create(ctx context.Context, key *IdempotencyKey) (*IdempotencyKey, *apierror.APIError)
	AdvanceRecoveryPoint(ctx context.Context, typeID string, recoveryPoint RecoveryPoint) *apierror.APIError
	GetRecoveryPoint(ctx context.Context, typeID string) (RecoveryPoint, *apierror.APIError)
	SetResponse(ctx context.Context, typeID string, code int, body json.RawMessage, recoveryPoint RecoveryPoint) *apierror.APIError
}

type IdempotencyMed

type IdempotencyMed interface {
	// UpsertIdempotencyKey upserts and returns the idempotency key for the request scope.
	//
	//  1. Resolve the idempotency key from the request context, falling back to the request ID.
	//  2. Compute the scope hash from the actor, target account, service, handler, and key.
	//  3. Return the existing key for the scope hash when one exists.
	//  4. Otherwise persist a new key at the Started recovery point, re-fetching the
	//     existing row if a concurrent request inserted the same scope hash first.
	UpsertIdempotencyKey(ctx context.Context, identity *RequestIdentity) (*IdempotencyKey, *apierror.APIError)

	// CacheErrorResponse caches a non-transient error response for the idempotency key.
	//
	//  1. Return transient errors uncached so the client can retry.
	//  2. Persist non-transient errors as the cached response and mark the key finished.
	CacheErrorResponse(ctx context.Context, typeID string, apiErr *apierror.APIError) *apierror.APIError

	// CacheSuccessResponse caches a successful response for the idempotency key.
	//
	//  1. Marshal the response data to JSON.
	//  2. Persist it as the cached response and mark the key finished.
	CacheSuccessResponse(ctx context.Context, typeID string, data any) *apierror.APIError
}

type ListAPIKeysResult

type ListAPIKeysResult struct {
	APIKeys  []*apikey.APIKey
	PageInfo pagination.PageInfo
}

type ListRegistrationSessionsInput

type ListRegistrationSessionsInput struct {
	Cursor *string
	Limit  int32
}

type ListRegistrationSessionsResult

type ListRegistrationSessionsResult struct {
	Sessions []*RegistrationSession
	PageInfo pagination.PageInfo
}

type LoginResult

type LoginResult struct {
	User         *types.User
	RefreshToken string // #nosec G117 - Struct field, not a hardcoded credential
	AccessToken  string // #nosec G117 - Struct field, not a hardcoded credential
}

type MediatorFactory

type MediatorFactory interface {
	Build(RepoFactory) Mediators
}

MediatorFactory builds mediators bound to a given repository factory (e.g., per transaction).

type Mediators

type Mediators struct {
	User         UserMed
	APIKey       APIKeyMed
	DocAPIKey    DocAPIKeyMed
	Password     PasswordMed
	RefreshToken RefreshTokenMed
	Idempotency  IdempotencyMed
	Registration RegistrationMed
}

Mediators groups all mediator dependencies built for a specific repository factory.

type NotificationPublisher

type NotificationPublisher interface {
	PublishSendEmail(ctx context.Context, data messaging.EmailSendData) *apierror.APIError
}

type PasswordMed

type PasswordMed interface {
	// RequestReset initiates a password reset flow for the given identifier.
	//
	//  1. Look up the user by identifier; silently succeed if not found to avoid leaking information about registered identifiers.
	//  2. Generate a short-lived password reset JWT (15 minutes).
	//  3. Build the reset link, optionally scoped to an account slug.
	//  4. Send a password reset email with the link.
	//
	// Behavior:
	//   - Only returns an error for internal service failures; unknown identifiers
	//     succeed silently to prevent enumeration.
	//
	// Side effects:
	//   - Sends a password reset email.
	RequestReset(ctx context.Context, identifier string, accountSlug, portalBaseURL *string) *apierror.APIError

	// ValidatePasswordResetToken validates a password reset token and returns the associated user.
	//
	// 1. Decode and verify the JWT token as a password-reset type.
	// 2. Look up the user by the token's subject (user ID).
	// 3. Return an authentication error if the user is not found.
	ValidatePasswordResetToken(ctx context.Context, token string) (*types.User, *apierror.APIError)

	// Validate validates the identifier/password combination and returns the associated user.
	//
	//  1. Look up the user by identifier (email or user ID).
	//  2. If the user has no stored password hash, silently send a password reset email (when an email is on file) and return the generic invalid-credentials error so that the response is indistinguishable from a missing user or wrong password. This preserves recovery for legitimate passwordless users without leaking account state to unauthenticated callers.
	//  3. Compare the provided password against the stored hash.
	//  4. Return the user if the password matches; return an authentication error otherwise.
	Validate(ctx context.Context, identifier, password string) (*types.User, *apierror.APIError)

	// Update updates a user's password.
	//
	// 1. Hash the new password.
	// 2. Persist the updated password hash in the repository.
	// 3. Revoke all existing refresh tokens for the user.
	// 4. Send a password updated notification email.
	//
	// Side effects:
	//   - Updates the stored password hash.
	//   - Revokes all refresh tokens for the user.
	//   - Sends a password updated email.
	Update(ctx context.Context, user *types.User, newPassword string) *apierror.APIError
}

type PasswordSvc

type PasswordSvc interface {
	// UpdatePassword updates a user's password.
	//
	// Side effects:
	//   - Revokes existing refresh tokens for the user.
	UpdatePassword(ctx context.Context, oldPassword, newPassword string) *apierror.APIError

	// ResetPassword completes the password reset flow and returns a token pair plus the user profile.
	//
	// Side effects:
	//   - Revokes existing refresh tokens for the user.
	ResetPassword(ctx context.Context, token, newPassword string) (*LoginResult, *apierror.APIError)

	// RequestPasswordReset initiates a password reset flow for the identifier.
	RequestPasswordReset(ctx context.Context, identifier string, accountSlug, portalBaseURL *string) *apierror.APIError
}

type PlanInfo

type PlanInfo struct {
	TypeID        string
	Name          string
	PlanTypeCode  string
	PricePerSeat  float64
	PricePerMonth *float64
	SeatMinimum   *int
}

PlanInfo holds the pricing plan data returned by the billing service.

type RecoveryPoint

type RecoveryPoint string
const (
	RecoveryPointStarted            RecoveryPoint = "auth:started"
	RecoveryPointCustomerCreated    RecoveryPoint = "auth:customer_created"
	RecoveryPointCoreAccountCreated RecoveryPoint = "auth:core_account_created"
	RecoveryPointAccountsCreated    RecoveryPoint = "auth:accounts_created"
	RecoveryPointFinished           RecoveryPoint = "auth:finished"
)

func (RecoveryPoint) IsFinished

func (r RecoveryPoint) IsFinished() bool

func (RecoveryPoint) IsStarted

func (r RecoveryPoint) IsStarted() bool

func (RecoveryPoint) IsValid

func (r RecoveryPoint) IsValid() bool

func (RecoveryPoint) String

func (r RecoveryPoint) String() string

type RefreshToken

type RefreshToken struct {
	Token     string
	UserID    string     `audit:"user_id"`
	ExpiresAt time.Time  `audit:"expires_at"`
	RevokedAt *time.Time `audit:"revoked_at"`
}

func (*RefreshToken) IsExpired

func (m *RefreshToken) IsExpired() bool

IsExpired reports whether the refresh token has passed its expiration time.

func (*RefreshToken) IsRevoked

func (m *RefreshToken) IsRevoked() bool

IsRevoked reports whether the refresh token has been revoked.

type RefreshTokenMed

type RefreshTokenMed interface {
	// Create issues a new refresh token for the given user ID.
	//
	// 1. Generate a cryptographically random opaque token.
	// 2. Default expiration to 30 days if expiresInDays is nil.
	// 3. Persist the token in the repository with the computed expiration.
	Create(ctx context.Context, userID string, expiresInDays *int) (*RefreshToken, *apierror.APIError)

	// Validate validates a refresh token and returns the associated user ID.
	//
	// 1. Look up the refresh token in the repository.
	// 2. Verify the token is not revoked.
	// 3. Verify the token is not expired.
	// 4. Return the associated user ID.
	Validate(ctx context.Context, refreshToken string) (string, *apierror.APIError)

	// Revoke revokes a single refresh token, preventing it from being used to mint new access tokens.
	//
	// 1. Look up the refresh token in the repository.
	// 2. Verify the token is not already revoked or expired.
	// 3. Mark the token as revoked in the repository.
	Revoke(ctx context.Context, refreshToken string) *apierror.APIError

	// RevokeAll revokes all refresh tokens associated with a user.
	//
	// 1. Revoke all refresh tokens for the given user ID in the repository.
	//
	// Behavior:
	//   - Prevents stale tokens from being used after a password change.
	RevokeAll(ctx context.Context, userID string) *apierror.APIError
}

type RefreshTokenRepo

type RefreshTokenRepo interface {
	Find(ctx context.Context, token string) (*RefreshToken, *apierror.APIError)
	Create(ctx context.Context, userID string, token string, expiresInDays int) (*RefreshToken, *apierror.APIError)
	Revoke(ctx context.Context, token string) *apierror.APIError
	RevokeAll(ctx context.Context, userID string) *apierror.APIError
}

type RefreshTokenResult

type RefreshTokenResult struct {
	AccessToken string // #nosec G117 - Struct field, not a hardcoded credential
}

type RegisterInput

type RegisterInput struct {
	Name        string
	Email       string
	Password    string  // #nosec G117 - Struct field, not a hardcoded credential
	AccountSlug *string // Portal context for the "already registered" magic login link.
	// PortalBaseURL is the base URL of the account's verified custom portal domain, resolved server-side by the gateway. When set, email links use it instead of the slug-prefixed dashboard URL.
	PortalBaseURL *string
}

type RegisterUserInput

type RegisterUserInput struct {
	Name           string
	Email          string
	HashedPassword string
	AccountSlug    *string // Portal context for the "already registered" magic login link.
}

type RegistrationAddress

type RegistrationAddress struct {
	Line1      string
	Line2      string
	City       string
	State      string
	PostalCode string
	Country    string
}

RegistrationAddress is a structured postal address collected during registration.

type RegistrationMed

type RegistrationMed interface {
	// CreateSession creates a new registration session or returns an existing active session for the given email (idempotent).
	//
	//  1. Check if the user already exists (noted but does not prevent session creation).
	//  2. Look for an existing non-expired session for the email; if found, update the plan code if different and resend the verification email.
	//  3. Generate a unique type ID and verification token.
	//  4. Create a new registration session record.
	//  5. Send the verification email.
	//
	// Side effects:
	//   - Sends a verification email to the user.
	CreateSession(ctx context.Context, input CreateRegistrationSessionInput) (*CreateRegistrationSessionResult, *apierror.APIError)

	// ResendVerificationEmail regenerates the verification token and resends the verification email.
	//
	// 1. Look up the session by type ID.
	// 2. Validate the session is not completed and email is not already verified.
	// 3. Generate a new verification token and update the session.
	// 4. Send the verification email with the new token.
	//
	// Side effects:
	//   - Rotates the verification token.
	//   - Sends a new verification email.
	ResendVerificationEmail(ctx context.Context, sessionID string) *apierror.APIError

	// VerifyToken verifies the email verification token and marks the session as email-verified.
	//
	// 1. Look up the session by verification token.
	// 2. Reject completed sessions.
	// 3. Check token expiry (24-hour TTL from last update).
	// 4. If already verified, return the current session without changes (idempotent).
	// 5. Check if a user already exists for the session's email.
	// 6. Mark the email as verified and advance the step to user_details.
	// 7. Re-fetch and return the updated session.
	VerifyToken(ctx context.Context, token string) (*RegistrationSession, *apierror.APIError)

	// CreateUserForSession creates a new user for the registration session and returns the user ID with auth tokens.
	//
	//  1. Look up the session by type ID and validate it is not completed and email is verified.
	//  2. If the session already has a user, generate tokens for the existing user (idempotent).
	//  3. Reject if an account already exists for the session's email — pre-existing accounts must authenticate via login, not by holding a verified session id.
	//  4. Hash the password and create a new user record.
	//  5. Associate the user with the session and update session data with the user name.
	//  6. Advance the session step to account_details.
	//  7. Generate and return an access token and refresh token.
	//
	// Side effects:
	//   - May create a new user record.
	//   - Associates the user with the session.
	//   - Advances the session step to account_details.
	CreateUserForSession(ctx context.Context, input CreateUserForRegistrationInput) (*CreateUserForRegistrationOutput, *apierror.APIError)

	// UpdateSession updates an in-progress registration session's step and form data.
	//
	// 1. Look up the session by type ID and validate it is not completed.
	// 2. Validate the step transition allows only forward progression.
	// 3. Merge the provided session data into the existing data (non-nil fields only).
	// 4. Persist the updated step and data.
	// 5. Re-fetch and return the refreshed session.
	UpdateSession(ctx context.Context, sessionID string, step *constants.RegistrationStep, sessionData *UpdateRegistrationSessionData) (*RegistrationSession, *apierror.APIError)

	// GetSession returns the registration session for the given type ID.
	//
	// 1. Look up and return the session by its type ID.
	GetSession(ctx context.Context, sessionID string) (*RegistrationSession, *apierror.APIError)

	// CompleteSession marks a registration session as completed and records the account ID.
	//
	// 1. Look up the session by type ID.
	// 2. Mark the session as completed with the provided account ID in the repository.
	CompleteSession(ctx context.Context, sessionID, accountID string) *apierror.APIError
}

type RegistrationQueueRepo

type RegistrationQueueRepo interface {
	// Create inserts a registration queue entry for the session, deduplicating on registration_session_id. The returned bool is true only when this call actually inserted a new row (so callers can suppress follow-on side effects like alert emails on retries).
	Create(ctx context.Context, email, name, planCode, registrationSessionID string) (bool, *apierror.APIError)
}

type RegistrationSession

type RegistrationSession struct {
	ID                      int64
	TypeID                  string
	Email                   string                     `audit:"email"`
	PlanCode                string                     `audit:"plan_code"`
	Step                    constants.RegistrationStep `audit:"step"`
	VerificationToken       string
	IsEmailVerified         bool    `audit:"is_email_verified"`
	IsExistingUser          *bool   `audit:"is_existing_user"`
	UserID                  *string `audit:"user_id"`
	AccountID               *string `audit:"account_id"`
	StripeCustomerID        *string
	StripeCheckoutSessionID *string
	StripeSubscriptionID    *string
	PaymentCompleted        bool                    `audit:"payment_completed"`
	SessionData             RegistrationSessionData `audit:"session_data"`
	CompletedAt             *time.Time              `audit:"completed_at"`
	CreatedAt               time.Time
	UpdatedAt               time.Time
}

func (*RegistrationSession) ToProto

type RegistrationSessionData

type RegistrationSessionData struct {
	UserName                 string
	AccountName              string
	BillingAddressLine1      string
	BillingAddressLine2      string
	BillingAddressCity       string
	BillingAddressState      string
	BillingAddressPostalCode string
	BillingAddressCountry    string
}

type RegistrationSessionRepo

type RegistrationSessionRepo interface {
	// GetByEmail returns the most recent active (uncompleted) registration session for the given email. Returns a not-found error if none exists.
	GetByEmail(ctx context.Context, email string) (*RegistrationSession, *apierror.APIError)

	// GetByTypeID returns the registration session with the given type ID. Returns a not-found error if none exists.
	GetByTypeID(ctx context.Context, typeID string) (*RegistrationSession, *apierror.APIError)

	// GetByToken returns the registration session matching the verification token. Returns a not-found error if no session has the given token.
	GetByToken(ctx context.Context, token string) (*RegistrationSession, *apierror.APIError)

	// GetByID returns the registration session with the given database ID. Returns a not-found error if none exists.
	GetByID(ctx context.Context, id int64) (*RegistrationSession, *apierror.APIError)

	// GetIncompleteByUserID returns the most recent incomplete registration session for the given user, or (nil, nil) if none exists.
	GetIncompleteByUserID(ctx context.Context, userID string) (*RegistrationSession, *apierror.APIError)

	// Create persists a new registration session and returns the database-assigned ID.
	Create(ctx context.Context, session *RegistrationSession) (int64, *apierror.APIError)

	// UpdatePlanCode changes the plan code on a registration session.
	UpdatePlanCode(ctx context.Context, id int64, planCode string) *apierror.APIError

	// UpdateToken replaces the verification token for the given session.
	UpdateToken(ctx context.Context, id int64, verificationToken string) *apierror.APIError

	// UpdateEmailVerified marks the session's email as verified and sets the is_existing_user flag.
	UpdateEmailVerified(ctx context.Context, id int64, isExistingUser *bool) *apierror.APIError

	// UpdateStep advances the session to the given step and persists the session data.
	UpdateStep(ctx context.Context, id int64, step constants.RegistrationStep, sessionData RegistrationSessionData) *apierror.APIError

	// UpdateUser sets the user ID on the session and persists updated session data.
	UpdateUser(ctx context.Context, id int64, userID string, sessionData RegistrationSessionData) *apierror.APIError

	// UpdateStripeCustomer sets the Stripe customer ID and checkout session ID on the registration session.
	UpdateStripeCustomer(ctx context.Context, id int64, stripeCustomerID *string, stripeCheckoutSessionID *string) *apierror.APIError

	// UpdatePaymentCompleted sets the payment completed flag and Stripe subscription ID on the registration session.
	UpdatePaymentCompleted(ctx context.Context, id int64, paymentCompleted bool, stripeSubscriptionID *string) *apierror.APIError

	// ListByUserID returns open (uncompleted) registration sessions for the given user with cursor-based pagination.
	ListByUserID(ctx context.Context, userID string, cursor *string, limit int32) ([]*RegistrationSession, pagination.PageInfo, *apierror.APIError)

	// UpdateAccountID sets the account ID on the registration session without marking it as completed.
	UpdateAccountID(ctx context.Context, id int64, accountID string) *apierror.APIError

	// Complete marks the registration session as completed and records the account ID.
	Complete(ctx context.Context, id int64, accountID *string) *apierror.APIError
}

type RegistrationSessionSvc

type RegistrationSessionSvc interface {
	// CreateSession creates a new registration session or returns an existing active (uncompleted) session for the given email.
	//
	// Side effects:
	//   - Sends a verification email to the user.
	CreateSession(ctx context.Context, input CreateRegistrationSessionInput) (*CreateRegistrationSessionResult, *apierror.APIError)

	// ResendVerificationEmail regenerates the verification token for an existing registration session and resends the verification email.
	//
	// Side effects:
	//   - Rotates the verification token.
	//   - Sends a new verification email to the user.
	ResendVerificationEmail(ctx context.Context, sessionID string) *apierror.APIError

	// VerifyToken verifies the email token from the registration verification link. Marks the session's email as verified and advances the step to user_details. Idempotent: repeated calls return the same session.
	VerifyToken(ctx context.Context, token string) (*RegistrationSession, *apierror.APIError)

	// GetSession returns the current state of a registration session by its type ID. Returns a not-found error if the session does not exist.
	GetSession(ctx context.Context, sessionID string) (*RegistrationSession, *apierror.APIError)

	// CreateUserForSession creates or resolves a user for a registration session and returns the user ID with auth tokens.
	//
	// Side effects:
	//   - Creates a new user if one does not already exist for the session email.
	//   - Associates the user with the registration session.
	//   - Advances the session step to account_details.
	CreateUserForSession(ctx context.Context, input CreateUserForRegistrationInput) (*CreateUserForRegistrationOutput, *apierror.APIError)

	// UpdateSession updates an in-progress registration session's step, form data, and/or Stripe-related fields. Returns the updated session.
	//
	// Authorization:
	//   - Requires a user identity in context.
	UpdateSession(ctx context.Context, input UpdateRegistrationSessionInput) (*RegistrationSession, *apierror.APIError)

	// ListSessions returns a paginated list of open (uncompleted) registration sessions for the authenticated user.
	//
	// Authorization:
	//   - Requires a user identity in context.
	ListSessions(ctx context.Context, input ListRegistrationSessionsInput) (*ListRegistrationSessionsResult, *apierror.APIError)

	// SetupBilling creates a Stripe customer and Setup Intent for a registration session. Uses recovery points for crash safety.
	//
	// Authorization:
	//   - Requires a user identity in context.
	SetupBilling(ctx context.Context, input SetupBillingInput) (*SetupBillingOutput, *apierror.APIError)

	// ConfirmPayment verifies that a Setup Intent succeeded and marks the registration session's payment as completed.
	//
	// Authorization:
	//   - Requires a user identity in context matching the session's user.
	ConfirmPayment(ctx context.Context, input ConfirmPaymentInput) (*ConfirmPaymentOutput, *apierror.APIError)

	// CompleteRegistration finalizes a registration session by calling core-service to create the production account, sandbox, roles, and permissions, then marks the session as completed.
	//
	// Authorization:
	//   - Requires a user identity in context matching the session's user.
	CompleteRegistration(ctx context.Context, sessionID string) (*CompleteRegistrationOutput, *apierror.APIError)

	// GetIncompleteByUserID returns the most recent incomplete registration session for the given user, or (nil, nil) if none exists.
	GetIncompleteByUserID(ctx context.Context, userID string) (*RegistrationSession, *apierror.APIError)
}

type RepoFactory

type RepoFactory interface {
	NewUserRepo() UserRepo
	NewRefreshTokenRepo() RefreshTokenRepo
	NewAPIKeyRepo() APIKeyRepo
	NewDocAPIKeyRepo() DocAPIKeyRepo
	NewRegistrationSessionRepo() RegistrationSessionRepo
	NewRegistrationQueueRepo() RegistrationQueueRepo
	NewIdempotencyKeyRepo() IdempotencyKeyRepo
	NewOutboxRepo() messaging.OutboxRepo
}

RepoFactory provides repository instances that share the same underlying sqlc query executor (plain DB access or a transaction).

type RequestIdentity

type RequestIdentity struct {
	ActorID         string
	IdentityType    types.IdentityActorType
	TargetAccountID *string
}

type RevokeAPIKeyInput

type RevokeAPIKeyInput struct {
	APIKeyID string
}

type RotateAPIKeyInput

type RotateAPIKeyInput struct {
	APIKeyID  string
	ExpiresAt *time.Time
	// RevokeAt schedules when the old key is revoked. Nil or past/now means immediate revocation; a future instant keeps the old key valid until then.
	RevokeAt *time.Time
}

type SetupBillingInput

type SetupBillingInput struct {
	SessionID string
}

type SetupBillingOutput

type SetupBillingOutput struct {
	StripeCustomerID string
	ClientSecret     string // #nosec G117 -- Stripe ephemeral client secret
	PublishableKey   string
}

type SetupIntentResult

type SetupIntentResult struct {
	SetupIntentID   string
	ClientSecret    string // #nosec G117 -- Stripe ephemeral client secret
	Status          string
	PaymentMethodID *string
	PublishableKey  string
}

SetupIntentResult holds the result of a Setup Intent operation from billing-service.

type StripeCustomer

type StripeCustomer struct {
	ID string
}

StripeCustomer represents a Stripe customer created during registration.

type TokenSvc

type TokenSvc interface {
	// RefreshToken exchanges a valid refresh token for a new short-lived access token.
	RefreshToken(ctx context.Context, refreshToken string) (*RefreshTokenResult, *apierror.APIError)

	// RevokeRefreshToken invalidates a refresh token so it can no longer be used to mint access tokens.
	RevokeRefreshToken(ctx context.Context, refreshToken string) *apierror.APIError
}

type UpdateRegistrationSessionData

type UpdateRegistrationSessionData struct {
	UserName                 *string
	AccountName              *string
	BillingAddressLine1      *string
	BillingAddressLine2      *string
	BillingAddressCity       *string
	BillingAddressState      *string
	BillingAddressPostalCode *string
	BillingAddressCountry    *string
}

func (*UpdateRegistrationSessionData) MergeInto

MergeInto applies non-nil fields from the update into the target, leaving fields that were not provided in the PATCH request unchanged.

type UpdateRegistrationSessionInput

type UpdateRegistrationSessionInput struct {
	SessionID   string
	Step        *constants.RegistrationStep
	SessionData *UpdateRegistrationSessionData
}

type UserMed

type UserMed interface {
	// GenAuthAccessToken mints an access token that can be used to authenticate requests to the API.
	GenAuthAccessToken(ctx context.Context, userID string) (string, *apierror.APIError)

	// Register registers a new user with the given input.
	//
	// 1. Check if a user with the given email already exists; return a validation error if so.
	// 2. Generate a unique user ID.
	// 3. Create the user record in the repository.
	// 4. Send a welcome email if the user has an email and name.
	//
	// Side effects:
	//   - Sends a welcome email.
	Register(ctx context.Context, input RegisterUserInput) (*types.User, *apierror.APIError)

	// ValidateMagicLoginToken validates a magic-login token and returns the associated user.
	ValidateMagicLoginToken(ctx context.Context, token string) (*types.User, *apierror.APIError)

	// SendAlreadyRegisteredEmail generates a magic login token and sends the "already registered" email so the user can log in with one click. This must be called outside a transaction so the outbox message is not rolled back.
	SendAlreadyRegisteredEmail(ctx context.Context, user *types.User, accountSlug, portalBaseURL *string)

	// ValidateCredential validates credentials provided by a request and returns an identity.
	//
	//  1. If authToken is empty, resolve the account mode for the target account (if provided) and return an unauthenticated identity.
	//  2. If authToken has the API key prefix, delegate to validateAPIKeyCredential.
	//  3. Otherwise, delegate to validateUserCredential for JWT-based validation.
	//
	// Behavior:
	//   - If authToken is empty, returns an unauthenticated identity.
	//   - If authToken is an API key credential, validates it as an API key.
	//   - Otherwise, validates it as a user credential (JWT).
	ValidateCredential(ctx context.Context, authToken string, targetAccountID *string, actorAccountID *string) (*types.Identity, *apierror.APIError)
}

type UserRepo

type UserRepo interface {
	Find(ctx context.Context, identifier string) (*types.User, *apierror.APIError)
	Create(ctx context.Context, userID, email, name, hashedPassword string) (*types.User, *apierror.APIError)
	UpdatePassword(ctx context.Context, userID string, hashedPassword string) *apierror.APIError
}

type UserSvc

type UserSvc interface {
	// Login authenticates a user and returns a token pair (access + refresh) plus the user profile.
	Login(ctx context.Context, identifier, password string) (*LoginResult, *apierror.APIError)

	// Register creates a new user and returns a token pair so the user is immediately logged in.
	Register(ctx context.Context, input RegisterInput) (*LoginResult, *apierror.APIError)

	// MagicLogin exchanges a magic-login token for a token pair, logging the user in without a password.
	MagicLogin(ctx context.Context, token string) (*LoginResult, *apierror.APIError)
}

Directories

Path Synopsis
mock
client
Package clientmock is a generated GoMock package.
Package clientmock is a generated GoMock package.
factory
Package factorymock is a generated GoMock package.
Package factorymock is a generated GoMock package.
mediator
Package mediatormock is a generated GoMock package.
Package mediatormock is a generated GoMock package.
publisher
Package publishermock is a generated GoMock package.
Package publishermock is a generated GoMock package.
repository
Package repositorymock is a generated GoMock package.
Package repositorymock is a generated GoMock package.
service
Package servicemock is a generated GoMock package.
Package servicemock is a generated GoMock package.

Jump to

Keyboard shortcuts

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