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
- func GetMigrations(dialect plugins.Dialect) ([]plugins.Migration, error)
- func GetSchemaRequirements(dialect plugins.Dialect) []plugins.SchemaRequirement
- type AcceptInvitationRequest
- type AddOrganizationMemberRequest
- type AddTeamMemberRequest
- type Config
- type CreateInvitationRequest
- type CreateOrganizationRequest
- type CreateTeamRequest
- type DeclineInvitationRequest
- type InvitationResponse
- type Permission
- type Plugin
- func (p *Plugin) AcceptInvitation(ctx context.Context, tokenHash, userID string) (*orgtypes.Invitation, error)
- func (p *Plugin) AcceptInvitationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) AddOrganizationMember(ctx context.Context, orgID, userID, role string) error
- func (p *Plugin) AddOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) AddTeamMember(ctx context.Context, teamID, userID, role string) error
- func (p *Plugin) AddTeamMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CanAccessTeam(ctx context.Context, userID, teamID string) (bool, error)
- func (p *Plugin) CancelInvitation(ctx context.Context, id string) error
- func (p *Plugin) CancelInvitationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CreateInvitation(ctx context.Context, orgID string, teamID *string, ...) (*orgtypes.Invitation, string, error)
- func (p *Plugin) CreateInvitationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CreateOrganization(ctx context.Context, name, slug, ownerID string) (*orgtypes.Organization, error)
- func (p *Plugin) CreateOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) CreateTeam(ctx context.Context, orgID, name, description string) (*orgtypes.Team, error)
- func (p *Plugin) CreateTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) DeclineInvitation(ctx context.Context, tokenHash string) (*orgtypes.Invitation, error)
- func (p *Plugin) DeclineInvitationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) DeleteOrganization(ctx context.Context, id string) error
- func (p *Plugin) DeleteOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) DeleteTeam(ctx context.Context, id string) error
- func (p *Plugin) DeleteTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) Dependencies() []plugins.Dependency
- func (p *Plugin) Description() string
- func (p *Plugin) EnrichUser(ctx context.Context, user *core.EnrichedUser) error
- func (p *Plugin) GetMigrations() []plugins.Migration
- func (p *Plugin) GetOrganization(ctx context.Context, id string) (orgtypes.Organization, error)
- func (p *Plugin) GetOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) GetTeam(ctx context.Context, id string) (*orgtypes.Team, error)
- func (p *Plugin) GetTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) GetUserOrganizations(ctx context.Context, userID string, offset, limit int) ([]*orgtypes.Organization, int, error)
- func (p *Plugin) HasOrgPermission(ctx context.Context, userID, orgID string, perm Permission) (bool, error)
- func (p *Plugin) HasOrgRole(ctx context.Context, userID, orgID string, roles ...string) (bool, error)
- func (p *Plugin) HasTeamPermission(ctx context.Context, userID, teamID string, perm Permission) (bool, error)
- func (p *Plugin) HasTeamRole(ctx context.Context, userID, teamID string, roles ...string) (bool, error)
- func (p *Plugin) Init(ctx context.Context, aegis plugins.Aegis) error
- func (p *Plugin) IsOrganizationMember(ctx context.Context, userID, orgID string) booldeprecated
- func (p *Plugin) IsOwner(ctx context.Context, userID, orgID string) bool
- func (p *Plugin) IsOwnerOrAdmin(ctx context.Context, userID, orgID string) booldeprecated
- func (p *Plugin) ListInvitations(ctx context.Context, orgID string, teamID string, offset, limit int) ([]*orgtypes.Invitation, int, error)
- func (p *Plugin) ListInvitationsHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListOrganizationMembers(ctx context.Context, orgID string, offset, limit int) ([]*orgtypes.Member, int, error)
- func (p *Plugin) ListOrganizationMembersHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListOrganizationsHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListTeamMembers(ctx context.Context, teamID string, offset, limit int) ([]*orgtypes.TeamMember, int, error)
- func (p *Plugin) ListTeamMembersHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ListTeams(ctx context.Context, orgID string, offset, limit int) ([]*orgtypes.Team, int, error)
- func (p *Plugin) ListTeamsHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) MountRoutes(r router.Router, prefix string)
- func (p *Plugin) Name() string
- func (p *Plugin) OrgRoles() map[string]RoleDefinition
- func (p *Plugin) ProvidesAuthMethods() []string
- func (p *Plugin) RemoveOrganizationMember(ctx context.Context, userID, orgID string) error
- func (p *Plugin) RemoveOrganizationMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) RemoveTeamMember(ctx context.Context, teamID, userID string) error
- func (p *Plugin) RemoveTeamMemberHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) RequireOrgPermission(perm Permission) func(http.Handler) http.Handler
- func (p *Plugin) RequireOrgRole(roles ...string) func(http.Handler) http.Handler
- func (p *Plugin) RequireOrganizationAdminMiddleware() func(http.Handler) http.Handlerdeprecated
- func (p *Plugin) RequireOrganizationMemberMiddleware() func(http.Handler) http.Handlerdeprecated
- func (p *Plugin) RequireOrganizationOwnerMiddleware() func(http.Handler) http.Handler
- func (p *Plugin) RequireTeamPermission(perm Permission) func(http.Handler) http.Handler
- func (p *Plugin) RequireTeamRole(roles ...string) func(http.Handler) http.Handler
- func (p *Plugin) RequiresTables() []string
- func (p *Plugin) TeamRoles() map[string]RoleDefinition
- func (p *Plugin) UpdateMemberRole(ctx context.Context, orgID, userID, role string) error
- func (p *Plugin) UpdateMemberRoleHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateOrganization(ctx context.Context, id, name, slug string) error
- func (p *Plugin) UpdateOrganizationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateTeam(ctx context.Context, id, name, description string) error
- func (p *Plugin) UpdateTeamHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) UpdateTeamMemberRole(ctx context.Context, teamID, userID, role string) error
- func (p *Plugin) UpdateTeamMemberRoleHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) ValidateAddMember(req AddOrganizationMemberRequest) error
- func (p *Plugin) ValidateAddTeamMember(req AddTeamMemberRequest) error
- func (p *Plugin) ValidateCreateInvitation(req CreateInvitationRequest) error
- func (p *Plugin) ValidateUpdateMemberRole(req UpdateMemberRoleRequest) error
- func (p *Plugin) ValidateUpdateTeamMemberRole(req UpdateTeamMemberRoleRequest) error
- func (p *Plugin) VerifyInvitation(ctx context.Context, rawToken string) (*orgtypes.Invitation, error)
- func (p *Plugin) VerifyInvitationHandler(w http.ResponseWriter, r *http.Request)
- func (p *Plugin) Version() string
- type RoleDefinition
- type UpdateMemberRoleRequest
- type UpdateOrganizationRequest
- type UpdateTeamMemberRoleRequest
- type UpdateTeamRequest
Constants ¶
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 ¶
This section is empty.
Functions ¶
func GetMigrations ¶
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 ¶
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 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 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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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:
- Create organization record
- 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) 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 ¶
DeleteOrganization soft-deletes an organization.
func (*Plugin) DeleteOrganizationHandler ¶
func (p *Plugin) DeleteOrganizationHandler(w http.ResponseWriter, r *http.Request)
DeleteOrganizationHandler deletes an organization
func (*Plugin) DeleteTeam ¶
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 ¶
Description returns the plugin description
func (*Plugin) EnrichUser ¶
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) GetMigrations ¶
GetMigrations returns the plugin migrations
func (*Plugin) GetOrganization ¶
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) 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's role in the organization grants perm. 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 ¶
Init initializes the organizations plugin with Aegis services.
This method validates database schema requirements and stores the session service for authentication middleware.
Initialization Steps:
- Initialize store if not provided (DefaultOrganizationStore)
- Build schema validation requirements (tables, foreign keys)
- Validate schema via Aegis
- 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) IsOrganizationMember
deprecated
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 ¶
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
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) 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) 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) MountRoutes ¶
MountRoutes registers HTTP routes for the organizations plugin
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 ¶
ProvidesAuthMethods returns the provided auth methods
func (*Plugin) RemoveOrganizationMember ¶
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 ¶
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 ¶
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 ¶
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) RequireOrganizationOwnerMiddleware ¶
RequireOrganizationOwnerMiddleware enforces owner-only access.
func (*Plugin) RequireTeamPermission ¶
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 ¶
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 ¶
RequiresTables returns required tables
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) UpdateMemberRole ¶
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 ¶
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) UpdateTeam ¶
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 ¶
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(req AddOrganizationMemberRequest) error
ValidateAddMember validates an AddOrganizationMemberRequest against the configured 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(req CreateInvitationRequest) error
ValidateCreateInvitation validates a CreateInvitationRequest. The role is validated against team roles when the invitation targets a team, and against org roles otherwise.
func (*Plugin) ValidateUpdateMemberRole ¶
func (p *Plugin) ValidateUpdateMemberRole(req UpdateMemberRoleRequest) error
ValidateUpdateMemberRole validates an UpdateMemberRoleRequest against the configured 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.
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 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 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 ¶
UpdateTeamRequest represents a request to update a team.
func (UpdateTeamRequest) Validate ¶
func (r UpdateTeamRequest) Validate() error
Validate validates the update team request.
Source Files
¶
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. |