signup

package
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package signup provides user signup and personal organization creation.

Index

Constants

View Source
const (
	ProviderGitHub = "github"
	ProviderGoogle = "google"
)

Login providers whose handle rules SlugForLogin knows.

Variables

View Source
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.

View Source
var ErrEmailTaken = errors.New("signup: email already has an account")

ErrEmailTaken is returned when signing up with an email that already has an account.

View Source
var ErrInvalidEmail = errors.New("signup: invalid email address")

ErrInvalidEmail is returned for a signup email that is not a plain address.

View Source
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

func SlugForLogin(provider, providerLogin, email string) string

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

func SlugForManaged(entered string) string

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

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.

func NewService

func NewService(
	client *ent.Client,
	principalSvc principal.Service,
	orgSvc organization.Service,
	inviteSvc invite.Service,
) Service

NewService creates a new signup service.

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.

Jump to

Keyboard shortcuts

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