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 ¶
- Variables
- type Admission
- type Config
- type NopAdmission
- type Service
- func (s *Service) AcceptInvitation(ctx context.Context, rawToken, userID, email, displayName string) (store.Member, error)
- func (s *Service) CreateInvitation(ctx context.Context, teamID, invitedByMemberID, email string, role store.Role) (string, store.Invitation, error)
- func (s *Service) CreateTeam(ctx context.Context, ...) (store.Team, error)
- func (s *Service) GetInvitationByToken(ctx context.Context, rawToken string) (store.Invitation, error)
- func (s *Service) GetMember(ctx context.Context, id string) (store.Member, error)
- func (s *Service) GetMemberByUserAndTeam(ctx context.Context, userID, teamID string) (store.Member, error)
- func (s *Service) GetTeam(ctx context.Context, id string) (store.Team, error)
- func (s *Service) GetTeamBySlug(ctx context.Context, slug string) (store.Team, error)
- func (s *Service) ListInvitationsByTeam(ctx context.Context, teamID string) ([]store.Invitation, error)
- func (s *Service) ListMembersByTeam(ctx context.Context, teamID string) ([]store.Member, error)
- func (s *Service) ListMembershipsByUser(ctx context.Context, userID string) ([]store.Member, error)
- func (s *Service) RemoveMember(ctx context.Context, memberID string) error
- func (s *Service) RevokeInvitation(ctx context.Context, invitationID string) error
- func (s *Service) SetMemberActive(ctx context.Context, memberID string, active bool) error
- func (s *Service) UpdateMemberRole(ctx context.Context, memberID string, role store.Role) error
- type Stores
Constants ¶
This section is empty.
Variables ¶
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 ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements team/membership/invitation flows.
func NewService ¶
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) 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) GetTeamBySlug ¶
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 ¶
ListMembersByTeam lists every member of a team.
func (*Service) ListMembershipsByUser ¶
ListMembershipsByUser lists every team a user belongs to — used to drive a multi-team login/team-selection step.
func (*Service) RemoveMember ¶
RemoveMember permanently removes a member from a team. It refuses to remove the last remaining owner.
func (*Service) RevokeInvitation ¶
RevokeInvitation marks a pending invitation revoked so its token can no longer be accepted.
func (*Service) SetMemberActive ¶
SetMemberActive activates or deactivates a member (soft removal).
type Stores ¶
type Stores struct {
Teams store.TeamStore
Members store.MemberStore
Invitations store.InvitationStore
}
Stores groups the persistence ports the team package needs.