organizations

package
v2.1.2 Latest Latest
Warning

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

Go to latest
Published: Oct 3, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package organizations provides multi-tenancy and team management for Aegis.

This plugin enables SaaS applications to manage multiple organizations (workspaces, companies, tenants) with member roles and team hierarchies. It implements a complete RBAC (Role-Based Access Control) system for organizational resources.

Multi-Tenancy Architecture:

  • Organization: Top-level tenant (e.g., "Acme Corp", "Tech Startup")
  • Members: Users with roles in an organization (owner, admin, member)
  • Teams: Groups within an organization (e.g., "Engineering", "Sales")
  • Team Members: Users with roles in a team (lead, member)

Role Hierarchy:

Organization Roles:
  - owner: Full control, can delete organization, manage all members
  - admin: Can manage members, teams, but cannot delete organization
  - member: Read access to organization resources

Team Roles:
  - lead: Can manage team members and settings
  - member: Participate in team activities

Common Use Cases:

  • SaaS with company workspaces (Slack, Notion, GitHub)
  • Project management tools with teams
  • Enterprise apps with department hierarchies
  • Multi-tenant platforms with access control

Database Schema:

  • organization: Stores organization metadata (id, name, slug)
  • members: Links users to organizations with roles
  • team: Stores team metadata within organizations
  • team_member: Links users to teams with roles

Example Setup:

// Create organization plugin
orgPlugin := organizations.New(nil, nil, plugins.DialectPostgres)

// User creates organization
org, _ := orgPlugin.CreateOrganization(ctx, "Acme Corp", "acme", user.ID)
// User is automatically added as owner

// Owner adds admin
orgPlugin.AddOrganizationMember(ctx, org.ID, adminUserID, "admin")

// Admin creates team
team, _ := orgPlugin.CreateTeam(ctx, org.ID, "Engineering", "Dev team")

// Admin adds team member
orgPlugin.AddTeamMember(ctx, team.ID, devUserID, "member")

Security Features:

  • All routes require authentication (RequireAuthMiddleware)
  • Role-based middleware (RequireOrganizationMember, RequireOrganizationAdmin, RequireOrganizationOwner)
  • Foreign key constraints prevent orphaned records
  • Cascade deletes when organization is deleted

Index

Constants

View Source
const (
	// Request schemas
	SchemaCreateOrganizationRequest    = "CreateOrganizationRequest"
	SchemaUpdateOrganizationRequest    = "UpdateOrganizationRequest"
	SchemaAddOrganizationMemberRequest = "AddOrganizationMemberRequest"
	SchemaUpdateMemberRoleRequest      = "UpdateMemberRoleRequest"
	SchemaCreateTeamRequest            = "CreateTeamRequest"
	SchemaUpdateTeamRequest            = "UpdateTeamRequest"
	SchemaAddTeamMemberRequest         = "AddTeamMemberRequest"
	SchemaUpdateTeamMemberRoleRequest  = "UpdateTeamMemberRoleRequest"

	// Response schemas
	SchemaOrganization     = "Organization"
	SchemaOrganizationList = "OrganizationList"
	SchemaTeam             = "Team"
	SchemaTeamList         = "TeamList"
	SchemaMember           = "Member"
	SchemaMemberList       = "MemberList"
	SchemaTeamMember       = "TeamMember"
	SchemaTeamMemberList   = "TeamMemberList"

	// Invitation schemas
	SchemaCreateInvitationRequest  = "CreateInvitationRequest"
	SchemaAcceptInvitationRequest  = "AcceptInvitationRequest"
	SchemaDeclineInvitationRequest = "DeclineInvitationRequest"
	SchemaInvitation               = "Invitation"
	SchemaInvitationList           = "InvitationList"
)

Schema names for OpenAPI specification generation.

These constants define the OpenAPI schema names for organizations request/response types. They are used in route metadata to generate accurate API documentation with typed request/response examples.

Variables

View Source
var (
	// ErrPermissionRequired means an override was sent without a permission.
	ErrPermissionRequired = errors.New("permission is required")
	// ErrInvalidEffect means the effect was neither "grant" nor "deny".
	ErrInvalidEffect = errors.New("invalid permission effect")
	// ErrDuplicatePermission means the same permission appeared twice.
	ErrDuplicatePermission = errors.New("duplicate permission")
)

Validation errors returned by SetMemberPermissionOverrides.

View Source
var (
	// ErrRoleReserved means the name belongs to a compiled (built-in or
	// Config.OrgRoles) role, which cannot be edited through the API.
	ErrRoleReserved = errors.New("role name is reserved")

	// ErrRoleExists means a custom role with that name already exists.
	ErrRoleExists = errors.New("role already exists")

	// ErrRoleInUse means members still hold the role, so it cannot be deleted.
	ErrRoleInUse = errors.New("role is assigned to members")
)

Role-management errors.

Functions

func GetMigrations

func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)

GetMigrations returns all database migrations for the organizations plugin.

This function loads migrations from embedded SQL files and returns them in version order.

Version Numbering:

  • Version 001+: Migrations from migrations/<dialect>/<version>_<description>.<up|down>.sql

Migration File Format:

  • Up migration: 001_initial.up.sql
  • Down migration: 001_initial.down.sql

Parameters:

  • dialect: Database dialect (postgres, mysql, sqlite)

Returns:

  • []plugins.Migration: Sorted list of migrations (oldest first)
  • error: If migration files cannot be read or parsed

func GetSchemaRequirements

func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement

GetSchemaRequirements returns schema validation requirements for the organizations plugin.

This function defines structural requirements that must be satisfied for the plugin to function correctly. The Init() method validates these requirements at startup.

Validation Checks:

  • Table existence: organization, members, team, team_member
  • Column existence: All required columns in each table
  • Security-critical role columns are checked with ColumnSpec to detect type/nullability drift that could cause silent privilege confusion.

Parameters:

  • dialect: Database dialect (postgres, mysql, sqlite)

Returns:

  • []plugins.SchemaRequirement: List of validation requirements

Types

type AcceptInvitationRequest

