teams

package
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package teams is a thin client for the Truestamp Teams + Memberships JSON:API surfaces (GET /api/json/teams, /api/json/memberships). It exposes the small subset of operations the CLI needs to discover a user's team memberships and validate that a configured team is accessible.

The membership read policy on the server side filters to the actor's own memberships, so `ListMyMemberships` returns "the teams I'm a member of" with no extra filtering. See truestamp-v2/lib/truestamp/teams/membership.ex policy block.

Index

Constants

View Source
const (
	OwnershipCreatorRetains = "creator_retains"
	OwnershipTeamRetains    = "team_retains"
)

Ownership models accepted by CreateTeam. creator_retains is the free-tier-compatible default; team_retains requires a plan entitlement (the team, not the creator, retains ownership across membership changes).

Variables

View Source
var (
	ErrUnauthorized = jsonapi.ErrUnauthorized
	ErrForbidden    = jsonapi.ErrForbidden
	ErrNotFound     = jsonapi.ErrNotFound
	ErrBadRequest   = jsonapi.ErrBadRequest
	ErrRateLimited  = jsonapi.ErrRateLimited
	ErrServer       = jsonapi.ErrServer
)
View Source
var (
	// ErrTeamLimitReached is returned by CreateTeam when the actor's plan
	// team quota is exhausted (a distinct, user-actionable case: upgrade the
	// plan). Identified structurally, not by error code: the server mints
	// none for :create, so a pointer-less 4xx whose detail names the team
	// limit marks this case (see mapCreateError / mentionsTeamLimit).
	ErrTeamLimitReached = errors.New("team limit reached")
	// ErrOwnershipNotEntitled is returned by CreateTeam when the requested
	// ownership_model (e.g. team_retains) requires a plan entitlement the
	// actor lacks, distinct from the team-count limit.
	ErrOwnershipNotEntitled = errors.New("ownership model not entitled")
)

Functions

func FormatRole

func FormatRole(role string) string

FormatRole returns a human-friendly title-cased role label suitable for table cells: "team_owner" -> "Owner", "team_admin" -> "Admin", "team_member" -> "Member", "team_viewer" -> "Viewer". Unknown inputs pass through unchanged so a future server-side role addition shows up verbatim instead of being silently dropped.

func GetMyRoleOnTeam

func GetMyRoleOnTeam(ctx context.Context, cfg Config, teamID string) (string, error)

GetMyRoleOnTeam returns the role string for the authenticated actor on the given team, or the empty string if the actor has no membership (without distinguishing the "no membership" case from "lookup failed"). Callers that need the failure distinction should call ListMyMemberships and search the slice.

func OwnershipDescription added in v0.10.0

func OwnershipDescription(model string) string

OwnershipDescription returns a one-line explanation of an ownership model for help text and form field descriptions.

func OwnershipLabel added in v0.10.0

func OwnershipLabel(model string) string

OwnershipLabel returns a short, title-cased label for an ownership model, suitable for selects and cards. Unknown values pass through unchanged.

func OwnershipModels added in v0.10.0

func OwnershipModels() []string

OwnershipModels returns the ownership models a client may offer, in display order. Both surfaces (the `teams create` subcommand and the console modal) build their pickers from this single source so a future model is added once.

func PrivilegeRank

func PrivilegeRank(role string) int

PrivilegeRank returns a sort key for the given role. Lower values rank higher (Owner = 0, Viewer = 3). Unknown roles sort last so a future server-side addition isn't silently grouped with viewers.

Types

type APIError

type APIError = jsonapi.APIError

Errors surfaced by the client. CLI layers may errors.Is these to pick an exit code and user-facing message.

ErrUnauthorized covers 401 (the credential was rejected, a dead or expired OAuth session, or an invalid API key). ErrForbidden covers 403, auth was accepted but the actor isn't allowed to read this resource (typically: the tenant header points to a team the actor isn't a member of). Distinguishing the two matters because the user-facing remediation is completely different: 401 → run `auth login`; 403 → check the team id, ask for membership. The transport, the class sentinels and APIError live in internal/jsonapi; these aliases keep this client's surface stable for the commands that errors.Is its classes.

