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 ¶
This section is empty.
Variables ¶
var ( ErrForbidden = errors.New("forbidden") ErrNotFound = errors.New("not found") ErrBadRequest = errors.New("bad request") ErrRateLimited = errors.New("rate limited") ErrServer = errors.New("server error") )
Errors surfaced by the client. CLI layers may errors.Is these to pick an exit code and user-facing message.
ErrUnauthorized covers 401 (the API key itself is invalid or expired). 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.
Functions ¶
func FormatRole ¶
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 ¶
GetMyRoleOnTeam returns the role string for the API key's user on the given team, or the empty string if the user 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 PrivilegeRank ¶
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 struct {
Status int
Detail string // preferred; falls back to Title
RetryAfter string // verbatim Retry-After header on 429
// contains filtered or unexported fields
}
APIError carries HTTP status + preserved `errors[].detail` from the JSON:API envelope for display to the user. Wraps one of the sentinel errors above so callers can errors.Is() the class while still showing the detail text.
type Config ¶
type Config struct {
APIURL string // e.g. https://www.truestamp.com/api/json
APIKey string // Bearer token (never logged)
Team string // optional tenant id; sent verbatim as the `tenant` header
}
Config carries the subset of runtime configuration needed for a request. Kept small to avoid importing the top-level config package.
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 API key's user has access to, with the user's role on that team.
The implementation anchors 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:
- `/memberships` can return rows whose team_id no longer exists (orphaned dev seed data, mid-cascade-delete races).
- Under admin bypass policies, `/memberships` may return memberships from other users that the actor doesn't actually hold. Anchoring on the team list ensures every returned row represents a team the actor can actually read.
- `?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.