type AcceptInvitationRequest struct {
	Token string `json:"token"` // Raw invitation token from invite email
}

AcceptInvitationRequest represents a request to accept an invitation.

Validation Rules:

  • token: Required

func (AcceptInvitationRequest) Validate

func (r AcceptInvitationRequest) Validate() error

Validate validates the accept invitation request.

type AddOrganizationMemberRequest

type AddOrganizationMemberRequest struct {
	UserID string `json:"userId"` // User ID to add
	Role   string `json:"role"`   // Member role ("admin" or "member")
}

AddOrganizationMemberRequest represents a request to add a member to an organization.

Validation Rules:

  • userId: Required (must be a valid user ID in the system)
  • role: Required, must be "admin" or "member" ("owner" cannot be assigned this way)

Example:

{
  "userId": "user_xyz789",
  "role": "admin"
}

Security Note: The "owner" role cannot be assigned via this endpoint to prevent privilege escalation. Ownership is assigned during organization creation or via explicit transfer (if implemented).

func (AddOrganizationMemberRequest) Validate

func (r AddOrganizationMemberRequest) Validate() error

Validate validates the add organization member request.

type AddTeamMemberRequest

type AddTeamMemberRequest struct {
	UserID string `json:"userId"`
	Role   string `json:"role"`
}

AddTeamMemberRequest represents a request to add a member to a team.

func (AddTeamMemberRequest) Validate

func (r AddTeamMemberRequest) Validate() error

Validate validates the add team member request.

type Config

type Config struct {
	// OrgRoles overrides or extends the built-in organization roles
	// (owner, admin, member). Each key is an assignable role; its
	// Permissions determine what the role can do. A key that matches a
	// built-in replaces that role's permissions; new keys add custom roles.
	// The owner role is never assignable via the member-management endpoints.
	OrgRoles map[string]RoleDefinition

	// TeamRoles overrides or extends the built-in team roles (lead, member).
	// Semantics are the same as OrgRoles.
	TeamRoles map[string]RoleDefinition

	// InvitationSubject is the subject line for invitation emails.
	// Default: "You're invited!".
	InvitationSubject string

	// InvitationBodyTemplate is the body template for invitation emails.
	// Use %s as a placeholder for the accept URL (substituted via fmt.Sprintf).
	// Default: "You have been invited.\n\nAccept your invitation here: %s".
	InvitationBodyTemplate string
}

Config holds optional configuration for the organizations plugin.

type CreateInvitationRequest

type CreateInvitationRequest struct {
	Email     string  `json:"email"`               // Invitee email address
	Role      string  `json:"role"`                // Role on acceptance ("admin" or "member")
	TeamID    *string `json:"teamId,omitempty"`    // Optional team ID (omit for org-level)
	ExpiresIn string  `json:"expiresIn,omitempty"` // Optional duration (e.g. "72h"), default 168h
}

CreateInvitationRequest represents a request to invite a user to an organization or team.

Validation Rules:

  • email: Required, valid email format
  • role: Required, must be "admin" or "member"
  • teamId: Optional (omit for org-level invitation)
  • expiresIn: Optional duration string (e.g. "72h"), defaults to 7 days

Example Org-Level:

{
  "email": "newuser@example.com",
  "role": "member"
}

Example Team-Level:

{
  "email": "dev@example.com",
  "role": "member",
  "teamId": "team_def456"
}

func (CreateInvitationRequest) Validate

func (r CreateInvitationRequest) Validate() error

Validate validates the create invitation request.

type CreateOrganizationRequest

type CreateOrganizationRequest struct {
	Name string `json:"name"` // Organization display name
	Slug string `json:"slug"` // URL-friendly identifier (must be unique)
}

CreateOrganizationRequest represents a request to create an organization.

Validation Rules:

  • name: Required, 1-100 characters (organization display name)
  • slug: Required, 3-50 characters, lowercase alphanumeric + hyphens only

Example:

{
  "name": "Acme Corporation",
  "slug": "acme-corp"
}

func (CreateOrganizationRequest) Validate

func (r CreateOrganizationRequest) Validate() error

Validate validates the create organization request.

Returns:

  • error: Validation error if name or slug is invalid

type CreateRoleRequest added in v2.1.0

type CreateRoleRequest struct {
	Name        string   `json:"name"`        // Role name (1-50 chars, not a compiled role)
	Permissions []string `json:"permissions"` // Permission strings
}

CreateRoleRequest creates a custom organization role.

func (CreateRoleRequest) Validate added in v2.1.0

func (r CreateRoleRequest) Validate() error

Validate validates the create role request.

type CreateTeamRequest

type CreateTeamRequest struct {
	Name        string `json:"name"`        // Team display name
	Description string `json:"description"` // Team purpose/description
}

CreateTeamRequest represents a request to create a team within an organization.

Validation Rules:

  • name: Required, 1-100 characters (team display name)
  • description: Optional, max 500 characters (team purpose)

Example:

{
  "name": "Engineering",
  "description": "Software development team"
}

func (CreateTeamRequest) Validate

func (r CreateTeamRequest) Validate() error

Validate validates the create team request.

type DeclineInvitationRequest

type DeclineInvitationRequest struct {
	Token string `json:"token"` // Raw invitation token from invite email
}

DeclineInvitationRequest represents a request to decline an invitation.

Validation Rules:

  • token: Required

func (DeclineInvitationRequest) Validate

func (r DeclineInvitationRequest) Validate() error

Validate validates the decline invitation request.

type InvitationResponse

type InvitationResponse struct {
	ID             string  `json:"id"`
	OrganizationID string  `json:"organizationId"`
	TeamID         *string `json:"teamId,omitempty"`
	Email          string  `json:"email"`
	Role           string  `json:"role"`
	Token          string  `json:"token"` // Raw token — only returned at creation
	Status         string  `json:"status"`
	ExpiresAt      string  `json:"expiresAt"`
	CreatedAt      string  `json:"createdAt"`
	UpdatedAt      string  `json:"updatedAt"`
}

InvitationResponse is the response returned when creating an invitation. It includes the raw token which must be delivered to the invitee.

