Help us shape the future of Go! Take the 2026 Go Developer Survey

amsonia

package module
v0.1.1-0...-37944d3 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

Amsonia Core — Tenant authorization for Go, enforced in PostgreSQL

Apache-2.0 license CI

Amsonia Core is a self-hosted authorization foundation for Go teams building multi-tenant SaaS. It keeps tenant membership, delegated RBAC, administrator sessions, policy audit, and PostgreSQL row-level isolation in one source-owned system instead of leaving those boundaries to every handler.

Visit Amsonia · Read the Core documentation · Explore Amsonia Platform · View the API contract

Core is evaluated from the repository: the API, migrations, policy kernel, integration tests, OpenAPI contract, local stack, and Console are all inspectable.

Core focuses on identity, sessions, tenants, memberships, roles, permissions, authorization decisions, and policy audit. Billing, commerce, AI, education, messaging, and white-label operations belong to Amsonia Platform, not this open-source Core.

The problem it solves

Multi-tenant authorization usually begins as scattered tenant_id filters and role checks. The expensive failures arrive later: one missing predicate exposes another customer's row, delegated admins create privilege cycles, policy changes cannot be reconstructed, and identity/session code grows beside the authorization code without a stable boundary.

Core makes those invariants explicit:

  • Storage isolation: a signed transaction-local tenant binding drives PostgreSQL FORCE ROW LEVEL SECURITY; forged settings reveal no tenant rows.
  • Delegation safety: immutable role versions, scoped permissions, expected versions, and grant-cycle protection constrain administrative changes.
  • Operational evidence: append-only audit events and a versioned OpenAPI contract make behavior inspectable rather than implicit.
  • Source ownership: the Go kernel, adapters, schema, API, tests, and Console run in infrastructure you control.

Fit

Use Core when you are building a Go/PostgreSQL multi-tenant product, want RBAC and tenant isolation to share one tested boundary, and prefer operating source code over depending on a hosted authorization control plane.

Core is not a general-purpose relationship graph, not an OAuth/OIDC identity provider, and not a drop-in replacement for every policy engine. The host application still authenticates end users, identifies the active tenant, loads business resources, and decides which permission a request requires. Core evaluates that permission and can enforce the same tenant boundary on application-owned PostgreSQL tables.

Approach Best fit Tenant data isolation Operating shape
Amsonia Core Go SaaS with tenant RBAC, admin sessions, audit, and PostgreSQL Signed context plus reference FORCE RLS policies Self-hosted source, API, CLI, Console
Embedded policy library Application-specific RBAC/ABAC logic Designed separately by the application Linked into each service
Relationship/ReBAC service Fine-grained object relationship graphs Application database remains a separate boundary Dedicated authorization service
Identity platform Login, federation, and user lifecycle Depends on the product and integration Hosted or self-hosted identity service

What it provides

  • Tenant-first delegated authorization with own, workspace, and tenant scopes.
  • PostgreSQL-enforced tenant isolation with signed transaction-local bindings and FORCE ROW LEVEL SECURITY.
  • Argon2id administrator authentication, lockout, rotating refresh sessions, and one-time administrator bootstrap.
  • Permission catalogs, immutable role versions, grant-cycle protection, and append-only audit events.
  • A standalone Go API, migration/administration CLI, in-memory adapter, and React management console.

Verify it in five minutes

Requirements: Docker with the Compose plugin and Go 1.25.13+.

git clone https://github.com/willunylabs/amsonia-core.git
cd amsonia-core
make demo

The command generates independent local infrastructure credentials in a Git-ignored, owner-readable file, builds the complete stack, and prompts once for an administrator password without echoing or storing it. Open http://127.0.0.1:8080. Re-running make demo is idempotent; make demo-down stops the stack without deleting its PostgreSQL volume.

Then run the host-table proof in examples/business-data-rls: two tenants query the same invoice table without tenant predicates, each sees only its own row, and a cross-tenant insert is rejected by PostgreSQL RLS. The walkthrough is in docs/business-data-rls.md.

Evidence, not feature claims

