team

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package team implements team/organization management: creating teams, managing membership and roles, and inviting new members by email. It has no notion of HTTP or a specific database — persistence is defined by the store interfaces it depends on.

Authorization is the caller's responsibility: methods that change roles or membership do not themselves check the caller's role. A host application checks (e.g.) "is the caller an owner or admin of this team?" before calling UpdateMemberRole or RemoveMember, typically using the caller's own Member record fetched via GetMemberByUserAndTeam.

What this package is not

This is multi-tenancy, not an authorization model, and the difference matters most in one place: a role here is always a role *in a team*. An identity that spans teams has no home in this model, and shouldn't be given one — build it in your own schema, joined to authit by user id, and keep using authit for authentication.

The tell that you are about to fight the model: you find yourself inventing a team that every privileged user joins, or writing a membership row per team to express one global capability. Both fall over the moment such a principal must reach a team it holds no membership in at all. A flag or a table of your own, checked by your own code before you call in here, is the shape that keeps working.

Composition is expected, not exceptional: one account is routinely both a cross-team principal in your model and an ordinary Member of some team here, and the two answer different questions about it.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvitationInvalid  = errors.New("authit/team: invitation invalid, expired, or already used")
	ErrEmailMismatch      = errors.New("authit/team: invitation email does not match")
	ErrLastOwner          = errors.New("authit/team: team must have at least one owner")
	ErrNotOwner           = errors.New("authit/team: caller is not an owner")
	ErrMemberNotFound     = errors.New("authit/team: member not found")
	ErrSlugTaken          = errors.New("authit/team: slug already in use")
	ErrMembershipRejected = errors.New("authit/team: membership rejected")
)

Functions

This section is empty.

Types

type Admission

type Admission interface {
	AdmitMember(ctx context.Context, teamID string, currentCount int) error
}

Admission is consulted before a new member is admitted to a team, either by direct creation or invitation acceptance. It lets a host application enforce a seat limit (e.g. from its billing plan) without team needing to know anything about billing. The default, NopAdmission, admits everyone.

type Config

type Config struct {
	// InvitationTTL is how long an invitation stays valid. Defaults to 7
	// days.
	InvitationTTL time.Duration
	// AuditLogger receives security-relevant events (membership and role
	// changes, invitations). Nil means events are not recorded — see
	// package audit.
	AuditLogger audit.Logger
}

Config tunes the team package's flows.

type NopAdmission

type NopAdmission struct{}

NopAdmission admits every member unconditionally.

func (NopAdmission) AdmitMember

func (NopAdmission) AdmitMember(context.Context, string, int) error

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service implements team/membership/invitation flows.

func NewService

func NewService(stores Stores, admission Admission, cfg Config) (*Service, error)

NewService constructs a Service. admission may be nil, in which case NopAdmission is used. Config.AuditLogger may be nil, in which case audit.NoopLogger is used.

func (*Service) AcceptInvitation

func (s *Service) AcceptInvitation(ctx context.Context, rawToken, userID, email, displayName string) (store.Member, error)

AcceptInvitation consumes an invitation and creates a Member linking userID to the invitation's team with the invited role. email must match the address the invitation was sent to.

func (*Service) CreateInvitation

func (s *Service) CreateInvitation(ctx context.Context, teamID, invitedByMemberID, email string, role store.Role) (string, store.Invitation, error)

CreateInvitation invites email to join teamID with the given role, consulting Admission first (e.g. to enforce a seat limit). Returns the raw invitation token — hand it to the invitee (typically embedded in an emailed link); only its hash is persisted.

func (*Service) CreateTeam

func (s *Service) CreateTeam(ctx context.Context, name, slug, ownerUserID, ownerDisplayName, ownerEmail string) (store.Team, error)

CreateTeam creates a new team owned by ownerUserID and creates the corresponding owner Member record.

func (*Service) GetInvitationByToken

func (s *Service) GetInvitationByToken(ctx context.Context, rawToken string) (store.Invitation, error)

GetInvitationByToken looks up an invitation by its raw token without consuming it, for a "validate before showing the accept form" step. It returns ErrInvitationInvalid if the token is unknown, expired, or not pending.

func (*Service) GetMember

func (s *Service) GetMember(ctx context.Context, id string) (store.Member, error)

GetMember looks up a member by ID.

func (*Service) GetMemberByUserAndTeam

func (s *Service) GetMemberByUserAndTeam(ctx context.Context, userID, teamID string) (store.Member, error)

GetMemberByUserAndTeam looks up a user's membership within a specific team — typically used by a host application to resolve the caller's role for an authorization check.

func (*Service) GetTeam

func (s *Service) GetTeam(ctx context.Context, id string) (store.Team, error)

GetTeam looks up a team by ID.

func (*Service) GetTeamBySlug

func (s *Service) GetTeamBySlug(ctx context.Context, slug string) (store.Team, error)

GetTeamBySlug looks up a team by its slug.

func (*Service) ListInvitationsByTeam

func (s *Service) ListInvitationsByTeam(ctx context.Context, teamID string) ([]store.Invitation, error)

ListInvitationsByTeam lists every invitation for a team (pending, accepted, and revoked — the caller can filter by Status if needed).

func (*Service) ListMembersByTeam

func (s *Service) ListMembersByTeam(ctx context.Context, teamID string) ([]store.Member, error)

ListMembersByTeam lists every member of a team.

func (*Service) ListMembershipsByUser

func (s *Service) ListMembershipsByUser(ctx context.Context, userID string) ([]store.Member, error)

ListMembershipsByUser lists every team a user belongs to — used to drive a multi-team login/team-selection step.

func (*Service) RemoveMember

func (s *Service) RemoveMember(ctx context.Context, memberID string) error

RemoveMember permanently removes a member from a team. It refuses to remove the last remaining owner.

func (*Service) RevokeInvitation

func (s *Service) RevokeInvitation(ctx context.Context, invitationID string) error

RevokeInvitation marks a pending invitation revoked so its token can no longer be accepted.

func (*Service) SetMemberActive

func (s *Service) SetMemberActive(ctx context.Context, memberID string, active bool) error

SetMemberActive activates or deactivates a member (soft removal).

func (*Service) UpdateMemberRole

func (s *Service) UpdateMemberRole(ctx context.Context, memberID string, role store.Role) error

UpdateMemberRole changes a member's role. It refuses to demote the last remaining owner of a team, since that would leave the team unownable.

type Stores

type Stores struct {
	Teams       store.TeamStore
	Members     store.MemberStore
	Invitations store.InvitationStore
}

Stores groups the persistence ports the team package needs.

Jump to

Keyboard shortcuts

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