Documentation
¶
Overview ¶
Package invite provides organization invitation management.
Index ¶
- Constants
- Variables
- type API
- type APIConfig
- type AcceptInviteInput
- type AcceptInviteRequest
- type AcceptInviteResponse
- type CreateInviteInput
- type CreateInviteRequest
- type CreateInviteResponse
- type DeclineInviteRequest
- type DeclineInviteResponse
- type DefaultService
- func (s *DefaultService) Accept(ctx context.Context, input AcceptInviteInput) (*Invite, error)
- func (s *DefaultService) Create(ctx context.Context, input CreateInviteInput) (*InviteResult, error)
- func (s *DefaultService) Decline(ctx context.Context, token string) (*Invite, error)
- func (s *DefaultService) ExpireOld(ctx context.Context) (int, error)
- func (s *DefaultService) GetByID(ctx context.Context, id uuid.UUID) (*Invite, error)
- func (s *DefaultService) GetByToken(ctx context.Context, token string) (*Invite, error)
- func (s *DefaultService) HasPendingInvite(ctx context.Context, organizationID uuid.UUID, email string) (bool, error)
- func (s *DefaultService) List(ctx context.Context, input ListInvitesInput) ([]*Invite, error)
- func (s *DefaultService) ListPendingForEmail(ctx context.Context, email string) ([]*Invite, error)
- func (s *DefaultService) PrincipalEmail(ctx context.Context, principalID uuid.UUID) (string, error)
- func (s *DefaultService) Resend(ctx context.Context, id uuid.UUID) (*InviteResult, error)
- func (s *DefaultService) Revoke(ctx context.Context, id uuid.UUID) error
- type GetInviteByTokenRequest
- type GetInviteByTokenResponse
- type Invite
- type InviteResponse
- type InviteResult
- type InviteResultResponse
- type ListInvitesInput
- type ListInvitesRequest
- type ListInvitesResponse
- type ListPendingInvitesForEmailRequest
- type ListPendingInvitesForEmailResponse
- type PrincipalEmailFunc
- type ResendInviteRequest
- type ResendInviteResponse
- type RevokeInviteRequest
- type Service
- type ServiceOption
- func WithAppRoles(svc *approle.Service) ServiceOption
- func WithAuthorizer(a authz.Authorizer) ServiceOption
- func WithAuthzSyncer(syncer authz.RelationshipSyncer) ServiceOption
- func WithLogger(logger *slog.Logger) ServiceOption
- func WithResourceGrants(svc *resourcegrant.Service) ServiceOption
- func WithSyncMode(mode authz.SyncMode) ServiceOption
- type Status
Constants ¶
const DefaultInviteExpiry = 7 * 24 * time.Hour // 7 days
Default invite expiration time.
Variables ¶
var ErrAppRoleRequired = errors.New("invite: choose an application role for at least one entitled application")
ErrAppRoleRequired means an admin or member invitation chose no application role although the organization is entitled to applications (ADR-007: nobody gets into an application by being a member alone, and an invitation must open at least one).
var ErrExpired = errors.New("invite has expired")
ErrExpired means an invite passed its expiration time.
var ErrInviterNotAuthorized = errors.New("invite: the inviter may no longer invite to this organization")
ErrInviterNotAuthorized means the principal who created an invite may no longer invite to the organization, or no longer to the invite's role (it was removed, deactivated or demoted), so the invite cannot be accepted: an invitation carries its inviter's current authority, not the authority it had when the invite was sent.
var ErrNotPending = errors.New("invite is no longer pending")
ErrNotPending means an invite is no longer pending: it was accepted, declined, revoked or expired, possibly by a concurrent request.
var ErrResourceInvite = errors.New("invite: invalid resource target")
ErrResourceInvite means an invitation's resource target is incomplete, is not for a collaborator, or cannot be honored by the service.
Functions ¶
This section is empty.
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API provides HTTP handlers for Invite management.
type APIConfig ¶
type APIConfig struct {
// BasePath is the base path for all endpoints (default: "/api/v1").
BasePath string
// Logger is the logger to use (default: slog.Default()).
Logger *slog.Logger
// Authorizer checks the caller's organization permissions; managing
// an organization's invites needs authz.PermOrgInviteMember. Until it
// is set, those endpoints answer 501.
Authorizer authz.Authorizer
// Caller identifies the authenticated principal (default:
// requestprincipal.PrincipalUUID, set by requestprincipal.Middleware).
Caller func(ctx context.Context) (uuid.UUID, error)
// PrincipalEmail returns the caller's email, which an invite must be
// addressed to (default: the service's PrincipalEmail, when it has
// one, as DefaultService does).
PrincipalEmail PrincipalEmailFunc
}
APIConfig configures the Invite API.
func DefaultAPIConfig ¶
func DefaultAPIConfig() APIConfig
DefaultAPIConfig returns the default API configuration.
type AcceptInviteInput ¶
type AcceptInviteInput struct {
Token string
PrincipalID uuid.UUID // The principal accepting the invite
}
AcceptInviteInput contains input for accepting an invite.
type AcceptInviteRequest ¶
type AcceptInviteRequest struct {
Token string `path:"token"`
Body struct {
PrincipalID string `` /* 169-byte string literal not displayed */
}
}
AcceptInviteRequest is the request for accepting an invite.
type AcceptInviteResponse ¶
type AcceptInviteResponse struct {
Body *InviteResponse
}
AcceptInviteResponse is the response for accepting an invite.
type CreateInviteInput ¶
type CreateInviteInput struct {
OrganizationID uuid.UUID
InviterPrincipalID uuid.UUID
Email string
Role string
// AppRoles are the application roles to grant on acceptance:
// application identifier to role. None is preselected; an inviter
// chooses each. With an approle service configured (WithAppRoles),
// an admin or member invitation to an organization entitled to any
// application must name at least one (ErrAppRoleRequired).
AppRoles map[string]string
// ResourceType, ResourceID and ResourceRole target the invite at one
// resource (ADR-008); set all three, or none. They require Role to be
// "collaborator" and the service to be built with WithResourceGrants.
// A personal organization accepts only resource-targeted invites.
ResourceType string
ResourceID string
ResourceRole string
Message *string
ExpiresIn time.Duration // How long until expiry (default: 7 days)
}
CreateInviteInput contains input for creating an invite.
type CreateInviteRequest ¶
type CreateInviteRequest struct {
Slug string `path:"slug"`
Body struct {
Email string `json:"email" required:"true" format:"email"`
Role string `json:"role" required:"true" enum:"owner,admin,member,collaborator"`
AppRoles map[string]string `` /* 126-byte string literal not displayed */
ResourceType string `` /* 179-byte string literal not displayed */
ResourceID string `json:"resource_id,omitempty" doc:"The application's identifier of the resource"`
ResourceRole string `json:"resource_role,omitempty" enum:"viewer,contributor,maintainer" doc:"The resource role granted on acceptance"`
Message *string `json:"message,omitempty"`
InviterPrincipalID string `` /* 165-byte string literal not displayed */
ExpiresInHours int `json:"expires_in_hours,omitempty" minimum:"1" maximum:"720"`
}
}
CreateInviteRequest is the request for creating an invite.
type CreateInviteResponse ¶
type CreateInviteResponse struct {
Body *InviteResultResponse
}
CreateInviteResponse is the response for creating an invite.
type DeclineInviteRequest ¶
type DeclineInviteRequest struct {
Token string `path:"token"`
}
DeclineInviteRequest is the request for declining an invite.
type DeclineInviteResponse ¶
type DeclineInviteResponse struct {
Body *InviteResponse
}
DeclineInviteResponse is the response for declining an invite.
type DefaultService ¶
type DefaultService struct {
// contains filtered or unexported fields
}
DefaultService implements the Service interface.
func (*DefaultService) Accept ¶
func (s *DefaultService) Accept(ctx context.Context, input AcceptInviteInput) (*Invite, error)
Accept accepts an invite and creates the membership.
The invite must still be pending and unexpired when the acceptance is written: the status change is a conditional update in the same transaction as the membership, so an invite revoked or declined while it is being accepted is not accepted. The inviter must still be allowed to invite to the organization, and to invite an owner when the invite is for one; otherwise Accept returns ErrInviterNotAuthorized.
func (*DefaultService) Create ¶
func (s *DefaultService) Create(ctx context.Context, input CreateInviteInput) (*InviteResult, error)
Create creates a new invite.
func (*DefaultService) ExpireOld ¶
func (s *DefaultService) ExpireOld(ctx context.Context) (int, error)
ExpireOld expires invites that have passed their expiration time.
func (*DefaultService) GetByToken ¶
GetByToken retrieves an invite by its token.
func (*DefaultService) HasPendingInvite ¶
func (s *DefaultService) HasPendingInvite(ctx context.Context, organizationID uuid.UUID, email string) (bool, error)
HasPendingInvite checks if there's already a pending invite for this email/org.
func (*DefaultService) List ¶
func (s *DefaultService) List(ctx context.Context, input ListInvitesInput) ([]*Invite, error)
List lists invites with optional filters.
func (*DefaultService) ListPendingForEmail ¶
ListPendingForEmail lists pending invites for an email address.
func (*DefaultService) PrincipalEmail ¶ added in v0.14.0
PrincipalEmail returns a principal's email from the identity database, or "" when it has none or its email is not verified (email_verified_at unset). It is the API's default PrincipalEmailFunc: an address anyone can claim when creating a principal (a managed principal, an unverified signup or SCIM provisioning) must not list or answer the invitations sent to it.
func (*DefaultService) Resend ¶
func (s *DefaultService) Resend(ctx context.Context, id uuid.UUID) (*InviteResult, error)
Resend resends an invite with a new token.
type GetInviteByTokenRequest ¶
type GetInviteByTokenRequest struct {
Token string `path:"token"`
}
GetInviteByTokenRequest is the request for getting an invite by token.
type GetInviteByTokenResponse ¶
type GetInviteByTokenResponse struct {
Body *InviteResponse
}
GetInviteByTokenResponse is the response for getting an invite.
type Invite ¶
type Invite struct {
ID uuid.UUID
OrganizationID uuid.UUID
InviterPrincipalID uuid.UUID
Email string
Role string
// AppRoles are the application roles granted when the invite is
// accepted: application identifier to role. Empty means none.
AppRoles map[string]string
// ResourceType, ResourceID and ResourceRole, all set or all empty,
// target the invite at one resource (ADR-008): accepting it makes the
// invitee a collaborator of the organization and grants ResourceRole on
// the resource in one transaction. Role is then "collaborator".
ResourceType string
ResourceID string
ResourceRole string
Status Status
Message *string
ExpiresAt time.Time
AcceptedAt *time.Time
AcceptedByPrincipalID *uuid.UUID
ResendCount int
LastSentAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
Invite represents an invitation to join an organization.
type InviteResponse ¶
type InviteResponse struct {
ID string `json:"id"`
OrganizationID string `json:"organization_id"`
InviterPrincipalID string `json:"inviter_principal_id"`
Email string `json:"email"`
Role string `json:"role"`
AppRoles map[string]string `json:"app_roles,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
ResourceID string `json:"resource_id,omitempty"`
ResourceRole string `json:"resource_role,omitempty"`
Status string `json:"status"`
Message *string `json:"message,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
AcceptedAt *time.Time `json:"accepted_at,omitempty"`
AcceptedByPrincipalID *string `json:"accepted_by_principal_id,omitempty"`
ResendCount int `json:"resend_count"`
LastSentAt time.Time `json:"last_sent_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
InviteResponse is the API representation of an invite.
type InviteResult ¶
type InviteResult struct {
Invite *Invite
// InviteURL is the full URL to accept the invite
InviteURL string
}
InviteResult contains the result of invite operations.
type InviteResultResponse ¶
type InviteResultResponse struct {
Invite *InviteResponse `json:"invite"`
InviteURL string `json:"invite_url"`
}
InviteResultResponse includes the invite URL.
type ListInvitesInput ¶
type ListInvitesInput struct {
OrganizationID *uuid.UUID
Email *string
Status *Status
Limit int
Offset int
}
ListInvitesInput contains filters for listing invites.
type ListInvitesRequest ¶
type ListInvitesRequest struct {
Slug string `path:"slug"`
// Huma does not support pointer parameters; empty lists every status.
Status string `query:"status" enum:"pending,accepted,declined,expired,revoked"`
Limit int `query:"limit" default:"20" minimum:"1" maximum:"100"`
Offset int `query:"offset" default:"0" minimum:"0"`
}
ListInvitesRequest is the request for listing invites.
type ListInvitesResponse ¶
type ListInvitesResponse struct {
Body struct {
Invites []*InviteResponse `json:"invites"`
}
}
ListInvitesResponse is the response for listing invites.
type ListPendingInvitesForEmailRequest ¶
type ListPendingInvitesForEmailRequest struct {
// Email is deprecated: the invites listed are the caller's. When
// present it must be the caller's email.
Email string `` /* 130-byte string literal not displayed */
}
ListPendingInvitesForEmailRequest is the request for listing pending invites by email.
type ListPendingInvitesForEmailResponse ¶
type ListPendingInvitesForEmailResponse struct {
Body struct {
Invites []*InviteResponse `json:"invites"`
}
}
ListPendingInvitesForEmailResponse is the response for listing pending invites.
type PrincipalEmailFunc ¶ added in v0.14.0
PrincipalEmailFunc returns a principal's verified email, or "" when it has none (a managed principal) or has not verified it. The invite API treats the address it returns as proof that the caller owns it, so an implementation must not return an address the principal merely claimed.
type ResendInviteRequest ¶
type ResendInviteRequest struct {
Slug string `path:"slug"`
InviteID string `path:"invite_id" format:"uuid"`
}
ResendInviteRequest is the request for resending an invite.
type ResendInviteResponse ¶
type ResendInviteResponse struct {
Body *InviteResultResponse
}
ResendInviteResponse is the response for resending an invite.
type RevokeInviteRequest ¶
type RevokeInviteRequest struct {
Slug string `path:"slug"`
InviteID string `path:"invite_id" format:"uuid"`
}
RevokeInviteRequest is the request for revoking an invite.
type Service ¶
type Service interface {
// Create creates a new invite.
Create(ctx context.Context, input CreateInviteInput) (*InviteResult, error)
// GetByToken retrieves an invite by its token.
GetByToken(ctx context.Context, token string) (*Invite, error)
// GetByID retrieves an invite by ID.
GetByID(ctx context.Context, id uuid.UUID) (*Invite, error)
// Accept accepts an invite and creates the membership.
Accept(ctx context.Context, input AcceptInviteInput) (*Invite, error)
// Decline declines an invite.
Decline(ctx context.Context, token string) (*Invite, error)
// Revoke revokes an invite.
Revoke(ctx context.Context, id uuid.UUID) error
// Resend resends an invite with a new token.
Resend(ctx context.Context, id uuid.UUID) (*InviteResult, error)
// List lists invites with optional filters.
List(ctx context.Context, input ListInvitesInput) ([]*Invite, error)
// ListPendingForEmail lists pending invites for an email address.
ListPendingForEmail(ctx context.Context, email string) ([]*Invite, error)
// ExpireOld expires invites that have passed their expiration time.
ExpireOld(ctx context.Context) (int, error)
// HasPendingInvite checks if there's already a pending invite for this email/org.
HasPendingInvite(ctx context.Context, organizationID uuid.UUID, email string) (bool, error)
}
Service defines the invite service interface.
func NewService ¶
func NewService(client *ent.Client, baseURL string, opts ...ServiceOption) Service
NewService creates a new invite service.
type ServiceOption ¶ added in v0.14.0
type ServiceOption func(*DefaultService)
ServiceOption configures a DefaultService.
func WithAppRoles ¶ added in v0.14.0
func WithAppRoles(svc *approle.Service) ServiceOption
WithAppRoles lets invitations carry application roles: Create validates them and Accept grants them in the same transaction as the membership. Without it, an invitation's AppRoles are rejected.
func WithAuthorizer ¶ added in v0.21.0
func WithAuthorizer(a authz.Authorizer) ServiceOption
WithAuthorizer makes Accept ask authorizer whether an invite's inviter still holds authz.PermOrgInviteMember on the organization (and the owner-only authz.PermOrgDelete for an owner invite), the permissions the API required to create it. Use it when invitations may be created by principals whose authority is not an organization membership, such as platform administrators. Without it, the inviter must hold an active membership whose role grants those permissions (authz.OrgRoleAllows).
func WithAuthzSyncer ¶ added in v0.14.0
func WithAuthzSyncer(syncer authz.RelationshipSyncer) ServiceOption
WithAuthzSyncer sets the authorization syncer used to register accepted memberships in the authorization graph.
func WithLogger ¶ added in v0.14.0
func WithLogger(logger *slog.Logger) ServiceOption
WithLogger sets the logger for the service.
func WithResourceGrants ¶ added in v0.14.0
func WithResourceGrants(svc *resourcegrant.Service) ServiceOption
WithResourceGrants lets invitations target a resource (ADR-008): Create validates the target against the grant service's rules and Accept creates the collaborator membership and the grant in one transaction, through the service's GrantInTx. Without it, a resource target is refused. Memberships are written to the authorization graph through this service's own syncer, grants through the grant service's.
func WithSyncMode ¶ added in v0.14.0
func WithSyncMode(mode authz.SyncMode) ServiceOption
WithSyncMode sets the sync mode (strict or eventual).