Invariant Public evidence
Forged tenant settings expose no Core or host rows postgres/store_integration_test.go, postgres/host_table_integration_test.go
Runtime and maintenance roles cannot bypass RLS ops/configure_database_roles.sql
Delegation cycles and stale role versions are rejected Go unit tests and PostgreSQL lifecycle tests
Browser/API behavior stays versioned openapi/openapi.yaml and Console contract tests
Identity credentials and sessions use hardened primitives docs/security-model.md and internal/security tests

Repository layout

Path Purpose
cmd/api Standalone HTTP API
cmd/amsonia Migration and administrator CLI
memory In-memory adapter for tests
postgres PostgreSQL adapter, migrations, and RLS tests
internal/coreapp Identity, session, tenant, and HTTP composition
web React/Vite management console
site Astro product and technical website
openapi Versioned HTTP contract
ops Least-privileged database role provisioning

Manual development setup

Requirements: Go 1.25.13+, PostgreSQL 16+, Node.js 22.15+, and npm 10. Docker is optional. The complete local setup, role provisioning, environment variables, and PostgreSQL checks are in docs/getting-started.md.

Use this path when you want to manage PostgreSQL and Node.js directly rather than use the five-minute Compose flow. After completing the database and environment setup:

go run ./cmd/amsonia migrate
go run ./cmd/amsonia bootstrap-admin
go run ./cmd/api

In another terminal:

npm --prefix web ci
npm --prefix web run dev

Then open http://127.0.0.1:3000 and sign in with the bootstrap administrator.

HTTP API

The versioned contract is openapi/openapi.yaml. Core routes cover:

  • authentication and session refresh;
  • tenants and memberships;
  • permission catalogs and roles;
  • audit events; and
  • authorization checks.

The API is intended to sit behind HTTPS in production. Refresh credentials are HttpOnly, SameSite=Strict cookies and are never returned in JSON.

Development

Run the local quality gate with:

make check

It covers Go format/vet/unit/race checks, the reachable-vulnerability scan, web checks, and OpenAPI validation. Contribution rules are in CONTRIBUTING.md, and the security model/reporting process is in SECURITY.md and docs/security-model.md.

The project positioning and proof-first launch material are maintained in docs/launch/launch-kit.md. Contributions that add a reproducible example, negative security test, integration guide, or measured operational result are especially useful.

Amsonia product

Amsonia is the product family published by Willuny Labs LLC. Amsonia Core is a public authorization engineering repository with its own scope. Amsonia Platform is the complete commercial Go + Next.js SaaS codebase published by Willuny Labs, adding production SaaS modules, updates, and broader operations tooling. See Amsonia Platform for the product and architecture context:

License

Amsonia Core is Apache-2.0. Amsonia Platform and modules outside this repository are licensed separately.

Documentation

Overview

Package amsonia provides an opinionated, tenant-safe delegated authorization kernel for Go SaaS applications. It is embedded directly into a host application and requires no additional authorization service.

The host application owns authentication, tenant binding, resource loading, and HTTP adaptation. Amsonia owns tenant-isolated RBAC policy evaluation and delegated administration.

Stability

The exported API is the implementation contract for v1. Field additions require a design revision; the package follows Go's compatibility promise within each major version.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidInput          = errors.New("amsonia: invalid input")
	ErrForbidden             = errors.New("amsonia: forbidden")
	ErrConflict              = errors.New("amsonia: version conflict")
	ErrNotFound              = errors.New("amsonia: not found")
	ErrGrantCycle            = errors.New("amsonia: grant cycle would be created")
	ErrLastAdministrator     = errors.New("amsonia: tenant would lose its last administrator")
	ErrAlreadyBootstrapped   = errors.New("amsonia: tenant already bootstrapped")
	ErrDependencyUnavailable = errors.New("amsonia: dependency unavailable")
	ErrAuditUnavailable      = errors.New("amsonia: audit unavailable")
	ErrInvalidPolicy         = errors.New("amsonia: invalid policy data")
)

Public sentinel errors. Adapters wrap underlying errors without changing these classifications.

Functions

func WithTenant

func WithTenant(ctx context.Context, tenantID TenantID) context.Context

WithTenant returns a context carrying an explicit tenant for store calls.

Types

type AssignRoleInput