type MemberPermissionOverrideRequest added in v2.1.0

type MemberPermissionOverrideRequest struct {
	Permission string `json:"permission"` // Permission string
	Effect     string `json:"effect"`     // "grant" or "deny"
}

MemberPermissionOverrideRequest is one entry in UpdateMemberPermissionsRequest. Permission values are app-defined strings (including the built-in org/team permissions).

type MemberPermissions added in v2.1.0

type MemberPermissions struct {
	Role        string                              `json:"role"`
	Overrides   []orgtypes.MemberPermissionOverride `json:"overrides"`
	Permissions []Permission                        `json:"permissions"`
}

MemberPermissions describes a member's effective authorization: the role, the raw overrides, and the resolved permission set.

type MemberPermissionsEntry added in v2.1.1

type MemberPermissionsEntry struct {
	UserID string `json:"userId"`
	MemberPermissions
}

MemberPermissionsEntry is one row of the members-permissions listing: a member's user ID plus the effective authorization resolved for them. The embedded MemberPermissions fields are flattened into the JSON object.

type Permission

type Permission string

Permission is a capability that a role can grant within an organization or team. Applications check permissions rather than role names, so custom roles are first-class: define a role, grant it permissions, and the built-in handlers honor it.

const (
	// PermOrgView allows viewing the organization and its members/teams.
	PermOrgView Permission = "org:view"
	// PermOrgManage allows updating organization settings.
	PermOrgManage Permission = "org:manage"
	// PermOrgDelete allows deleting the organization.
	PermOrgDelete Permission = "org:delete"

	// PermMemberView allows listing organization members.
	PermMemberView Permission = "member:view"
	// PermMemberManage allows adding and removing organization members.
	PermMemberManage Permission = "member:manage"
	// PermMemberAssignRoles allows changing a member's organization role.
	PermMemberAssignRoles Permission = "member:assign_roles"

	// PermTeamView allows viewing teams and their members.
	PermTeamView Permission = "team:view"
	// PermTeamManage allows creating, updating, and deleting teams.
	PermTeamManage Permission = "team:manage"
	// PermTeamMemberManage allows adding, updating, and removing team members.
	PermTeamMemberManage Permission = "team_member:manage"

	// PermInvitationManage allows creating, listing, and canceling invitations.
	PermInvitationManage Permission = "invitation:manage"
)

type Plugin

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

Plugin implements multi-tenant organization and team management.

This plugin provides complete CRUD operations for organizations, members, teams, and team members with role-based access control.

Components:

  • sessionService: User authentication for protected routes
  • store: Database persistence for organizations, members, teams
  • dialect: SQL dialect (PostgreSQL, MySQL, SQLite)
  • config: Plugin configuration (roles, email templates)
  • emailSender: Optional function for delivering invitation emails, sourced from the email-otp plugin at init time

Endpoints Provided:

Organizations: POST, GET, PUT, DELETE /organizations
Members: POST, GET, PATCH, DELETE /organizations/:id/members
Teams: POST, GET, PUT, DELETE /teams, /organizations/:id/teams
Team Members: POST, GET, PATCH, DELETE /teams/:teamId/members
Invitations: POST, GET, DELETE /organizations/:id/invitations
            POST, GET, DELETE /teams/:teamId/invitations
            POST /organizations/invitations/accept
            POST /organizations/invitations/decline
            GET  /organizations/invitations/verify

func New

func New(cfg *Config, store orgtypes.OrganizationStore, dialect ...plugins.Dialect) *Plugin

New creates a new organizations plugin for multi-tenancy management.

Parameters:

  • cfg: Optional configuration (nil = use defaults)
  • store: Organization storage implementation (nil = use DefaultOrganizationStore)
  • dialect: Database dialect (defaults to PostgreSQL)

Returns:

  • *Plugin: Initialized plugin ready for Init() call

Example:

plugin := organizations.New(nil, nil, plugins.DialectPostgres)

func (*Plugin) AcceptInvitation

func (p *Plugin) AcceptInvitation(ctx context.Context, tokenHash, userID string) (*orgtypes.Invitation, error)

AcceptInvitation accepts a pending invitation, creating the appropriate member record (and team member record if team-level).

The caller must validate the token (hash, expiry, status) before calling.

Parameters:

  • ctx: Request context
  • tokenHash: SHA-256 hash of the raw token
  • userID: ID of the accepting user (must exist in the auth system)

Returns:

  • *Invitation: Updated invitation with status "accepted"
  • error: If invitation not found, expired, or already processed

func (*Plugin) AcceptInvitationHandler

func (p *Plugin) AcceptInvitationHandler(w http.ResponseWriter, r *http.Request)

AcceptInvitationHandler accepts a pending invitation using the raw token.

This endpoint is NOT authenticated — the token is the credential. The caller must provide the user ID of the accepting user in the request body.

func (*Plugin) AddOrganizationMember

func (p *Plugin) AddOrganizationMember(ctx context.Context, orgID, userID, role string) error

AddOrganizationMember adds a user to an organization with a specified role.

This method creates a membership record linking the user to the organization. The caller must verify admin/owner permissions before calling this method.

Valid Roles:

  • "owner": Full control (only one owner per organization recommended)
  • "admin": Can manage members and teams
  • "member": Read-only access to organization resources

Parameters:

  • ctx: Request context
  • orgID: Organization ID
  • userID: User ID to add
  • role: Membership role ("owner", "admin", "member")

Returns:

  • error: Database error or duplicate membership

func (*Plugin) AddOrganizationMemberHandler

func (p *Plugin) AddOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)

AddOrganizationMemberHandler adds a member to an organization

func (*Plugin) AddTeamMember

func (p *Plugin) AddTeamMember(ctx context.Context, teamID, userID, role string) error

AddTeamMember adds a user to a team with a specified role.

func (*Plugin) AddTeamMemberHandler

func (p *Plugin) AddTeamMemberHandler(w http.ResponseWriter, r *http.Request)

AddTeamMemberHandler adds a member to a team

func (*Plugin) CanAccessTeam

