Documentation
¶
Overview ¶
Package accountlinking provides secure linking and unlinking of authentication accounts while preserving a single canonical Core User identity in GoModularAuth.
Index ¶
- Constants
- Variables
- func RegisterSchema(s *schema.Schema) error
- type Account
- type AccountLinkedPayload
- type AccountLinking
- type AccountUnlinkedPayload
- type CompleteTicketInput
- type Config
- type CreateTicketInput
- type LinkAccountInput
- type LinkFailedPayload
- type LinkingTicket
- type LinkingTicketResult
- type Module
- type Option
- type Principal
- type TicketCreatedPayload
- type UnlinkAccountByIDInput
- type UnlinkAccountInput
- type UnlinkFailedPayload
- type User
Constants ¶
const ( // EventAccountLinked is emitted when an authentication provider account is successfully linked to a user. EventAccountLinked auth.EventName = "account.linked" // EventAccountUnlinked is emitted when an authentication provider account is detached from a user. EventAccountUnlinked auth.EventName = "account.unlinked" // EventLinkFailed is emitted when an account linking attempt fails (conflict, validation, authorization). EventLinkFailed auth.EventName = "account.link.failed" // EventUnlinkFailed is emitted when an account unlinking attempt is blocked (e.g. final auth method). EventUnlinkFailed auth.EventName = "account.unlink.failed" // EventTicketCreated is emitted when a new linking ceremony ticket is issued. EventTicketCreated auth.EventName = "account.ticket.created" )
const (
ModelLinkingTickets = "linking_tickets"
)
const PluginID = "account-linking"
PluginID identifies this plugin in the GoModularAuth runtime.
Variables ¶
var ( // ErrAccountNotFound indicates that the requested account does not exist. ErrAccountNotFound = errors.New("account-linking: account not found") // ErrAccountAlreadyLinked indicates that the provider account is already linked to the current user. ErrAccountAlreadyLinked = errors.New("account-linking: account is already linked to this user") // ErrAccountConflict indicates that the provider account is already linked to another distinct user. // The plugin strictly forbids silent merging of two Users to prevent account takeover. ErrAccountConflict = errors.New("account-linking: account is already linked to another user") // ErrCannotUnlinkFinalAuthMethod indicates that the requested unlink operation would remove the user's // sole remaining authentication method, permanently orphaning the user account. ErrCannotUnlinkFinalAuthMethod = errors.New("account-linking: cannot unlink the user's sole remaining authentication method") ErrUnauthorized = errors.New("account-linking: unauthorized actor for account operation") // ErrUserNotFound indicates that the target user does not exist in the Core user store. ErrUserNotFound = errors.New("account-linking: user not found") // ErrInvalidInput indicates that one or more required input arguments are missing or malformed. ErrInvalidInput = errors.New("account-linking: invalid input") // ErrTicketNotFound indicates that the linking ticket does not exist. ErrTicketNotFound = errors.New("account-linking: linking ticket not found") // ErrTicketExpired indicates that the linking ticket has exceeded its validity window. ErrTicketExpired = errors.New("account-linking: linking ticket has expired") // ErrTicketAlreadyUsed indicates that the linking ticket was already redeemed (single-use violation). ErrTicketAlreadyUsed = errors.New("account-linking: linking ticket was already consumed") // ErrProviderMismatch indicates that the linking ticket was issued for a specific provider but presented with another. ErrProviderMismatch = errors.New("account-linking: provider does not match linking ticket target") // ErrStoreRequired indicates that a persistent store is required for ticket-based linking operations. ErrStoreRequired = errors.New("account-linking: store is required") )
Functions ¶
func RegisterSchema ¶
RegisterSchema registers the linking tickets model in the compiled schema.
Types ¶
type AccountLinkedPayload ¶
type AccountLinkedPayload struct {
UserID string `json:"user_id"`
AccountID string `json:"account_id"`
Provider string `json:"provider"`
ProviderID string `json:"provider_id"`
ActorSubject string `json:"actor_subject,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
AccountLinkedPayload carries structured details for EventAccountLinked.
type AccountLinking ¶
type AccountLinking interface {
// LinkAccount securely attaches an external provider account to a target User.
// It participates in a Store transaction, enforces caller authorization, prevents
// silent merging across distinct users (detecting collisions), and emits audit events.
LinkAccount(ctx context.Context, in LinkAccountInput) (*account.Account, error)
// UnlinkAccount detaches an external provider account from a User by provider and providerID.
// Fails with ErrCannotUnlinkFinalAuthMethod if this is the user's sole authentication method
// unless AllowOrphan is explicitly enabled.
UnlinkAccount(ctx context.Context, in UnlinkAccountInput) error
// UnlinkAccountByID detaches an external provider account from a User by Core Account ID.
// Enforces ownership and final authentication method protection.
UnlinkAccountByID(ctx context.Context, in UnlinkAccountByIDInput) error
// CreateLinkingTicket issues a single-use, time-limited, cryptographically random ticket
// for two-step linking flows (e.g. initiating linking in session, completing after OAuth/WebAuthn redirect).
CreateLinkingTicket(ctx context.Context, in CreateTicketInput) (*LinkingTicketResult, error)
// CompleteLinkingTicket redeems a linking ticket, validating expiration, single-use redemption,
// and provider restrictions, before linking the account within a Store transaction.
CompleteLinkingTicket(ctx context.Context, in CompleteTicketInput) (*account.Account, error)
// GetLinkingTicket inspects a linking ticket by its plain token without consuming it.
GetLinkingTicket(ctx context.Context, ticketToken string) (*LinkingTicket, error)
// RevokeLinkingTicket cancels an active linking ticket before expiration.
RevokeLinkingTicket(ctx context.Context, ticketToken string) error
// ListLinkedAccounts returns all authentication accounts currently attached to a user.
ListLinkedAccounts(ctx context.Context, userID string) ([]*account.Account, error)
// GetAccount retrieves a specific linked account by provider and provider ID.
GetAccount(ctx context.Context, provider, providerID string) (*account.Account, error)
// CanUnlinkAccount evaluates whether the specified account can be safely unlinked
// without leaving the user without any authentication methods.
CanUnlinkAccount(ctx context.Context, userID, accountID string) (bool, error)
}
AccountLinking defines the strongly typed capability for orchestrating secure account linking and unlinking across any authentication mechanism in GoModularAuth.
Applications and transport adapters retrieve this capability via:
linking, err := auth.Get[plugins.AccountLinking](engine) // or linking, err := auth.Get[accountlinking.AccountLinking](engine)
type AccountUnlinkedPayload ¶
type AccountUnlinkedPayload struct {
UserID string `json:"user_id"`
AccountID string `json:"account_id"`
Provider string `json:"provider"`
ProviderID string `json:"provider_id"`
ActorSubject string `json:"actor_subject,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
AccountUnlinkedPayload carries structured details for EventAccountUnlinked.
type CompleteTicketInput ¶
type CompleteTicketInput struct {
// TicketToken is the unhashed token issued during CreateLinkingTicket.
TicketToken string `json:"ticket_token"`
// Provider identifies the authentication provider being linked.
Provider string `json:"provider"`
// ProviderID identifies the user within the provider.
ProviderID string `json:"provider_id"`
// Metadata contains optional provider claims.
Metadata map[string]string `json:"metadata,omitempty"`
}
CompleteTicketInput specifies parameters to redeem a linking ticket and attach an external account.
type Config ¶
type Config struct {
// DefaultTicketTTL is the default validity duration for linking tickets. Defaults to 15 minutes.
DefaultTicketTTL time.Duration
// AllowOrphanByDefault controls whether removing the final authentication method is allowed by default. Defaults to false.
AllowOrphanByDefault bool
// Clock provides time abstraction for deterministic testing. Defaults to time.Now().UTC.
Clock func() time.Time
}
Config holds configuration parameters for the Account Linking plugin.
type CreateTicketInput ¶
type CreateTicketInput struct {
// UserID identifies the authenticated User initiating the linking flow.
UserID string `json:"user_id"`
// TargetProvider restricts the ticket to a specific provider if specified (optional).
TargetProvider string `json:"target_provider,omitempty"`
// TTL defines the ticket expiration window. Defaults to plugin configuration if zero.
TTL time.Duration `json:"ttl,omitempty"`
// Metadata holds arbitrary state to roundtrip with the ticket (e.g. redirect URI).
Metadata map[string]string `json:"metadata,omitempty"`
}
CreateTicketInput specifies parameters to initiate a two-step linking ceremony.
type LinkAccountInput ¶
type LinkAccountInput struct {
// UserID identifies the canonical Core User.
UserID string `json:"user_id"`
// Provider identifies the authentication provider (e.g. "google", "github", "passkey", "password").
Provider string `json:"provider"`
// ProviderID identifies the user uniquely inside the provider (e.g. subject, sub, email, or credential ID).
ProviderID string `json:"provider_id"`
// Metadata contains optional provider-specific claims or attributes.
Metadata map[string]string `json:"metadata,omitempty"`
}
LinkAccountInput specifies the parameters to link a new authentication provider identity to a User.
type LinkFailedPayload ¶
type LinkFailedPayload struct {
UserID string `json:"user_id"`
Provider string `json:"provider"`
ProviderID string `json:"provider_id"`
Reason string `json:"reason"`
ActorSubject string `json:"actor_subject,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
LinkFailedPayload carries audit details when a link operation fails.
type LinkingTicket ¶
type LinkingTicket struct {
ID string `json:"id"`
TokenHash string `json:"token_hash"`
UserID string `json:"user_id"`
TargetProvider string `json:"target_provider,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
ConsumedAt *time.Time `json:"consumed_at,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
}
LinkingTicket holds ephemeral single-use state for two-step account linking ceremonies.
func (*LinkingTicket) Expired ¶
func (t *LinkingTicket) Expired(now time.Time) bool
Expired reports whether the ticket is expired relative to the given time.
func (*LinkingTicket) IsConsumed ¶
func (t *LinkingTicket) IsConsumed() bool
IsConsumed reports whether the ticket has already been used.
type LinkingTicketResult ¶
type LinkingTicketResult struct {
// TicketID is the internal identifier.
TicketID string `json:"ticket_id"`
// TicketToken is the unhashed bearer token to present when completing the linking ceremony.
TicketToken string `json:"ticket_token"`
// UserID is the bound user ID.
UserID string `json:"user_id"`
// TargetProvider is the optional restricted provider.
TargetProvider string `json:"target_provider,omitempty"`
// ExpiresAt specifies the exact expiration instant.
ExpiresAt time.Time `json:"expires_at"`
}
LinkingTicketResult holds the issued ticket metadata and plain token.
type Module ¶
type Module struct {
// contains filtered or unexported fields
}
Module implements auth.PluginModule using plugin-sdk.
type Option ¶
type Option func(*Config)
Option configures Config.
func WithAllowOrphanByDefault ¶
WithAllowOrphanByDefault sets whether removing the final auth method is permitted globally.
func WithDefaultTicketTTL ¶
WithDefaultTicketTTL sets the default validity duration for linking tickets.
type TicketCreatedPayload ¶
type TicketCreatedPayload struct {
TicketID string `json:"ticket_id"`
UserID string `json:"user_id"`
TargetProvider string `json:"target_provider,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
ActorSubject string `json:"actor_subject,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
TicketCreatedPayload carries audit details when a linking ticket is generated.
type UnlinkAccountByIDInput ¶
type UnlinkAccountByIDInput struct {
// UserID identifies the target canonical Core User.
UserID string `json:"user_id"`
// AccountID identifies the specific Core Account record to delete.
AccountID string `json:"account_id"`
// AllowOrphan overrides the lockout protection, permitting removal of the user's sole remaining auth method.
AllowOrphan bool `json:"allow_orphan,omitempty"`
}
UnlinkAccountByIDInput specifies the parameters to unlink an authentication account directly by Core Account ID.
type UnlinkAccountInput ¶
type UnlinkAccountInput struct {
// UserID identifies the target canonical Core User.
UserID string `json:"user_id"`
// Provider identifies the authentication provider to unlink.
Provider string `json:"provider"`
// ProviderID identifies the account to unlink within the provider.
ProviderID string `json:"provider_id"`
// AllowOrphan overrides the lockout protection, permitting removal of the user's sole remaining auth method.
AllowOrphan bool `json:"allow_orphan,omitempty"`
}
UnlinkAccountInput specifies the parameters to unlink an authentication account by provider name and identifier.
type UnlinkFailedPayload ¶
type UnlinkFailedPayload struct {
UserID string `json:"user_id"`
AccountID string `json:"account_id,omitempty"`
Provider string `json:"provider,omitempty"`
ProviderID string `json:"provider_id,omitempty"`
Reason string `json:"reason"`
ActorSubject string `json:"actor_subject,omitempty"`
Timestamp time.Time `json:"timestamp"`
}
UnlinkFailedPayload carries audit details when an unlink operation fails.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package migrations embeds SQL migrations for the Account Linking plugin.
|
Package migrations embeds SQL migrations for the Account Linking plugin. |