type AssignRoleInput struct {
	SubjectID           SubjectID
	RoleID              RoleID
	ExpectedRoleVersion int64
}

AssignRoleInput binds a subject to a role with grant-cycle protection.

type AuditOutcome

type AuditOutcome string

AuditOutcome classifies an audit event.

const (
	AuditOutcomeSuccess  AuditOutcome = "success"
	AuditOutcomeNoop     AuditOutcome = "noop"
	AuditOutcomeDenied   AuditOutcome = "denied"
	AuditOutcomeConflict AuditOutcome = "conflict"
	AuditOutcomeFailed   AuditOutcome = "failed"
)

type AuditPhase

type AuditPhase string

AuditPhase distinguishes result, intent, and completion events.

const (
	AuditPhaseResult    AuditPhase = "result"
	AuditPhaseIntent    AuditPhase = "intent"
	AuditPhaseCompleted AuditPhase = "completed"
)

type Authorizer

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

Authorizer evaluates authorization requests against the catalog and the injected policy reader. It is safe for concurrent use.

func NewAuthorizer

func NewAuthorizer(
	catalog *Catalog,
	policies PolicyReader,
	memberships WorkspaceMembershipReader,
) (*Authorizer, error)

NewAuthorizer constructs an Authorizer. All dependencies are required; a nil dependency returns ErrInvalidInput.

func (*Authorizer) Check

func (a *Authorizer) Check(ctx context.Context, request CheckRequest) (Decision, error)

Check evaluates one exact permission for one principal within one tenant.

The result is deterministic and independent of adapter row order:

  • All loaded grants are validated first; any malformed persisted grant returns reason invalid_policy.
  • Valid candidates are evaluated in scope precedence tenant, workspace, own (matching delegation coverage order).
  • The first scope with a satisfying grant becomes EffectiveScope.
  • When no grant allows, reason precedence is invalid_request, tenant_mismatch, owner_mismatch, workspace_membership_missing, workspace_role_denied, then permission_not_granted.
  • Dependency failure returns an error with reason dependency_unavailable and never enters reason precedence.

type BootstrapAuthorizer

type BootstrapAuthorizer interface {
	AuthorizeBootstrap(ctx context.Context, tenantID TenantID) (HostProvenance, error)
}

BootstrapAuthorizer authenticates the host tenant-provisioning workflow and returns bounded non-subject initiator provenance.

type BootstrapInput

type BootstrapInput struct {
	TenantID             TenantID
	OwnerSubjectID       SubjectID
	OwnerRoleID          RoleID
	OwnerRoleName        string
	OwnerRoleDescription string
	Grants               []RolePermissionGrant
	Metadata             MutationMetadata
}

BootstrapInput is the tenant-provisioning payload. Grants must all reference OwnerRoleID and must include the three configured control permissions at tenant scope.

type Bootstrapper

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

Bootstrapper provisions an empty tenant exactly once. Bootstrap is exposed through a separate type, not ordinary Manager, and requires a host BootstrapAuthorizer.

func NewBootstrapper

func NewBootstrapper(
	catalog *Catalog,
	store Store,
	controls ControlPermissions,
	authorizer BootstrapAuthorizer,
	clock Clock,
) (*Bootstrapper, error)

NewBootstrapper constructs a Bootstrapper. All dependencies are required.

func (*Bootstrapper) BootstrapTenant

func (b *Bootstrapper) BootstrapTenant(ctx context.Context, input BootstrapInput) (Role, error)

BootstrapTenant atomically creates the initial owner role, required owner grants, owner assignment with bootstrap provenance, initial role snapshot, bootstrap marker, and audit event. It creates no normal grant edge.

A retained purge tombstone makes bootstrap permanently unavailable for a previously purged tenant ID.

type Catalog

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

Catalog is an immutable, validated set of application permission definitions. Construct with NewCatalog; the catalog cannot be mutated afterwards.

func NewCatalog

func NewCatalog(definitions []PermissionDefinition) (*Catalog, error)

NewCatalog validates and indexes permission definitions. Duplicate keys cause construction to fail.

func (*Catalog) Keys

func (c *Catalog) Keys() []PermissionKey

Keys returns the catalog keys in sorted order.