func (p *Plugin) CanAccessTeam(ctx context.Context, userID, teamID string) (bool, error)

CanAccessTeam checks whether a user can access a team.

A user can access a team if they are a member of the parent organization AND a member of the specific team (with any team role).

func (*Plugin) CancelInvitation

func (p *Plugin) CancelInvitation(ctx context.Context, id string) error

CancelInvitation deletes (cancels) a pending invitation.

Only the inviter or an admin/owner can cancel. The caller should verify permissions before calling.

Parameters:

  • ctx: Request context
  • id: Invitation ID to cancel

Returns:

  • error: Database error

func (*Plugin) CancelInvitationHandler

func (p *Plugin) CancelInvitationHandler(w http.ResponseWriter, r *http.Request)

CancelInvitationHandler cancels (deletes) a pending invitation.

func (*Plugin) CreateInvitation

func (p *Plugin) CreateInvitation(ctx context.Context, orgID string, teamID *string, email, role, inviterID, expiresIn string) (*orgtypes.Invitation, string, error)

CreateInvitation creates a new pending invitation, generates a token, and optionally sends an invitation email via the email-otp plugin.

Parameters:

  • ctx: Request context
  • orgID: Target organization ID
  • teamID: Optional team ID (nil = org-level invitation)
  • email: Invitee email address
  • role: Role on acceptance ("admin" or "member")
  • inviterID: User ID of the person creating the invitation
  • expiresIn: Duration until expiry (e.g. "72h"), defaults to 168h (7 days)

Returns:

  • *Invitation: Created invitation (TokenHash is never exposed)
  • rawToken: The raw token — returned once and never stored
  • error: Database or token generation error

func (*Plugin) CreateInvitationHandler

func (p *Plugin) CreateInvitationHandler(w http.ResponseWriter, r *http.Request)

CreateInvitationHandler creates a new invitation.

This handler accepts both org-level and team-level invitation requests. For org-level invites, use POST /organizations/:id/invitations. For team-level invites, use POST /teams/:teamId/invitations.

Endpoint:

  • Method: POST
  • Path: /organizations/:id/invitations or /teams/:teamId/invitations
  • Auth: Required (must be admin or owner)

Request Body:

{
  "email": "user@example.com",
  "role": "member",
  "teamId": "team_abc123"
}

func (*Plugin) CreateOrganization

func (p *Plugin) CreateOrganization(ctx context.Context, name, slug, ownerID string) (*orgtypes.Organization, error)

CreateOrganization creates a new organization and adds the creator as owner.

This method performs two database operations atomically:

  1. Create organization record
  2. Create member record with role="owner" for creator

Parameters:

  • ctx: Request context
  • name: Organization display name (e.g., "Acme Corporation")
  • slug: URL-friendly identifier (e.g., "acme-corp")
  • ownerID: User ID of the organization creator

Returns:

  • *Organization: Created organization with metadata
  • error: Database error or duplicate slug error

func (*Plugin) CreateOrganizationHandler

func (p *Plugin) CreateOrganizationHandler(w http.ResponseWriter, r *http.Request)

CreateOrganizationHandler creates a new organization with the user as owner.

This endpoint allows any authenticated user to create an organization. The creator is automatically assigned the "owner" role with full administrative privileges.

Endpoint:

  • Method: POST
  • Path: /organizations
  • Auth: Required (any authenticated user)

Request Body:

{
  "name": "Acme Corporation",
  "slug": "acme-corp"
}

Validation:

  • name: Required, 1-100 characters
  • slug: Required, 3-50 characters, lowercase alphanumeric with hyphens only

Response (201 Created):

{
  "success": true,
  "message": "Organization created successfully",
  "data": {
    "id": "org_abc123",
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-01T00:00:00Z"
  }
}

func (*Plugin) CreateRole added in v2.1.0

func (p *Plugin) CreateRole(ctx context.Context, orgID, name string, permissions []Permission) (RoleInfo, error)

CreateRole persists a custom role for the organization. The name must not collide with a compiled role or an existing custom role.

func (*Plugin) CreateRoleHandler added in v2.1.0

func (p *Plugin) CreateRoleHandler(w http.ResponseWriter, r *http.Request)

CreateRoleHandler creates a custom organization role.

func (*Plugin) CreateTeam

func (p *Plugin) CreateTeam(ctx context.Context, orgID, name, description string) (*orgtypes.Team, error)

CreateTeam creates a new team within an organization.

func (*Plugin) CreateTeamHandler

func (p *Plugin) CreateTeamHandler(w http.ResponseWriter, r *http.Request)

CreateTeamHandler creates a new team within an organization

func (*Plugin) DeclineInvitation

func (p *Plugin) DeclineInvitation(ctx context.Context, tokenHash string) (*orgtypes.Invitation, error)

DeclineInvitation declines a pending invitation without creating any member records.

Parameters:

  • ctx: Request context
  • tokenHash: SHA-256 hash of the raw token

Returns:

  • *Invitation: Updated invitation with status "declined"
  • error: If invitation not found or already processed

func (*Plugin) DeclineInvitationHandler

func (p *Plugin) DeclineInvitationHandler(w http.ResponseWriter, r *http.Request)

DeclineInvitationHandler declines a pending invitation using the raw token.

This endpoint is NOT authenticated — the token is the credential.

func (*Plugin) DeleteOrganization

func (p *Plugin) DeleteOrganization(ctx context.Context, id string) error

DeleteOrganization soft-deletes an organization.

func (*Plugin) DeleteOrganizationHandler

func (p *Plugin) DeleteOrganizationHandler(w http.ResponseWriter, r *http.Request)

DeleteOrganizationHandler deletes an organization

func (*Plugin) DeleteRole added in v2.1.0

func (p *Plugin) DeleteRole(ctx context.Context, orgID, name string) error

DeleteRole removes a custom role. Roles still assigned to members are refused so members do not silently lose their permissions.

func (*Plugin) DeleteRoleHandler added in v2.1.0

func (p *Plugin) DeleteRoleHandler(w http.ResponseWriter, r *http.Request)

DeleteRoleHandler removes a custom role that is not in use.

