Documentation
¶
Overview ¶
Package signup provides user signup and personal organization creation.
Index ¶
- Constants
- Variables
- func SlugForLogin(provider, providerLogin, email string) string
- func SlugForManaged(entered string) string
- type AcceptInviteOnSignupInput
- type AcceptInviteOnSignupResult
- type DefaultService
- func (s *DefaultService) Signup(ctx context.Context, input SignupInput) (*SignupResult, error)
- func (s *DefaultService) SignupOrGetExisting(ctx context.Context, input SignupInput) (*SignupResult, bool, error)
- func (s *DefaultService) SignupWithInvite(ctx context.Context, input AcceptInviteOnSignupInput) (*AcceptInviteOnSignupResult, error)
- type PersonalOrg
- type PersonalOrgParams
- type Service
- type SignupInput
- type SignupResult
Constants ¶
const ( ProviderGitHub = "github" ProviderGoogle = "google" )
Login providers whose handle rules SlugForLogin knows.
Variables ¶
var ErrEmailNotVerified = errors.New("signup: email not verified; cannot sign in to the existing account")
ErrEmailNotVerified is returned by SignupOrGetExisting when an account exists for an email the caller has not verified.
var ErrEmailTaken = errors.New("signup: email already has an account")
ErrEmailTaken is returned when signing up with an email that already has an account.
var ErrInvalidEmail = errors.New("signup: invalid email address")
ErrInvalidEmail is returned for a signup email that is not a plain address.
var ErrInviteEmailMismatch = errors.New("signup: email does not match the invite")
ErrInviteEmailMismatch is returned when signing up with an invite under an email other than the one invited.
Functions ¶
func SlugForLogin ¶ added in v0.14.0
SlugForLogin derives the base personal-organization slug for a person who arrived through an upstream login (ADR-006 decision 2):
- github: the GitHub username (providerLogin);
- google: the local part of the email address, before "@";
- any other provider: providerLogin when set, else the email local part.
When the preferred source is empty the other one is used. The result is always a valid organization slug (3 to 63 characters, ^[a-z][a-z0-9-]*[a-z0-9]$), but it is only a base: it may already be taken, and the caller resolves a collision with a numeric suffix against the organization table (see CreatePersonalOrgInTx). Managed-principal and reserved-slug rules are not part of this function.
func SlugForManaged ¶ added in v0.14.0
SlugForManaged returns the base personal-organization slug for a managed principal (ADR-006 decision 2, ADR-009): the value its manager entered when there is one, else two words and a number such as "quiet-otter-42". A generated slug is random and never derived from an email address or any other fact about the person. Like SlugForLogin it is only a base; the caller resolves collisions and reserved names.
Types ¶
type AcceptInviteOnSignupInput ¶
type AcceptInviteOnSignupInput struct {
// SignupInput contains the user's signup details.
SignupInput
// InviteToken is the invitation token to accept.
InviteToken string
}
AcceptInviteOnSignupInput contains input for signing up while accepting an invite.
type AcceptInviteOnSignupResult ¶
type AcceptInviteOnSignupResult struct {
// SignupResult contains the user and personal org.
SignupResult
// InvitedOrganizationID is the organization from the invite.
InvitedOrganizationID uuid.UUID
// InvitedRole is the role granted by the invite.
InvitedRole string
}
AcceptInviteOnSignupResult contains the result of signing up with an invite.
type DefaultService ¶
type DefaultService struct {
// contains filtered or unexported fields
}
DefaultService implements the Service interface.
func (*DefaultService) Signup ¶
func (s *DefaultService) Signup(ctx context.Context, input SignupInput) (*SignupResult, error)
Signup creates a new user with their personal organization.
func (*DefaultService) SignupOrGetExisting ¶
func (s *DefaultService) SignupOrGetExisting(ctx context.Context, input SignupInput) (*SignupResult, bool, error)
SignupOrGetExisting handles OAuth login - gets existing user or creates new one.
func (*DefaultService) SignupWithInvite ¶
func (s *DefaultService) SignupWithInvite(ctx context.Context, input AcceptInviteOnSignupInput) (*AcceptInviteOnSignupResult, error)
SignupWithInvite creates a new user, their personal org, and accepts an invite.
type PersonalOrg ¶ added in v0.14.0
type PersonalOrg struct {
Organization *ent.Organization
Membership *ent.PrincipalMembership
}
PersonalOrg is a personal organization and its owner membership.
func CreatePersonalOrgInTx ¶ added in v0.14.0
func CreatePersonalOrgInTx(ctx context.Context, tx *ent.Tx, params PersonalOrgParams) (*PersonalOrg, error)
CreatePersonalOrgInTx creates a personal organization with an active owner membership inside tx, so a caller can make it atomic with the principal it belongs to. The caller commits or rolls back. It does not notify an authorization syncer: services that need that call organization.Service.CreatePersonalOrg instead.
The organization has type personal. Its slug is params.Slug if free, else params.Slug-1 ... -100, else the slug with an eight-character random suffix. Two transactions may still pick the same free slug; the loser fails with a constraint error and the caller retries the whole transaction.
func EnsurePersonalOrgInTx ¶ added in v0.14.0
func EnsurePersonalOrgInTx(ctx context.Context, tx *ent.Tx, params PersonalOrgParams) (*PersonalOrg, bool, error)
EnsurePersonalOrgInTx creates the principal's personal organization when it has none, and reports whether it did. It is the idempotent backfill for a principal that predates signup-on-first-login, and it is safe to call on every login: an existing personal organization is returned untouched.
The principal row is locked so two concurrent logins of the same existing principal do not both create an organization.
type PersonalOrgParams ¶ added in v0.14.0
type PersonalOrgParams struct {
// OwnerID is the owning principal; it becomes the active owner member.
OwnerID uuid.UUID
// Name is the organization's display name (required).
Name string
// Slug is the preferred slug. It is made valid and, when taken, given a
// numeric suffix. Use SlugForLogin to derive it.
Slug string
// ReservedSlugs are deployment-configured slugs to avoid in addition to
// organization.ReservedSlugs; a reserved slug gets a numeric suffix.
ReservedSlugs []string
// LogoURL is the optional logo, usually the owner's avatar.
LogoURL string
}
PersonalOrgParams describes a personal organization to create inside a caller-provided transaction.
type Service ¶
type Service interface {
// Signup creates a new user with their personal organization.
// This is the standard signup flow for new users.
Signup(ctx context.Context, input SignupInput) (*SignupResult, error)
// SignupWithInvite creates a new user, their personal org, and accepts an invite.
// This is used when a user signs up by accepting an invitation to an org.
SignupWithInvite(ctx context.Context, input AcceptInviteOnSignupInput) (*AcceptInviteOnSignupResult, error)
// SignupOrGetExisting handles the OAuth login flow:
// - If user exists, returns them and their personal org
// - If user doesn't exist, creates them with personal org
SignupOrGetExisting(ctx context.Context, input SignupInput) (*SignupResult, bool, error)
}
Service defines the signup service interface.
type SignupInput ¶
type SignupInput struct {
// Email is the user's email address (required).
Email string
// DisplayName is the user's display name (required).
DisplayName string
// GivenName is the user's first name (optional).
GivenName string
// FamilyName is the user's last name (optional).
FamilyName string
// AvatarURL is the user's avatar URL (optional, often from OAuth provider).
AvatarURL *string
// Locale is the user's preferred locale (optional).
Locale string
// Timezone is the user's preferred timezone (optional).
Timezone string
// PersonalOrgSlug is the URL-safe slug for the personal org (required).
// Usually derived from email or username.
PersonalOrgSlug string
// PersonalOrgName is the display name for the personal org (optional).
// Defaults to the user's DisplayName if not provided.
PersonalOrgName string
// Metadata contains optional metadata for the user.
Metadata map[string]any
// EmailVerified asserts that the caller verified Email belongs to the
// person signing up, for example an identity provider's verified
// flag. SignupOrGetExisting returns an existing account for an email
// only when it is set.
EmailVerified bool
}
SignupInput contains input for signing up a new user.
type SignupResult ¶
type SignupResult struct {
// Principal is the created human principal.
Principal *principal.Principal
// PersonalOrganization is the created personal organization.
PersonalOrganization *organization.Organization
// Membership is the owner membership in the personal organization.
Membership *organization.Membership
}
SignupResult contains the result of a signup operation.