func (*Catalog) Lookup

func (c *Catalog) Lookup(key PermissionKey) (PermissionDefinition, bool)

Lookup returns the definition for a key and whether it exists.

type CheckRequest

type CheckRequest struct {
	Principal  Principal
	Permission PermissionKey
	Mode       ResourceMode
	Resource   ResourceContext
}

CheckRequest is a complete authorization request.

type Clock

type Clock interface {
	Now() time.Time
}

Clock is the injectable time source.

type ControlPermissions

type ControlPermissions struct {
	ManageRoles  PermissionKey
	ManageGrants PermissionKey
	AssignRoles  PermissionKey
}

ControlPermissions are the three application-defined management permission keys that gate delegated administration.

type CreateRoleInput

type CreateRoleInput struct {
	RoleID      RoleID
	Name        string
	Description string
}

CreateRoleInput creates an empty role at version 1.

type CreateRoleWithPermissionsInput

type CreateRoleWithPermissionsInput struct {
	RoleID      RoleID
	Name        string
	Description string
	Permissions []PermissionKey
}

CreateRoleWithPermissionsInput creates one role and its initial tenant-wide grants in a single transaction and immutable version snapshot.

type Decision

type Decision struct {
	Allowed        bool       `json:"allowed"`
	Reason         ReasonCode `json:"reason"`
	EffectiveScope Scope      `json:"effective_scope,omitempty"`
}

Decision is the public authorization outcome.

type DeleteRoleInput

type DeleteRoleInput struct {
	RoleID          RoleID
	ExpectedVersion int64
}

DeleteRoleInput tombstones a role with no permissions or subjects.

type EffectiveGrant

type EffectiveGrant struct {
	RoleID         RoleID
	Permission     PermissionKey
	Scope          Scope
	WorkspaceRoles []string
}

EffectiveGrant is the read-only view of one scoped grant held by a subject.

func (EffectiveGrant) Validate

func (g EffectiveGrant) Validate(requested PermissionKey) error

Validate checks a persisted grant against the catalog. It is exported for adapters and tests; the authorizer calls it on every loaded grant.

type GrantEdge

type GrantEdge struct {
	TenantID  TenantID
	Grantor   SubjectID
	Grantee   SubjectID
	RoleID    RoleID
	CreatedAt time.Time
}

GrantEdge records the grantor→grantee relationship used for cycle prevention. One edge exists per active normal assignment.

type GrantPermissionInput

type GrantPermissionInput struct {
	RoleID          RoleID
	ExpectedVersion int64
	Permission      PermissionKey
	Scope           Scope
	WorkspaceRoles  []string
}

GrantPermissionInput adds one scoped grant to a role.

type GrantProvenance

type GrantProvenance string

GrantProvenance distinguishes delegated from bootstrap provenance.

const (
	GrantProvenanceDelegated GrantProvenance = "delegated"
	GrantProvenanceBootstrap GrantProvenance = "bootstrap"
)

type HostProvenance

type HostProvenance struct {
	Initiator string
	At        time.Time
}

HostProvenance identifies a host-initiated operation such as bootstrap, export, or purge. It is not a subject identity.

type Maintenance

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

Maintenance exposes offline tenant export and purge. It is constructed separately from Manager and requires MaintenanceAuthorizer on every call.

func NewMaintenance

func NewMaintenance(
	store MaintenanceStore,
	authorizer MaintenanceAuthorizer,
	securityAudit SecurityAuditSink,
	clock Clock,
) (*Maintenance, error)

NewMaintenance constructs the maintenance API.

func (*Maintenance) ExportTenant

func (m *Maintenance) ExportTenant(ctx context.Context, tenantID TenantID) ([]byte, error)

ExportTenant returns a canonical, versioned snapshot of one tenant's authorization state. Export reads one consistent snapshot under the same per-tenant serialization used by normal mutations.

func (*Maintenance) PurgeTenant

func (m *Maintenance) PurgeTenant(ctx context.Context, tenantID TenantID, meta MutationMetadata) error

PurgeTenant deletes one tenant's authorization state after durable intent audit. A successful purge creates a permanent replay tombstone for that tenant ID; purged tenant IDs are never reused and cannot be bootstrapped again.