type Config

type Config = jsonapi.Config

Errors surfaced by the client. CLI layers may errors.Is these to pick an exit code and user-facing message.

ErrUnauthorized covers 401 (the credential was rejected, a dead or expired OAuth session, or an invalid API key). ErrForbidden covers 403, auth was accepted but the actor isn't allowed to read this resource (typically: the tenant header points to a team the actor isn't a member of). Distinguishing the two matters because the user-facing remediation is completely different: 401 → run `auth login`; 403 → check the team id, ask for membership. The transport, the class sentinels and APIError live in internal/jsonapi; these aliases keep this client's surface stable for the commands that errors.Is its classes.

type Membership

type Membership struct {
	ID     string `json:"id"`
	TeamID string `json:"team_id"`
	Role   string `json:"role"` // "team_owner" | "team_admin" | "team_member" | "team_viewer"
	Team   *Team  `json:"team,omitempty"`
}

Membership pairs a user's role with the team it applies to. Team is populated from a parallel `GET /teams` call, joined client-side (see ListMyMemberships). JSON tags are snake_case to match the shape of every other --json surface in the CLI; without explicit tags the Go field names ("ID", "TeamID") would leak.

func ListMyMemberships

func ListMyMemberships(ctx context.Context, cfg Config) ([]Membership, error)

ListMyMemberships returns one Membership row per team the authenticated actor has access to, with that actor's role on the team.

The implementation is keyed on `GET /teams` (source of truth for "teams I can read", per the server-side `relates_to_actor_via(:members)` READ policy) rather than `/memberships` because:

  1. `/memberships` can return rows whose team_id no longer exists (orphaned dev seed data, mid-cascade-delete races).
  2. Under admin bypass policies, `/memberships` may return memberships from other users that the actor doesn't actually hold. Keying on the team list ensures every returned row represents a team the actor can actually read.
  3. `?include=team` on the memberships endpoint is unreliable, the included array is sparse for reasons we haven't fully diagnosed (possibly Ash's per-response include limits or relationship-load authorization filtering).

Roles are joined from a parallel `/memberships` request and deduplicated by team_id (when multiple memberships exist for the same team, e.g. different users on the same team under admin bypass, we take the first match deterministically).

Both endpoints walk `links.next` so users with many memberships or teams aren't silently truncated by the server's default page size.

type Team

type Team struct {
	ID             string `json:"id"`
	Name           string `json:"name"`
	Personal       bool   `json:"personal"`
	OwnershipModel string `json:"ownership_model"`
	CreatedAt      string `json:"inserted_at"`
}

Team is the subset of a JSON:API team resource the CLI consumes.

func CreateTeam added in v0.10.0

func CreateTeam(ctx context.Context, cfg Config, name, ownershipModel string) (*Team, error)

CreateTeam creates a new (non-personal) team owned by the authenticated actor. ownershipModel is OwnershipCreatorRetains or OwnershipTeamRetains (the latter requires a plan entitlement); an empty string lets the server apply its default. The server derives the creator from the actor and auto-creates the owner Membership, so neither is sent.

The tenant header is cleared because creating a team is an account-level action, not an operation within an existing team context. On a plan-limit rejection the error satisfies errors.Is(err, ErrTeamLimitReached); on an ownership-entitlement rejection, errors.Is(err, ErrOwnershipNotEntitled).

func GetTeam

func GetTeam(ctx context.Context, cfg Config, id string) (*Team, error)

GetTeam fetches a single team by id. Useful for `truestamp teams use <id>` validation: a 2xx confirms the user can read it (which by policy means they have a membership), 4xx surfaces the JSON:API detail string.

Jump to

Keyboard shortcuts

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