orgs

package module
v0.3.4 Latest Latest
Warning

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

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

Documentation

Overview

Package orgs holds the building blocks for organisations in multi-tenant apps (ADR-0023, ADR-0048): organisation IDs, the membership check every organisation operation starts with, and invitation emails. The generated app owns the flows, tables and SQL in internal/modules/orgs.

Stability: stable (ADR-0015, ADR-0054).

Index

Constants

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

The organisation roles every app declares in its org catalog. Apps may add their own. Role names are public API.

Variables

View Source
var (
	// ErrOrgNotFound reports an organisation that doesn't exist, is deleted,
	// or that the caller isn't a member of. The three look the same, so
	// organisation IDs can't be probed.
	ErrOrgNotFound = errors.New("orgs: organisation not found")
	// ErrNotMember is returned by [Memberships] when the user isn't a
	// member of a live organisation. [RequireMember] turns it into
	// [ErrOrgNotFound].
	ErrNotMember = errors.New("orgs: not a member")
	// ErrInvalidID reports a string that isn't an organisation ID.
	ErrInvalidID = errors.New("orgs: invalid organisation ID")
)

Errors. Check them with errors.Is.

Functions

func Authorize

func Authorize(ctx context.Context, catalog *auth.Catalog, member Member, permission string) (context.Context, error)

Authorize is RequireMember for a membership the caller has already read, such as one in a deleted organisation, which Memberships doesn't return: it checks that member is the signed-in user and that their role grants permission, with the same two-factor step-up, and returns a context whose actor acts in member's organisation. Every permission check on an organisation goes through it, so roles that require two-factor authentication can't be bypassed by a path that reads the role itself.

For a request authenticated with an API key, the role's permissions are limited by auth.Principal.Restrict: never those of roles that require two-factor authentication, and only the key's scopes. A service account's key reaches only the organisation the service account belongs to.

It returns actor.ErrUnauthenticated without a signed-in user, ErrOrgNotFound when member belongs to another user or has an invalid organisation ID, or a service account's key is used for another organisation, actor.ErrStepUpRequired when the role grants permission only to sessions verified with a second factor, and actor.ErrForbidden when it doesn't grant it. The returned context is usable, with the role's permissions, even when the error is ErrStepUpRequired or ErrForbidden.

Types

type Emails

type Emails interface {
	// SendInvitation invites to to join an organisation.
	SendInvitation(ctx context.Context, to string, inv Invitation) error
}

Emails sends the emails organisations need. Apps use NewBrandedEmails or their own templates. Implementations should queue rather than deliver inline (jobs.AsyncSender), so requests don't wait for the provider.

func NewBrandedEmails

func NewBrandedEmails(sender mail.Sender, brand mail.Brand) Emails

NewBrandedEmails returns emails in brand's layout (mail.Brand.Render) sent through sender, which sets the From address (mail.WithDefaults). brand.Name appears in subjects and bodies.

func NewMailEmails

func NewMailEmails(sender mail.Sender, appName string) Emails

NewMailEmails returns the emails of NewBrandedEmails with appName as the whole brand: no link, logo or support address.

type ID

type ID string

ID identifies an organisation, such as org_2x7…. It is a distinct type so a user ID can't be passed where an organisation ID is expected.

func NewID

func NewID() ID

NewID returns a random organisation ID with 128 bits of randomness.

func ParseID

func ParseID(s string) (ID, error)

ParseID returns s as an ID, or ErrInvalidID when s isn't shaped like one. It doesn't check that the organisation exists.

func (ID) String

func (id ID) String() string

String returns the ID as stored.

type Invitation

type Invitation struct {
	// OrgName is the organisation's name and InvitedBy who sent the
	// invitation (a name or email address).
	OrgName   string
	InvitedBy string
	// Role is the role the invited person gets.
	Role string
	// URL opens the invitation in the app's frontend; it carries the token.
	URL string
	// ExpiresIn is how long the invitation stays valid.
	ExpiresIn time.Duration
}

Invitation is what an invitation email says.

type Member

type Member struct {
	OrgID  ID
	UserID string
	Role   string
}

Member is a user's membership in an organisation, or an organisation service account's role in its organisation (ADR-0058): then UserID is the service account's ID.

func RequireMember

func RequireMember(ctx context.Context, m Memberships, catalog *auth.Catalog, orgID ID, permission string) (context.Context, Member, error)

RequireMember checks that the signed-in user is a member of orgID whose role grants permission, and returns a context whose actor acts in that organisation: OrgID is set and Permissions are the role's permissions in catalog, replacing platform permissions for the operation. Every organisation operation calls it first (ADR-0048).

A service account's API key acts like a member when m answers for its ID (ADR-0058); see Authorize for what it may reach.

It returns actor.ErrUnauthenticated without a signed-in user, ErrOrgNotFound when orgID isn't one of the user's live organisations, actor.ErrStepUpRequired when the role grants permission only to sessions verified with a second factor, and actor.ErrForbidden when it doesn't grant it.

The membership is read on every call, so removing a member takes effect on their next request.

type Memberships

type Memberships interface {
	// MemberRole returns userID's role in orgID, or [ErrNotMember] when the
	// user isn't a member or the organisation is deleted. An implementation
	// that also answers for service account IDs returns the role of an
	// enabled service account of that organisation.
	MemberRole(ctx context.Context, orgID ID, userID string) (string, error)
}

Memberships reads memberships. The app's orgs repository implements it.

Jump to

Keyboard shortcuts

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