Purge requires a non-empty request ID. Retrying with the same request ID reads the retained ledger, returns AlreadyCommitted=true, performs no deletion, and retries only the missing completion event using the original canonical event.

type MaintenanceAuthorizer

type MaintenanceAuthorizer interface {
	AuthorizeMaintenance(ctx context.Context, tenantID TenantID, operation string) (HostProvenance, error)
}

MaintenanceAuthorizer authenticates offline maintenance operations.

type MaintenanceStore

type MaintenanceStore interface {
	MaintainTenant(ctx context.Context, tenantID TenantID, fn func(MaintenanceTx) error) error
}

MaintenanceStore serializes maintenance against normal mutations through the same per-tenant lock and serializable database boundary.

type MaintenanceTx

type MaintenanceTx interface {
	ExportTenant(ctx context.Context) ([]byte, error)
	PurgeTenant(ctx context.Context, event MutationAuditEvent) (PurgeResult, error)
}

MaintenanceTx scopes export and purge to one serialized tenant boundary.

type Manager

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

Manager executes tenant policy mutations through one serialized tenant transaction per call. Applications must not write roles or bindings directly.

func NewManager

func NewManager(
	catalog *Catalog,
	store Store,
	memberships WorkspaceMembershipReader,
	controls ControlPermissions,
	securityAudit SecurityAuditSink,
	clock Clock,
) (*Manager, error)

NewManager constructs a Manager. All dependencies are required.

func (*Manager) AssignRole

func (m *Manager) AssignRole(ctx context.Context, actor Principal, meta MutationMetadata, input AssignRoleInput) (MutationResult, error)

AssignRole binds a subject to a role. The actor must cover every grant in the caller-specified target role version and must not create a grant cycle.

func (*Manager) CreateRole

func (m *Manager) CreateRole(ctx context.Context, actor Principal, meta MutationMetadata, input CreateRoleInput) (Role, MutationResult, error)

CreateRole creates an empty role.

func (*Manager) CreateRoleWithPermissions

func (m *Manager) CreateRoleWithPermissions(ctx context.Context, actor Principal, meta MutationMetadata, input CreateRoleWithPermissionsInput) (Role, MutationResult, error)

CreateRoleWithPermissions atomically creates a useful role. The actor must manage roles and already cover every permission being delegated.

func (*Manager) DeleteRole

func (m *Manager) DeleteRole(ctx context.Context, actor Principal, meta MutationMetadata, input DeleteRoleInput) (MutationResult, error)

DeleteRole tombstones an empty role. The role must have no permissions or subjects; the role ID can never be reused.

func (*Manager) GrantPermission

func (m *Manager) GrantPermission(ctx context.Context, actor Principal, meta MutationMetadata, input GrantPermissionInput) (MutationResult, error)

GrantPermission adds a scoped grant. The actor must already hold the same exact permission with coverage over the target scope and constraints.

func (*Manager) RevokePermission

func (m *Manager) RevokePermission(ctx context.Context, actor Principal, meta MutationMetadata, input RevokePermissionInput) (MutationResult, error)

RevokePermission removes a scoped grant. The actor must cover the exact existing grant.

func (*Manager) UnassignRole

func (m *Manager) UnassignRole(ctx context.Context, actor Principal, meta MutationMetadata, input UnassignRoleInput) (MutationResult, error)

UnassignRole removes a subject-role binding and its grant edge.

func (*Manager) UpdateRole

func (m *Manager) UpdateRole(ctx context.Context, actor Principal, meta MutationMetadata, input UpdateRoleInput) (Role, MutationResult, error)

UpdateRole renames or re-describes a role with optimistic concurrency.

type MutationAuditEvent

type MutationAuditEvent struct {
	TenantID       TenantID     `json:"tenant_id"`
	ActorSubjectID SubjectID    `json:"actor_subject_id,omitempty"`
	HostInitiator  string       `json:"host_initiator,omitempty"`
	Operation      string       `json:"operation"`
	Phase          AuditPhase   `json:"phase"`
	TargetType     string       `json:"target_type"`
	TargetID       string       `json:"target_id"`
	Outcome        AuditOutcome `json:"outcome"`
	ReasonCode     string       `json:"reason_code,omitempty"`
	RequestID      string       `json:"request_id,omitempty"`
	RoleVersion    int64        `json:"role_version,omitempty"`
	At             time.Time    `json:"at"`
}

