authplatform

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: MIT Imports: 12 Imported by: 0

README

togo

togo-framework/auth-platform

marketplace pkg.go.dev MIT

Organizations & teams for togo — multi-tenant auth with per-org roles, invites & branding.

Install

togo install togo-framework/auth-platform

auth-platform adds the organization / team layer on top of the togo auth plugin — what Fort calls platforms and Laravel Jetstream calls teams. Users join orgs as members with a per-org role, are added by email invite, and every request is scoped to a current org (resolved from a header, subdomain, or claim). Each org carries its own settings and branding. It composes with auth but works standalone.

Usage

import authplatform "github.com/togo-framework/auth-platform"

s, _ := authplatform.FromKernel(k)

// Create an org (the creator becomes the owner).
org, _ := s.CreateOrg("Acme Inc", "", ownerID)

// Invite by email, accept by token.
inv, _ := s.Invite(org.ID, "jane@acme.com", authplatform.RoleAdmin)
s.Accept(inv.Token, janeUserID)

// Roles & gating.
s.HasRole(org.ID, janeUserID, authplatform.RoleAdmin) // true
s.SetRole(org.ID, janeUserID, authplatform.RoleMember)

// Org switcher + per-org settings/branding.
orgs := s.OrgsForUser(userID)
s.SetSetting(org.ID, "feature.beta", true)
s.SetBranding(org.ID, authplatform.Branding{PrimaryColor: "#2C7BE2", LogoURL: "/logo.svg"})

Request scoping

// Resolve the current org from X-Org-Id / ?org= / subdomain, then read it anywhere.
router.Use(s.ResolveOrg)
orgID := authplatform.OrgID(ctx)
org, _ := s.CurrentOrg(ctx)

// Gate a route by org role (403 otherwise).
router.With(s.RequireOrgRole(authplatform.RoleAdmin)).Post("/api/billing", handler)

Roles

owner > admin > member (ranked — RequireOrgRole(admin) is satisfied by owners). Custom role strings are allowed and matched by exact name.

REST API

Method Path Purpose
GET /api/orgs orgs the current user belongs to (switcher)
POST /api/orgs create an org (creator = owner)
GET/PATCH/DELETE /api/orgs/{id} read / update branding+settings / delete
GET /api/orgs/{id}/members list members
POST /api/orgs/{id}/invites invite by email + role
POST /api/org-invites/accept accept an invite token

The current user is read from the auth context (or X-User-Id for standalone use).

Configuration

No required env. Data is held in a bounded in-memory store behind a small interface — back it with a database for persistence in production.


Premium sponsors

ID8 Media  ·  One Studio

Support togo — become a sponsor.

Documentation

Overview

Package authplatform adds organizations / teams (multi-tenancy) on top of the togo auth plugin — the layer Fort calls "platforms" and Laravel calls "teams".

An Org groups users; each user is a Member with a per-org Role (owner / admin / member or a custom role). Users are invited by email and accept via a token. A request is scoped to a "current org" (resolved from the X-Org-Id header, a subdomain, or a JWT claim) so the rest of the app can read OrgID(ctx). Each org carries its own Settings and Branding.

The plugin owns its data through a small Store interface (a bounded in-memory store by default; back it with a database via WithStore) and exposes a Go API plus a REST surface under /api/orgs. It composes with `auth` but works standalone.

Index

Constants

View Source
const (
	RoleOwner  = "owner"
	RoleAdmin  = "admin"
	RoleMember = "member"
)

Built-in roles (custom role strings are allowed too). Ranked for RequireOrgRole.

View Source
const (
	StatusInvited = "invited"
	StatusActive  = "active"
)

Member status values.

Variables

View Source
var (
	ErrNotFound   = errors.New("authplatform: not found")
	ErrForbidden  = errors.New("authplatform: forbidden")
	ErrInviteUsed = errors.New("authplatform: invite already used or expired")
)

Errors.

Functions

func OrgID

func OrgID(ctx context.Context) string

OrgID returns the current org id from the context ("" if unset).

func WithOrg

func WithOrg(ctx context.Context, orgID string) context.Context

WithOrg returns a context scoped to orgID.

func WithSubject

func WithSubject(ctx context.Context, userID string) context.Context

WithSubject scopes a context to a user id (for tests / manual wiring).

Types

type Branding

type Branding struct {
	Name         string `json:"name,omitempty"`
	PrimaryColor string `json:"primary_color,omitempty"`
	AccentColor  string `json:"accent_color,omitempty"`
	LogoURL      string `json:"logo_url,omitempty"`
}

Branding is per-org white-label config.

type Invite

type Invite struct {
	Token     string    `json:"token"`
	OrgID     string    `json:"org_id"`
	Email     string    `json:"email"`
	Role      string    `json:"role"`
	CreatedAt time.Time `json:"created_at"`
}

Invite is a pending invitation by email.

type Member