func (*Plugin) DeleteTeam

func (p *Plugin) DeleteTeam(ctx context.Context, id string) error

DeleteTeam deletes a team.

func (*Plugin) DeleteTeamHandler

func (p *Plugin) DeleteTeamHandler(w http.ResponseWriter, r *http.Request)

DeleteTeamHandler deletes a team

func (*Plugin) Dependencies

func (p *Plugin) Dependencies() []plugins.Dependency

Dependencies returns plugin dependencies

func (*Plugin) Description

func (p *Plugin) Description() string

Description returns the plugin description

func (*Plugin) EnrichUser

func (p *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error

EnrichUser implements plugins.UserEnricher to add organization memberships.

This method is called automatically by the authentication system after user lookup. It adds the user's organization memberships to the EnrichedUser, making them available in API responses without requiring separate queries.

Fields Added:

  • "organizations" ([]map[string]any): List of organizations the user belongs to, each containing id, name, and slug fields.

Parameters:

  • ctx: Request context
  • user: EnrichedUser to populate with organization data

Returns:

  • error: Always nil (organization lookup failure is not an error)

func (*Plugin) GetMemberPermissions added in v2.1.0

func (p *Plugin) GetMemberPermissions(ctx context.Context, userID, orgID string) (MemberPermissions, error)

GetMemberPermissions resolves a member's effective permissions: the role's permissions with per-member overrides applied (deny wins over grant, grant wins over the role). Returns sql.ErrNoRows when the user is not a member.

func (*Plugin) GetMemberPermissionsHandler added in v2.1.0

func (p *Plugin) GetMemberPermissionsHandler(w http.ResponseWriter, r *http.Request)

GetMemberPermissionsHandler returns a member's role, overrides and resolved permission set.

func (*Plugin) GetMigrations

func (p *Plugin) GetMigrations() []plugins.Migration

GetMigrations returns the plugin migrations

func (*Plugin) GetMyPermissionsHandler added in v2.1.1

func (p *Plugin) GetMyPermissionsHandler(w http.ResponseWriter, r *http.Request)

GetMyPermissionsHandler returns the authenticated user's own role, overrides and effective permission set for an organization. Any member can read their own authorization; non-members get 403.

func (*Plugin) GetOrganization

func (p *Plugin) GetOrganization(ctx context.Context, id string) (orgtypes.Organization, error)

GetOrganization retrieves an organization by ID.

func (*Plugin) GetOrganizationHandler

func (p *Plugin) GetOrganizationHandler(w http.ResponseWriter, r *http.Request)

GetOrganizationHandler retrieves details of a specific organization.

This endpoint returns organization metadata. Requires membership in the organization.

Endpoint:

  • Method: GET
  • Path: /organizations/:id
  • Auth: Required (must be organization member)

Path Parameters:

  • id: Organization ID

Response (200 OK):

{
  "success": true,
  "message": "Organization retrieved successfully",
  "data": {
    "id": "org_abc123",
    "name": "Acme Corporation",
    "slug": "acme-corp",
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-01T00:00:00Z"
  }
}

func (*Plugin) GetTeam

func (p *Plugin) GetTeam(ctx context.Context, id string) (*orgtypes.Team, error)

GetTeam retrieves a team by ID.

func (*Plugin) GetTeamHandler

func (p *Plugin) GetTeamHandler(w http.ResponseWriter, r *http.Request)

GetTeamHandler gets a specific team

func (*Plugin) GetUserOrganizations

func (p *Plugin) GetUserOrganizations(ctx context.Context, userID string, offset, limit int) ([]*orgtypes.Organization, int, error)

GetUserOrganizations retrieves all organizations for a user.

func (*Plugin) HasOrgPermission

func (p *Plugin) HasOrgPermission(ctx context.Context, userID, orgID string, perm Permission) (bool, error)

HasOrgPermission reports whether the user may exercise perm in the organization. The role's permissions are the baseline; per-member overrides adjust them: a deny always wins, a grant adds a permission the role does not carry. Users who are not members, or whose role is unknown, have no permissions. A store lookup error other than "member not found" is returned.

func (*Plugin) HasOrgRole

func (p *Plugin) HasOrgRole(ctx context.Context, userID, orgID string, roles ...string) (bool, error)

HasOrgRole checks whether the user has any of the given org-level roles.

This is the general-purpose role check for org-level permissions. Pass one or more role constants to check against.

Example:

isAdmin, _ := p.HasOrgRole(ctx, userID, orgID, orgtypes.RoleOwner, orgtypes.RoleAdmin)
isMember, _ := p.HasOrgRole(ctx, userID, orgID, orgtypes.RoleOwner, orgtypes.RoleAdmin, orgtypes.RoleMember)

func (*Plugin) HasTeamPermission

func (p *Plugin) HasTeamPermission(ctx context.Context, userID, teamID string, perm Permission) (bool, error)

HasTeamPermission reports whether the user's role in the team grants perm. Users who are not team members, or whose role is unknown, have no permissions. A store lookup error other than "member not found" is returned.

func (*Plugin) HasTeamRole

func (p *Plugin) HasTeamRole(ctx context.Context, userID, teamID string, roles ...string) (bool, error)

HasTeamRole checks whether the user has any of the given team-level roles.

Example:

canLead, _ := p.HasTeamRole(ctx, userID, teamID, orgtypes.RoleTeamLead)

func (*Plugin) Init

func (p *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error

Init initializes the organizations plugin with Aegis services.

This method validates database schema requirements and stores the session service for authentication middleware.

Initialization Steps:

  1. Initialize store if not provided (DefaultOrganizationStore)
  2. Build schema validation requirements (tables, foreign keys)
  3. Validate schema via Aegis
  4. Store session service for protected routes

Required Tables:

  • organization: Organization metadata
  • members: Organization membership with roles
  • team: Team metadata within organizations
  • team_member: Team membership with roles

Parameters:

  • ctx: Initialization context
  • aegis: Aegis interface providing services and DB

Returns:

  • error: Schema validation error if tables don't exist

func (*Plugin) IsCompiledRole added in v2.1.0

func (p *Plugin) IsCompiledRole(name string) bool

IsCompiledRole reports whether name is a built-in or Config.OrgRoles role. Compiled roles cannot be edited or deleted through the API.

func (*Plugin) IsOrganizationMember deprecated

func (p *Plugin) IsOrganizationMember(ctx context.Context, userID, orgID string) bool

IsOrganizationMember checks if a user is a member of an organization.

Deprecated: Use HasOrgPermission or HasOrgRole instead.

Returns true for any membership regardless of role, so custom roles count as members. A store error (including "not a member") returns false.

func (*Plugin) IsOwner

func (p *Plugin) IsOwner(ctx context.Context, userID, orgID string) bool

IsOwner checks if a user is the owner of an organization.

This method enforces permission requirements for destructive actions:

  • Deleting organization
  • Transferring ownership
  • Changing admin roles

Parameters:

  • ctx: Request context
  • userID: User ID to check
  • orgID: Organization ID

Returns:

  • bool: true if user has owner role

func (*Plugin) IsOwnerOrAdmin deprecated

func (p *Plugin) IsOwnerOrAdmin(ctx context.Context, userID, orgID string) bool

IsOwnerOrAdmin checks if a user is an owner or admin of an organization.

Deprecated: Use HasOrgRole instead.

This method enforces permission requirements for administrative actions:

  • Updating organization settings
  • Adding/removing members
  • Creating/deleting teams

Parameters:

  • ctx: Request context
  • userID: User ID to check
  • orgID: Organization ID

Returns:

  • bool: true if user has owner or admin role

func (*Plugin) ListInvitations

func (p *Plugin) ListInvitations(ctx context.Context, orgID string, teamID string, offset, limit int) ([]*orgtypes.Invitation, int, error)

ListInvitations returns a paginated list of invitations for an organization, optionally filtered to a specific team.

func (*Plugin) ListInvitationsHandler

func (p *Plugin) ListInvitationsHandler(w http.ResponseWriter, r *http.Request)

ListInvitationsHandler lists pending invitations for an organization or team.

func (*Plugin) ListMembersPermissionsHandler added in v2.1.1

func (p *Plugin) ListMembersPermissionsHandler(w http.ResponseWriter, r *http.Request)

ListMembersPermissionsHandler resolves effective permissions for every member of an organization, paginated. Requires member:assign_roles (owner/admin), the same gate as the single-member route.

It issues one lookup per member on the page, so a page size of N costs O(N) queries; keep the default page size for large organizations.

func (*Plugin) ListOrganizationMembers

func (p *Plugin) ListOrganizationMembers(ctx context.Context, orgID string, offset, limit int) ([]*orgtypes.Member, int, error)

ListOrganizationMembers lists all members of an organization.

func (*Plugin) ListOrganizationMembersHandler

func (p *Plugin) ListOrganizationMembersHandler(w http.ResponseWriter, r *http.Request)

ListOrganizationMembersHandler lists organization members.

func (*Plugin) ListOrganizationsHandler

func (p *Plugin) ListOrganizationsHandler(w http.ResponseWriter, r *http.Request)

ListOrganizationsHandler lists all organizations the user is a member of.

This endpoint returns all organizations where the user has any membership (owner, admin, or member role).

Endpoint:

  • Method: GET
  • Path: /organizations
  • Auth: Required

Response (200 OK):

{
  "success": true,
  "items": [
    {"id": "org_1", "name": "Acme Corp", "slug": "acme", ...},
    {"id": "org_2", "name": "Tech Inc", "slug": "tech", ...}
  ],
  "totalCount": 10,
  "page": 1,
  "offset": 0,
  "limit": 20
}

func (*Plugin) ListRoles added in v2.1.0

func (p *Plugin) ListRoles(ctx context.Context, orgID string) ([]RoleInfo, error)

ListRoles returns the compiled role catalog merged with the organization's persisted custom roles. Compiled definitions win on name collisions.

func (*Plugin) ListRolesHandler added in v2.1.0

func (p *Plugin) ListRolesHandler(w http.ResponseWriter, r *http.Request)

ListRolesHandler returns the compiled and custom roles for the organization.

func (*Plugin) ListTeamMembers

func (p *Plugin) ListTeamMembers(ctx context.Context, teamID string, offset, limit int) ([]*orgtypes.TeamMember, int, error)

ListTeamMembers lists all members of a team.

func (*Plugin) ListTeamMembersHandler

func (p *Plugin) ListTeamMembersHandler(w http.ResponseWriter, r *http.Request)

ListTeamMembersHandler lists team members

func (*Plugin) ListTeams

func (p *Plugin) ListTeams(ctx context.Context, orgID string, offset, limit int) ([]*orgtypes.Team, int, error)

ListTeams lists all teams in an organization.

func (*Plugin) ListTeamsHandler

func (p *Plugin) ListTeamsHandler(w http.ResponseWriter, r *http.Request)

ListTeamsHandler lists teams in an organization.

func (*Plugin) MemberPermissionOverrides added in v2.1.0

func (p *Plugin) MemberPermissionOverrides(ctx context.Context, orgID, userID string) ([]orgtypes.MemberPermissionOverride, error)

MemberPermissionOverrides returns a member's grant/deny overrides. Returns an empty slice when none exist.

func (*Plugin) MountRoutes

func (p *Plugin) MountRoutes(r router.Router, prefix string)

MountRoutes registers HTTP routes for the organizations plugin

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin name

func (*Plugin) OrgRoles

func (p *Plugin) OrgRoles() map[string]RoleDefinition

OrgRoles returns the resolved organization role definitions (built-ins merged with Config.OrgRoles). Mutating the returned map does not affect the plugin.

func (*Plugin) ProvidesAuthMethods

func (p *Plugin) ProvidesAuthMethods() []string

ProvidesAuthMethods returns the provided auth methods

func (*Plugin) RemoveOrganizationMember

func (p *Plugin) RemoveOrganizationMember(ctx context.Context, userID, orgID string) error

RemoveOrganizationMember removes a user from an organization.

func (*Plugin) RemoveOrganizationMemberHandler

func (p *Plugin) RemoveOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)

RemoveOrganizationMemberHandler removes a member from an organization

func (*Plugin) RemoveTeamMember

func (p *Plugin) RemoveTeamMember(ctx context.Context, teamID, userID string) error

RemoveTeamMember removes a user from a team.

func (*Plugin) RemoveTeamMemberHandler

func (p *Plugin) RemoveTeamMemberHandler(w http.ResponseWriter, r *http.Request)

RemoveTeamMemberHandler removes a member from a team

func (*Plugin) RequireOrgPermission

func (p *Plugin) RequireOrgPermission(perm Permission) func(http.Handler) http.Handler

RequireOrgPermission creates middleware that requires the authenticated user's organization role to grant perm on the organization identified by the ":id" path parameter.

Prefer this over RequireOrgRole so custom roles are honored: authority comes from the permission a role grants, not its name.

Example:

r.PUT("/organizations/:id",
    requireAuth(plugin.RequireOrgPermission(PermOrgManage)(handler)),
)

func (*Plugin) RequireOrgRole

func (p *Plugin) RequireOrgRole(roles ...string) func(http.Handler) http.Handler

RequireOrgRole creates middleware that requires the authenticated user to have at least one of the specified org-level roles on the organization identified by the ":id" path parameter.

Example:

r.GET("/organizations/:id/settings",
    requireAuth(
        plugin.RequireOrgRole(orgtypes.RoleOwner, orgtypes.RoleAdmin)(
            http.HandlerFunc(handler),
        ),
    ),
)

func (*Plugin) RequireOrganizationAdminMiddleware deprecated

func (p *Plugin) RequireOrganizationAdminMiddleware() func(http.Handler) http.Handler

RequireOrganizationAdminMiddleware enforces admin or owner privileges.

Deprecated: Use RequireOrgRole instead.

func (*Plugin) RequireOrganizationMemberMiddleware deprecated

func (p *Plugin) RequireOrganizationMemberMiddleware() func(http.Handler) http.Handler

RequireOrganizationMemberMiddleware enforces organization membership.

Deprecated: Use RequireOrgRole instead.

func (*Plugin) RequireOrganizationOwnerMiddleware

func (p *Plugin) RequireOrganizationOwnerMiddleware() func(http.Handler) http.Handler

RequireOrganizationOwnerMiddleware enforces owner-only access.

func (*Plugin) RequireTeamPermission

func (p *Plugin) RequireTeamPermission(perm Permission) func(http.Handler) http.Handler

RequireTeamPermission creates middleware that requires the authenticated user to be able to access the team (via CanAccessTeam) and to have a team role that grants perm.

func (*Plugin) RequireTeamRole

func (p *Plugin) RequireTeamRole(roles ...string) func(http.Handler) http.Handler

RequireTeamRole creates middleware that requires the authenticated user to have at least one of the specified team-level roles on the team identified by the ":teamId" path parameter.

Unlike RequireOrgRole, this middleware checks membership in the parent organization as well (via CanAccessTeam), so a user must be both an org member and a team member.

func (*Plugin) RequiresTables

func (p *Plugin) RequiresTables() []string

RequiresTables returns required tables

func (*Plugin) SetMemberPermissionOverrides added in v2.1.0

func (p *Plugin) SetMemberPermissionOverrides(ctx context.Context, orgID, userID string, overrides []orgtypes.MemberPermissionOverride) error

SetMemberPermissionOverrides replaces a member's overrides (send an empty slice to clear them). The target must be a member of the organization; duplicate permissions and unknown effects are rejected before anything is written. Empty IDs and timestamps are filled in on overrides in place.

This is the programmatic entry point: it applies no actor-authority cap because it has no actor. The HTTP handler applies the cap and the owner guard before delegating here.

Replacement is delete-then-insert: a failure part-way through leaves a partial set, which the caller can correct by re-sending the desired list.

func (*Plugin) TeamRoles

func (p *Plugin) TeamRoles() map[string]RoleDefinition

TeamRoles returns the resolved team role definitions (built-ins merged with Config.TeamRoles). Mutating the returned map does not affect the plugin.

func (*Plugin) UpdateMemberPermissionsHandler added in v2.1.0

func (p *Plugin) UpdateMemberPermissionsHandler(w http.ResponseWriter, r *http.Request)

UpdateMemberPermissionsHandler replaces a member's permission overrides.

The set is replaced, not merged: delete-then-insert. A failure part-way through leaves the member with a partial set, which the caller can correct by re-sending the desired list.

func (*Plugin) UpdateMemberRole

func (p *Plugin) UpdateMemberRole(ctx context.Context, orgID, userID, role string) error

UpdateMemberRole updates a user's role in an organization.

func (*Plugin) UpdateMemberRoleHandler

func (p *Plugin) UpdateMemberRoleHandler(w http.ResponseWriter, r *http.Request)

UpdateMemberRoleHandler updates a member's role

func (*Plugin) UpdateOrganization

func (p *Plugin) UpdateOrganization(ctx context.Context, id, name, slug string) error

UpdateOrganization updates an organization's name and slug.

func (*Plugin) UpdateOrganizationHandler

func (p *Plugin) UpdateOrganizationHandler(w http.ResponseWriter, r *http.Request)

UpdateOrganizationHandler updates an organization

func (*Plugin) UpdateRole added in v2.1.0

func (p *Plugin) UpdateRole(ctx context.Context, orgID, name string, permissions []Permission) (RoleInfo, error)

UpdateRole replaces a custom role's permissions. Compiled roles are refused; sql.ErrNoRows means the role does not exist for this organization.

func (*Plugin) UpdateRoleHandler added in v2.1.0

func (p *Plugin) UpdateRoleHandler(w http.ResponseWriter, r *http.Request)

UpdateRoleHandler replaces a custom role's permissions.

func (*Plugin) UpdateTeam

func (p *Plugin) UpdateTeam(ctx context.Context, id, name, description string) error

UpdateTeam updates a team's name and description.

func (*Plugin) UpdateTeamHandler

func (p *Plugin) UpdateTeamHandler(w http.ResponseWriter, r *http.Request)

UpdateTeamHandler updates a team

func (*Plugin) UpdateTeamMemberRole

func (p *Plugin) UpdateTeamMemberRole(ctx context.Context, teamID, userID, role string) error

UpdateTeamMemberRole updates a user's role in a team.

func (*Plugin) UpdateTeamMemberRoleHandler

func (p *Plugin) UpdateTeamMemberRoleHandler(w http.ResponseWriter, r *http.Request)

UpdateTeamMemberRoleHandler updates a team member's role

func (*Plugin) ValidateAddMember

func (p *Plugin) ValidateAddMember(ctx context.Context, orgID string, req AddOrganizationMemberRequest) error

ValidateAddMember validates an AddOrganizationMemberRequest against the configured and persisted org roles.

func (*Plugin) ValidateAddTeamMember

func (p *Plugin) ValidateAddTeamMember(req AddTeamMemberRequest) error

ValidateAddTeamMember validates an AddTeamMemberRequest against the configured team roles.

func (*Plugin) ValidateCreateInvitation

func (p *Plugin) ValidateCreateInvitation(ctx context.Context, orgID string, req CreateInvitationRequest) error

ValidateCreateInvitation validates a CreateInvitationRequest. The role is validated against team roles when the invitation targets a team, and against the org's assignable roles otherwise.

func (*Plugin) ValidateUpdateMemberRole

func (p *Plugin) ValidateUpdateMemberRole(ctx context.Context, orgID string, req UpdateMemberRoleRequest) error

ValidateUpdateMemberRole validates an UpdateMemberRoleRequest against the configured and persisted org roles.

func (*Plugin) ValidateUpdateTeamMemberRole

func (p *Plugin) ValidateUpdateTeamMemberRole(req UpdateTeamMemberRoleRequest) error

ValidateUpdateTeamMemberRole validates an UpdateTeamMemberRoleRequest against the configured team roles.

func (*Plugin) VerifyInvitation

func (p *Plugin) VerifyInvitation(ctx context.Context, rawToken string) (*orgtypes.Invitation, error)

VerifyInvitation validates a raw invitation token and returns the invitation details (without exposing the token hash). Used by the UI to pre-fill invitation information before the user accepts.

Parameters:

  • ctx: Request context
  • rawToken: The raw invitation token

Returns:

  • *Invitation: Invitation details (TokenHash is empty)
  • error: If token is invalid, expired, or already processed

func (*Plugin) VerifyInvitationHandler

func (p *Plugin) VerifyInvitationHandler(w http.ResponseWriter, r *http.Request)

VerifyInvitationHandler validates a raw invitation token and returns invitation details (for UI pre-fill). Unauthenticated.

func (*Plugin) Version

func (p *Plugin) Version() string

Version returns the plugin version

type RoleDefinition

type RoleDefinition struct {
	// Permissions granted to members holding this role.
	Permissions []Permission
}

RoleDefinition describes an assignable organization or team role and the permissions it grants.

func (RoleDefinition) Allows

func (d RoleDefinition) Allows(perm Permission) bool

Allows reports whether the role definition grants perm.

type RoleInfo added in v2.1.0

type RoleInfo struct {
	Name        string       `json:"name"`
	Permissions []Permission `json:"permissions"`
	Custom      bool         `json:"custom"`
}

RoleInfo describes a role in an organization's catalog.

type UpdateMemberPermissionsRequest added in v2.1.0

type UpdateMemberPermissionsRequest struct {
	Overrides []MemberPermissionOverrideRequest `json:"overrides"`
}

UpdateMemberPermissionsRequest replaces a member's permission overrides. Sending an empty list clears every override.

func (UpdateMemberPermissionsRequest) Validate added in v2.1.0

Validate validates the update member permissions request.

type UpdateMemberRoleRequest

type UpdateMemberRoleRequest struct {
	Role string `json:"role"` // New role ("admin" or "member")
}

UpdateMemberRoleRequest represents a request to update a member's role.

Validation Rules:

  • role: Required, must be "admin" or "member"

Example:

{
  "role": "admin"
}

Security Note: Cannot update to "owner" role via this endpoint. Ownership transfer requires a separate flow with additional safeguards.

func (UpdateMemberRoleRequest) Validate

func (r UpdateMemberRoleRequest) Validate() error

Validate validates the update member role request.

type UpdateOrganizationRequest

type UpdateOrganizationRequest struct {
	Name string `json:"name"` // Updated organization name
	Slug string `json:"slug"` // Updated URL-friendly identifier
}

UpdateOrganizationRequest represents a request to update an organization.

Validation Rules:

  • name: Required, 1-100 characters
  • slug: Required, 3-50 characters, lowercase alphanumeric + hyphens only

Note: Both fields must be provided even if only updating one. The handler will apply the new values.

func (UpdateOrganizationRequest) Validate

func (r UpdateOrganizationRequest) Validate() error

Validate validates the update organization request.

type UpdateRoleRequest added in v2.1.0

type UpdateRoleRequest struct {
	Permissions []string `json:"permissions"`
}

UpdateRoleRequest replaces a custom role's permissions.

type UpdateTeamMemberRoleRequest

type UpdateTeamMemberRoleRequest struct {
	Role string `json:"role"`
}

UpdateTeamMemberRoleRequest represents a request to update a team member's role.

func (UpdateTeamMemberRoleRequest) Validate

func (r UpdateTeamMemberRoleRequest) Validate() error

Validate validates the update team member role request.

type UpdateTeamRequest

type UpdateTeamRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

UpdateTeamRequest represents a request to update a team.

func (UpdateTeamRequest) Validate

func (r UpdateTeamRequest) Validate() error

Validate validates the update team request.

Directories

Path Synopsis
Package defaultstore implements the SQL-backed default store for the organizations plugin.
Package defaultstore implements the SQL-backed default store for the organizations plugin.
internal
Package types defines the domain models and types used by the organizations plugin.
Package types defines the domain models and types used by the organizations plugin.

Jump to

Keyboard shortcuts

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