MutationAuditEvent is the append-only record of one administrative action.

type MutationMetadata

type MutationMetadata struct {
	RequestID  string
	ReasonCode string
}

MutationMetadata carries bounded audit context for administrative actions.

type MutationResult

type MutationResult struct {
	Changed     bool
	RoleVersion int64
}

MutationResult reports whether a mutation changed state and the resulting role version.

type PermissionDefinition

type PermissionDefinition struct {
	Key         PermissionKey `json:"key"`
	Description string        `json:"description"`
}

PermissionDefinition is an immutable application catalog entry.

type PermissionKey

type PermissionKey string

PermissionKey is an exact three-segment permission identifier.

func ParsePermissionKey

func ParsePermissionKey(raw string) (PermissionKey, error)

ParsePermissionKey validates raw and returns a canonical PermissionKey. Empty segments, uppercase, whitespace, wildcards, and extra segments are rejected.

func (PermissionKey) Validate

func (k PermissionKey) Validate() error

Validate checks the canonical three-segment permission grammar.

type PolicyReader

type PolicyReader interface {
	ListEffectiveGrants(
		ctx context.Context,
		tenantID TenantID,
		subjectID SubjectID,
		permission PermissionKey,
	) ([]EffectiveGrant, error)
}

PolicyReader loads effective grants for one subject and permission within a tenant. Implementations must scope all reads to the tenant passed in.

type Principal

type Principal struct {
	TenantID  TenantID  `json:"tenant_id"`
	SubjectID SubjectID `json:"subject_id"`
}

Principal is an authenticated subject within one tenant.

type PurgeResult

type PurgeResult struct {
	Changed          bool
	AlreadyCommitted bool
	CanonicalEvent   MutationAuditEvent
}

PurgeResult reports the outcome of a purge attempt.

type RealClock

type RealClock struct{}

RealClock returns the current time.

func (RealClock) Now

func (RealClock) Now() time.Time

Now implements Clock.

type ReasonCode

type ReasonCode string

ReasonCode is a stable machine-readable decision or failure reason.

const (
	ReasonAllowed                 ReasonCode = "allowed"
	ReasonInvalidRequest          ReasonCode = "invalid_request"
	ReasonPermissionNotGranted    ReasonCode = "permission_not_granted"
	ReasonTenantMismatch          ReasonCode = "tenant_mismatch"
	ReasonOwnerMismatch           ReasonCode = "owner_mismatch"
	ReasonWorkspaceMembershipMiss ReasonCode = "workspace_membership_missing"
	ReasonWorkspaceRoleDenied     ReasonCode = "workspace_role_denied"
	ReasonInvalidPolicy           ReasonCode = "invalid_policy"
	ReasonDependencyUnavailable   ReasonCode = "dependency_unavailable"
)

Stable public reason codes returned in decisions.

type ResourceContext

type ResourceContext struct {
	TenantID       TenantID    `json:"tenant_id"`
	ResourceID     ResourceID  `json:"resource_id"`
	OwnerSubjectID SubjectID   `json:"owner_subject_id"`
	WorkspaceID    WorkspaceID `json:"workspace_id"`
}

ResourceContext carries authoritative resource facts loaded by the host business service. Field requirements depend on the check mode and scope.

type ResourceID

type ResourceID string

ResourceID identifies a business resource addressed by a check.

func (ResourceID) Validate

func (r ResourceID) Validate() error

type ResourceMode

type ResourceMode string

ResourceMode distinguishes existing-resource, create, and tenant-level checks.

const (
	// ResourceExisting addresses an already-persisted resource.
	ResourceExisting ResourceMode = "existing"
	// ResourceCreate addresses a resource being created.
	ResourceCreate ResourceMode = "create"
	// ResourceTenantAction addresses a tenant-level action with no resource.
	ResourceTenantAction ResourceMode = "tenant_action"
)

Resource modes.

type RevokePermissionInput

type RevokePermissionInput = GrantPermissionInput