type Member struct {
	OrgID    string    `json:"org_id"`
	UserID   string    `json:"user_id"`
	Role     string    `json:"role"`
	Status   string    `json:"status"`
	JoinedAt time.Time `json:"joined_at"`
}

Member is a user's membership in an org.

type Org

type Org struct {
	ID        string         `json:"id"`
	Name      string         `json:"name"`
	Slug      string         `json:"slug"`
	OwnerID   string         `json:"owner_id"`
	Branding  Branding       `json:"branding"`
	Settings  map[string]any `json:"settings"`
	CreatedAt time.Time      `json:"created_at"`
}

Org is a tenant (organization / team).

type Service

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

Service is the auth-platform runtime stored on the kernel (k.Get("auth-platform")).

func FromKernel

func FromKernel(k *togo.Kernel) (*Service, bool)

FromKernel returns the auth-platform Service registered on the kernel.

func (*Service) Accept

func (s *Service) Accept(inviteToken, userID string) (*Member, error)

Accept consumes an invite token and adds the user as an active member.

func (*Service) AddMember

func (s *Service) AddMember(orgID, userID, role string) (*Member, error)

AddMember directly adds (or updates) a member.

func (*Service) AllOrgs added in v0.1.2

func (s *Service) AllOrgs() []*Org

AllOrgs lists every org (admin view).

func (*Service) CreateOrg

func (s *Service) CreateOrg(name, slug, ownerID string) (*Org, error)

CreateOrg creates an org owned by ownerID and adds the owner as an active member.

func (*Service) CurrentOrg

func (s *Service) CurrentOrg(ctx context.Context) (*Org, bool)

CurrentOrg returns the current Org from the context.

func (*Service) DeleteOrg

func (s *Service) DeleteOrg(id string)

DeleteOrg removes an org and its memberships.

func (*Service) GetOrg

func (s *Service) GetOrg(id string) (*Org, bool)

GetOrg returns an org by id.

func (*Service) HasRole

func (s *Service) HasRole(orgID, userID, role string) bool

HasRole reports whether the user's role in the org is at least `role` (by rank; custom roles match by exact name).

func (*Service) Invite

func (s *Service) Invite(orgID, email, role string) (*Invite, error)

Invite creates a pending invitation and returns its token.

func (*Service) MemberRole

func (s *Service) MemberRole(orgID, userID string) (string, bool)

MemberRole returns a user's role in an org (and whether they're a member).

func (*Service) Members

func (s *Service) Members(orgID string) []*Member

Members lists an org's members.

func (*Service) OrgBySlug

func (s *Service) OrgBySlug(slug string) (*Org, bool)

OrgBySlug returns an org by slug.

func (*Service) OrgsForUser

func (s *Service) OrgsForUser(userID string) []*Org

OrgsForUser lists every org a user belongs to (the org switcher feed).

func (*Service) RemoveMember

func (s *Service) RemoveMember(orgID, userID string)

RemoveMember removes a member from an org.

func (*Service) RequireOrgRole

func (s *Service) RequireOrgRole(role string) func(http.Handler) http.Handler

RequireOrgRole is middleware that rejects (403) a request whose subject is not a member of the current org with at least `role`.

func (*Service) ResolveOrg

func (s *Service) ResolveOrg(next http.Handler) http.Handler

ResolveOrg is middleware that derives the current org from (in order) the X-Org-Id header, a `?org=` query param, or the first label of the host (subdomain) matched against an org slug, and stores it in the context.

func (*Service) SetBranding

func (s *Service) SetBranding(orgID string, b Branding) error

SetBranding updates an org's branding.

func (*Service) SetRole

func (s *Service) SetRole(orgID, userID, role string) error

SetRole changes a member's role.

func (*Service) SetSetting

func (s *Service) SetSetting(orgID, key string, value any) error

SetSetting sets a per-org setting.

func (*Service) Setting

func (s *Service) Setting(orgID, key string) (any, bool)

Setting reads a per-org setting.

func (*Service) WithStore added in v0.1.2

func (s *Service) WithStore(store Store) *Service

WithStore swaps the backing store (e.g. a DB-backed implementation).

type Store added in v0.1.2

type Store interface {
	SaveOrg(o *Org)
	GetOrg(id string) (*Org, bool)
	OrgBySlug(slug string) (*Org, bool)
	AllOrgs() []*Org
	DeleteOrg(id string)

	SaveMember(m *Member)
	GetMember(orgID, userID string) (*Member, bool)
	MembersByOrg(orgID string) []*Member
	RemoveMember(orgID, userID string)
	OrgsForUser(userID string) []*Org

	SaveInvite(inv *Invite)
	GetInvite(tokenStr string) (*Invite, bool)
	DeleteInvite(tokenStr string)
}

Store is the persistence seam. The default is a bounded in-memory store; install a DB-backed implementation with Service.WithStore. The Service always persists mutations via an explicit Save*, so a DB store never needs to track in-place changes to returned structs.

Jump to

Keyboard shortcuts

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