RevokePermissionInput removes one scoped grant from a role.

type Role

type Role struct {
	TenantID    TenantID `json:"tenant_id"`
	RoleID      RoleID   `json:"role_id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Version     int64    `json:"version"`
	Deleted     bool     `json:"deleted"`
}

Role is a tenant-scoped role with an optimistic-concurrency version.

type RoleID

type RoleID string

RoleID identifies a role within a tenant.

func (RoleID) Validate

func (r RoleID) Validate() error

type RolePermissionGrant

type RolePermissionGrant struct {
	RoleID         RoleID
	Permission     PermissionKey
	Scope          Scope
	WorkspaceRoles []string
}

RolePermissionGrant is one scoped permission grant on a role.

type RoleVersion

type RoleVersion struct {
	TenantID           TenantID
	RoleID             RoleID
	Version            int64
	Name               string
	Description        string
	Grants             []RolePermissionGrant
	Deleted            bool
	CreatedAt          time.Time
	CreatedBySubjectID SubjectID
	BootstrapInitiator string
}

RoleVersion is an immutable snapshot of a role's complete grants.

type Scope

type Scope string

Access scopes carried by role-permission grants.

const (
	ScopeOwn       Scope = "own"
	ScopeWorkspace Scope = "workspace"
	ScopeTenant    Scope = "tenant"
)

type SecurityAuditSink

type SecurityAuditSink interface {
	RecordSecurityEvent(ctx context.Context, event MutationAuditEvent) error
}

SecurityAuditSink durably records events that cannot live in a rolled-back mutation transaction: denied, conflict, failed, and maintenance lifecycle events. Implementations must be durable and deduplicate events by tenant, request ID, operation, and phase.

type Store

type Store interface {
	ReadTenant(ctx context.Context, tenantID TenantID, fn func(TenantReader) error) error
	MutateTenant(ctx context.Context, tenantID TenantID, fn func(TenantTx) error) error
}

Store is the persistence boundary. ReadTenant and MutateTenant scope a callback to one tenant.

MutateTenant must provide serializable behavior and exclusive mutation ordering for one tenant for the duration of the callback. A custom adapter that cannot provide that guarantee is not conformant.

type SubjectID

type SubjectID string

SubjectID identifies an authenticated principal within a tenant.

func (SubjectID) Validate

func (s SubjectID) Validate() error

type SubjectRoleGrant

type SubjectRoleGrant struct {
	TenantID         TenantID
	SubjectID        SubjectID
	RoleID           RoleID
	GrantorSubjectID SubjectID
	Provenance       GrantProvenance
	GrantedAt        time.Time
}

SubjectRoleGrant is a persisted subject-to-role binding.

type TenantID

type TenantID string

TenantID identifies one tenant's authorization domain.

func TenantFromContext

func TenantFromContext(ctx context.Context) (TenantID, bool)

TenantFromContext returns the tenant carried by the context.

func (TenantID) Validate

func (t TenantID) Validate() error

Validate checks that opaque IDs are usable and bounded.

type TenantReader

type TenantReader interface {
	ListEffectiveGrants(ctx context.Context, subjectID SubjectID, permission PermissionKey) ([]EffectiveGrant, error)
	GetRole(ctx context.Context, roleID RoleID) (Role, error)
	ListRolePermissionGrants(ctx context.Context, roleID RoleID) ([]RolePermissionGrant, error)
	ListSubjectRoleIDs(ctx context.Context, subjectID SubjectID) ([]RoleID, error)
	ListRoleSubjectIDs(ctx context.Context, roleID RoleID) ([]SubjectID, error)
	HasGrantPath(ctx context.Context, from SubjectID, to SubjectID) (bool, error)
	HasAdministrator(ctx context.Context, controls ControlPermissions) (bool, error)
	IsBootstrapped(ctx context.Context) (bool, error)
}

TenantReader provides read-only access inside one tenant.

type TenantTx

type TenantTx interface {
	TenantReader
	InsertRole(ctx context.Context, role Role) error
	UpdateRole(ctx context.Context, role Role, expectedVersion int64) error
	TombstoneRole(ctx context.Context, roleID RoleID, expectedVersion int64) error
	InsertRolePermissionGrant(ctx context.Context, grant RolePermissionGrant) (bool, error)
	DeleteRolePermissionGrant(ctx context.Context, grant RolePermissionGrant) (bool, error)
	InsertSubjectRoleGrant(ctx context.Context, grant SubjectRoleGrant) (bool, error)
	DeleteSubjectRoleGrant(ctx context.Context, subjectID SubjectID, roleID RoleID) (SubjectRoleGrant, bool, error)
	InsertGrantEdge(ctx context.Context, edge GrantEdge) error
	DeleteGrantEdge(ctx context.Context, edge GrantEdge) error
	InsertRoleVersion(ctx context.Context, snapshot RoleVersion) error
	InsertMutationAudit(ctx context.Context, event MutationAuditEvent) error
	MarkBootstrapped(ctx context.Context, provenance HostProvenance) error
}

TenantTx extends TenantReader with mutating operations. All operations run in one serialized tenant transaction; any returned error rolls back the transaction.

type UnassignRoleInput

type UnassignRoleInput = AssignRoleInput

UnassignRoleInput removes a subject-role binding.

type UpdateRoleInput

type UpdateRoleInput struct {
	RoleID          RoleID
	ExpectedVersion int64
	Name            string
	Description     string
}

UpdateRoleInput renames or re-describes a role.

type WorkspaceID

type WorkspaceID string

WorkspaceID identifies a workspace within a tenant.

func (WorkspaceID) Validate

func (w WorkspaceID) Validate() error

type WorkspaceMembership

type WorkspaceMembership struct {
	Role string
}

WorkspaceMembership is the host-provided membership fact for one workspace.

type WorkspaceMembershipReader

type WorkspaceMembershipReader interface {
	LookupWorkspaceMembership(
		ctx context.Context,
		tenantID TenantID,
		workspaceID WorkspaceID,
		subjectID SubjectID,
	) (WorkspaceMembership, error)
}

WorkspaceMembershipReader confirms workspace membership from authoritative host data. The workspace must belong to the tenant; a workspace that does not belong to the tenant or a subject that is not a member must return ErrNotFound and must not distinguish those cases.

Directories

Path Synopsis
cmd
amsonia command
Command amsonia provides operator workflows for Amsonia Core.
Command amsonia provides operator workflows for Amsonia Core.
amsonia-local command
Command amsonia-local runs the complete local Amsonia Core stack without putting generated database credentials in source control or shell history.
Command amsonia-local runs the complete local Amsonia Core stack without putting generated database credentials in source control or shell history.
amsonia-static command
Command amsonia-static serves generated Amsonia website assets without directory listings or implicit SPA fallbacks unless explicitly enabled.
Command amsonia-static serves generated Amsonia website assets without directory listings or implicit SPA fallbacks unless explicitly enabled.
api command
Command api runs the standalone Amsonia Core HTTP API.
Command api runs the standalone Amsonia Core HTTP API.
core-sync command
examples
business-data-rls command
Command business-data-rls proves that a host application's table can share Amsonia Core's signed PostgreSQL tenant boundary.
Command business-data-rls proves that a host application's table can share Amsonia Core's signed PostgreSQL tenant boundary.
saas command
Command saas demonstrates the complete Amsonia lifecycle for a Go SaaS application: bootstrap a tenant, delegate roles, authorize requests, and audit changes.
Command saas demonstrates the complete Amsonia lifecycle for a Go SaaS application: bootstrap a tenant, delegate roles, authorize requests, and audit changes.
internal
coreapp
Package coreapp implements the standalone Amsonia Core identity and tenant application service around the public authorization kernel.
Package coreapp implements the standalone Amsonia Core identity and tenant application service around the public authorization kernel.
coresync
Package coresync copies a declared set of files and records deterministic provenance.
Package coresync copies a declared set of files and records deterministic provenance.
Package memory provides a deterministic in-memory adapter for tests and local development.
Package memory provides a deterministic in-memory adapter for tests and local development.
Package postgres provides the production PostgreSQL adapter for Amsonia.
Package postgres provides the production PostgreSQL adapter for Amsonia.

Jump to

Keyboard shortcuts

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