org

package
v1.5.64 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

The wire base reaches Hanzo IAM on.

IAM is a zip app (github.com/zap-proto/zip). zip serves ONE route tree across every address it listens on, and the address SCHEME names the transport: a bare host:port is ZAP, "http://" is HTTP. IAM's deployment listens on both — `iam serve --zap :9653 --http http://:8000` — so /v1/iam/oauth/userinfo, /v1/iam/get-user and the JWKS answer identically on either wire.

So this is not a second client, a second protocol, or a second set of paths. It is the same request, addressed the same way, carried over the transport the configured endpoint names — the one vocabulary zip already uses for Listen and Mount. An https:// endpoint keeps net/http exactly as before.

What the ZAP address buys is the hop, not the encoding: https://hanzo.id leaves the cluster, crosses the public edge and comes back to a pod one hop away, and it ties base's credential path to that edge being up. zap:// iam.hanzo.svc.cluster.local:9653 is the pod.

KMS bridge for the base/platform plugin.

The plugin needs three operations per org:

  • GetSecret(orgId, secretPath) — read a per-org credential
  • SetSecret(orgId, secretPath, value) — write a per-org credential
  • DeleteSecret(orgId, secretPath) — remove a per-org credential

All three go to the ONE secrets store over the ONE in-cluster transport: native ZAP to github.com/luxfi/kms, the canonical KMS. There is no HTTP path. Secrets never live anywhere else.

THE ORG IS A PATH SEGMENT. The KMS store shards its base boundary on the "orgs/{org}" prefix of the secret PATH; a path without that segment is deployment-wide and shared by every base. The previous implementation carried the org in an HTTP URL (/v1/kms/orgs/{org}/secrets/…) and passed the caller's bare path straight through on the ZAP transport — so on the transport operators are told to use in-cluster, every org read and wrote the same deployment-wide record. ref() below is the one place that mapping lives.

OrgService provides per-org configuration, credential resolution, and customer identity management. Registered in app.Store() as "org" so Base Functions (Goja JS) can call methods directly:

var org = $app.store().get("org")
var creds = org.getCreds(orgId, "commerce")
var config = org.getConfig(orgId)
var customer = org.getCustomer(orgId, userId)

Package org gives one Base process a Base per org.

An org's Base is a Base of its own under {DataDir}/orgs/{org}/, opened the first time a request arrives carrying that org. Isolation is physical: a different org is a different file, so there is no query that can read across two. The file is opened under that org's own key — see [encryptedConnect] — so one org's data is unreadable with another's, and a deployment that configures no master key opens plaintext and says so rather than pretending otherwise.

Orgs and members are IAM's, read off the validated token. This package never writes them — a local copy is a second answer to "who is in this org", and the one a request arrives on wins.

It publishes /v1/bases: which Bases the caller can reach, and what state each is in. There is no create verb; using an org opens its Base.

org.MustRegister(app, org.Config{
	IAMEndpoint:     "https://hanzo.id",
	KMSEndpoint:     "zap.kms.svc.cluster.local:9999",
	IAMClientID:     "my-client-id",
	IAMClientSecret: "my-client-secret",
})

Index

Constants

This section is empty.

Variables

View Source
var ErrKMSNotConfigured = errors.New("kms: endpoint not configured")

ErrKMSNotConfigured is returned by every operation when no KMS endpoint is set. Callers treat it as "this deployment has no KMS" and fall back to the process environment; it is never a transport failure.

Functions

func CreateOrgCollections

func CreateOrgCollections(app core.App, orgSlug string, templates []CollectionTemplate) error

CreateOrgCollections creates prefixed collections for an org from the given templates. Each template's Name is prefixed with t_{slug}_.

Collections that already exist are skipped.

func DeleteOrgCollections

func DeleteOrgCollections(app core.App, orgSlug string) error

DeleteOrgCollections removes all collections with the org's prefix.

func ExchangeOAuth2Token

func ExchangeOAuth2Token(code, redirectURI string, config Config) (accessToken, refreshToken string, err error)

ExchangeOAuth2Token exchanges an authorization code for tokens using the IAM OAuth2 token endpoint.

func IDVEndpoint

func IDVEndpoint() string

IDVEndpoint returns the configured upstream IDV service URL, or "" when IDV is disabled. The trailing slash is stripped so callers can always concatenate "/v1/idv/...".

func IsAPIKey

func IsAPIKey(token string) bool

IsAPIKey returns true if the token is any type of IAM API key.

func IsAnalyticsKey

func IsAnalyticsKey(token string) bool

IsAnalyticsKey returns true if the token is an insights or analytics key.

func IsPublishableKey

func IsPublishableKey(token string) bool

IsPublishableKey returns true if the token has a publishable key prefix.

func IsSecretKey

func IsSecretKey(token string) bool

IsSecretKey returns true if the token has a secret key prefix.

func IsWidgetKey

func IsWidgetKey(token string) bool

IsWidgetKey returns true if the token is a widget embed key.

func ListOrgCollections

func ListOrgCollections(app core.App, orgSlug string) ([]string, error)

ListOrgCollections returns all collection names belonging to an org.

func MustRegister

func MustRegister(app core.App, config Config)

MustRegister registers the platform plugin to the provided app instance and panics if it fails.

func OrgPrefix

func OrgPrefix(slug string) string

OrgPrefix returns the collection name prefix for an org slug. Format: t_{slug}_

func Register

func Register(app core.App, config Config) error

Register registers the platform plugin to the provided app instance.

Hanzo Base is a pure IAM client — it never hosts identity. IAM must be reachable at boot via IAM_ENDPOINT (a hanzo.id base, or an in-process iam.Embed() served by the fused daemon). Base validates IAM JWTs against that endpoint's JWKS; there is no local password / OTP / MFA surface.

func ScopedQuery

func ScopedQuery(orgSlug, collection string) string

ScopedQuery returns the prefixed collection name for an org. Example: ScopedQuery("acme", "tasks") returns "t_acme_tasks".

Types

type AdminCreds

type AdminCreds struct {
	ClientID     string
	ClientSecret string
	Owner        string // default org for lookups when caller doesn't specify
}

AdminCreds holds the service's IAM application credentials. Pass these to the client via SetAdminCreds before invoking server-to-server methods.

type CollectionTemplate

type CollectionTemplate struct {
	// Name is the base collection name (without org prefix).
	// The actual collection will be created as t_{slug}_{Name}.
	Name string

	// Type is the collection type: "base", "auth", or "view".
	// Defaults to "base" if empty.
	Type string

	// Fields defines the fields for the collection.
	Fields []core.Field
}

CollectionTemplate defines a collection schema that gets cloned per org.

type ComplianceClient

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

ComplianceClient handles communication with the luxfi/compliance service. The compliance service provides KYC/AML, sanctions screening, transaction monitoring, and regulatory validation. This is an optional extension — if ComplianceEndpoint is empty, compliance features are disabled.

func NewComplianceClient

func NewComplianceClient(baseURL, apiKey string) *ComplianceClient

NewComplianceClient creates a client for the compliance service.

func (*ComplianceClient) CreateApplication

func (c *ComplianceClient) CreateApplication(givenName, familyName, email, country string) (string, error)

CreateApplication creates a compliance application for a user.

func (*ComplianceClient) Enabled

func (c *ComplianceClient) Enabled() bool

Enabled returns true if the compliance client is configured.

func (*ComplianceClient) GetKYCStatus

func (c *ComplianceClient) GetKYCStatus(applicationID string) (*ComplianceStatus, error)

GetKYCStatus returns the current KYC status for an application.

func (*ComplianceClient) InitiateKYC

func (c *ComplianceClient) InitiateKYC(applicationID, provider string) (verificationID, redirectURL string, err error)

InitiateKYC starts identity verification for an application.

func (*ComplianceClient) ScreenIndividual

func (c *ComplianceClient) ScreenIndividual(givenName, familyName, country string) (*ScreeningResult, error)

ScreenIndividual runs AML/sanctions screening.

func (*ComplianceClient) ValidatePayment

func (c *ComplianceClient) ValidatePayment(fromID, toID string, amount float64, currency, jurisdiction string) (approved bool, reason string, err error)

ValidatePayment checks payment compliance (travel rule, sanctions, CTR).

type ComplianceStatus

type ComplianceStatus struct {
	ApplicationID string `json:"application_id"`
	Status        string `json:"status"`     // draft, pending, pending_kyc, approved, rejected
	KYCStatus     string `json:"kyc_status"` // not_started, pending, verified, failed
	KYCProvider   string `json:"kyc_provider,omitempty"`
}

ComplianceStatus represents a user's KYC/compliance status.

type Config

type Config struct {
	// IAMEndpoint is the base URL for Hanzo IAM (default: "https://hanzo.id").
	//
	// It names the BRAND, so minting a token always addresses it: IAM derives
	// the issuer from the request host, and a relying party that discovered
	// through one brand refuses a token issued by another.
	IAMEndpoint string

	// IAMAddress is where the service answers, when that is not where the brand
	// lives. Reading takes it — validating a token, resolving a key — because
	// none of those answers depend on which brand was addressed.
	//
	// A brand's public origin leaves the cluster and comes back to a pod one
	// hop away. "iam.hanzo.svc.cluster.local:9653" is the pod, over ZAP, which
	// is what a bare address means. Empty means read the brand's origin.
	IAMAddress string

	// KMSEndpoint is the KMS ZAP address — "host:port", "zap://host:port" or
	// "zap+mdns://_kms._tcp" (default: "zap.kms.svc.cluster.local:9999").
	// An http(s) URL is rejected at Register: Base speaks native ZAP to KMS.
	KMSEndpoint string

	// IAMClientID is the OAuth2 client ID for IAM authentication.
	IAMClientID string

	// IAMClientSecret is the OAuth2 client secret for IAM authentication.
	IAMClientSecret string

	// IAMOrg is the IAM organization identifier (optional, used by auth proxy).
	IAMOrg string

	// IAMApp is the IAM application identifier (optional, used by auth proxy).
	IAMApp string

	// ComplianceEndpoint is the base URL for Lux Compliance service (optional).
	// If set, enables KYC/AML screening and payment compliance for orgs.
	ComplianceEndpoint string

	// ComplianceAPIKey is the API key for the compliance service.
	ComplianceAPIKey string

	// PrincipalEncryptionKey is the master key per-principal keys are derived
	// from, by github.com/hanzoai/cek — see OrgDB.OrgDEK and OrgDB.UserDEK for
	// the namespace each one uses. It must be 32 bytes, which is what a master
	// key from KMS is. If empty, encryption is disabled (dev mode).
	PrincipalEncryptionKey string

	// Deprecated: use PrincipalEncryptionKey.
	OrgEncryptionKey string

	// OrgStorageEndpoint is the S3-compatible storage endpoint for per-org
	// object storage (e.g., "s3.hanzo.space" or "s3.hanzo.ai").
	// Each org and user gets isolated prefixes with SSE-C encryption.
	// If empty, no per-org S3 storage is provisioned.
	OrgStorageEndpoint string

	// OrgStorageBucket is the root S3 bucket name (default: "orgs").
	OrgStorageBucket string

	// DefaultTemplates defines collection schemas cloned per org on creation.
	// If nil, no default org collections are created.
	DefaultTemplates []CollectionTemplate
}

Config defines the configuration for the platform plugin.

type EnsureUserSpec

type EnsureUserSpec struct {
	Owner       string // org slug (defaults to client's admin Owner if empty)
	Email       string // primary lookup key for existing users
	Name        string // username; auto-generated by IAM if empty
	DisplayName string
	Phone       string
	Type        string // IAM user type, e.g. "normal-user"
}

EnsureUserSpec describes a user to provision idempotently via EnsureUser.

type IAMClient

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

IAMClient handles authentication against Hanzo IAM with token caching.

Cache: a TTL-expirable LRU (hashicorp/golang-lru/v2) per credential kind — ValidateToken reads tokens, ResolveAPIKey reads keys — because the two expire on different clocks. LRU evicts the oldest entry when a cache is full. No O(n) eviction scans.

Singleflight: golang.org/x/sync/singleflight coalesces concurrent validation requests for the same token into a single upstream IAM call. Under load (N goroutines validating the same JWT simultaneously), only one HTTP request hits IAM; the remaining N-1 wait and reuse the result.

func NewIAMClient

func NewIAMClient(baseURL string) *IAMClient

NewIAMClient creates a new IAM client pointed at the given base URL with the default cache capacity (10,000 entries).

func NewIAMClientWithCache

func NewIAMClientWithCache(baseURL string, cacheSize int) *IAMClient

NewIAMClientWithCache creates a new IAM client with a custom cache capacity. cacheSize must be > 0; values <= 0 fall back to defaultCacheSize.

func (*IAMClient) EnsureUser

func (c *IAMClient) EnsureUser(ctx context.Context, spec EnsureUserSpec) (*IAMUser, error)

EnsureUser idempotently provisions an IAM user matching spec. If the user already exists (matched by email within spec.Owner), the existing user is returned without modification. Otherwise the user is created via POST /v1/iam/add-user and the new user is fetched and returned.

EnsureUser treats both HTTP 409 and IAM's status:"error" + "already exists" envelope as the idempotent-replay path — IAM responds with HTTP 200 in either case depending on version, and both shapes mean "this user is already there, fetch it".

spec.Email is required (used as the dedup key). spec.Owner defaults to the client's admin Owner if empty.

func (*IAMClient) InvalidateToken

func (c *IAMClient) InvalidateToken(token string)

InvalidateToken drops a credential from the cache. Safe to call for either JWT bearer tokens or pk-/sk-/hk- API keys — a value is only ever in the one cache that holds its kind, and removing what is not there is nothing.

func (*IAMClient) LookupByAttribute

func (c *IAMClient) LookupByAttribute(ctx context.Context, attr, value, org string, maxResults int) ([]IAMUser, error)

LookupByAttribute performs a server-to-server lookup of users matching attr=value within org. attr is an IAM user field name ("phone", "email", "name", etc.). org defaults to the client's admin Owner if empty. maxResults caps the page size; values <= 0 default to 10.

For attr=="phone", LookupByAttribute probes multiple phone normalizations (raw, with leading "+", US +1 stripped) since IAM stores phones in inconsistent shapes depending on the signup path.

Returns ([], nil) when no user matches — never an error for empty results. Errors are returned only for transport / decoding / IAM-side error responses.

func (*IAMClient) ResolveAPIKey

func (c *IAMClient) ResolveAPIKey(accessKey string) (*IAMUser, error)

ResolveAPIKey resolves an IAM API key (hk-/pk-/sk-) to user + org context. Uses IAM's GET /v1/iam/get-user?accessKey= endpoint. Results are cached for tokenCacheTTL; concurrent resolves of the same key are coalesced via singleflight.

func (*IAMClient) SetAdminCreds

func (c *IAMClient) SetAdminCreds(creds AdminCreds)

SetAdminCreds installs the service-level credentials used by LookupByAttribute, EnsureUser, and other server-to-server methods. Safe to call once at startup.

func (*IAMClient) ValidateToken

func (c *IAMClient) ValidateToken(token string) (*IAMUser, error)

ValidateToken validates a Bearer token against IAM userinfo. Results are cached for tokenCacheTTL (5 minutes). Concurrent validations of the same token are coalesced into a single upstream call via singleflight.

type IAMKey

type IAMKey struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	Type        string `json:"type"` // Organization, Application, User
	Org         string `json:"organization"`
	Application string `json:"application"`
	User        string `json:"user"`
	AccessKey   string `json:"accessKey"`
	State       string `json:"state"`
}

IAMKey represents an API key from IAM's Key table.

type IAMUser

type IAMUser struct {
	ID     string   `json:"id"`
	Email  string   `json:"email"`
	Name   string   `json:"name"`
	OrgIDs []string `json:"orgIds"`
}

IAMUser represents an authenticated user from Hanzo IAM.

func ValidateIAMToken

func ValidateIAMToken(token string, config Config) (*IAMUser, error)

ValidateIAMToken validates a bearer token against the IAM userinfo endpoint at config.IAMEndpoint/v1/iam/oauth/userinfo.

This is a convenience function that creates a one-off HTTP request. For production use with caching, use the IAMClient returned by NewIAMClient.

type KMSClient

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

KMSClient is the platform-side facade over the canonical KMS client.

One connection serves every org — the org rides in the secret path, not in the connection — and it is dialled lazily on the first secret call so a deployment without KMS never touches the network. A failed dial or a transport error drops the connection so the next call redials; a KMS restart does not permanently disable secrets for the process lifetime.

func NewKMSClient

func NewKMSClient(endpoint string) (*KMSClient, error)

NewKMSClient builds the bridge for a configured KMS endpoint.

endpoint is "zap://host:port", "zap+mdns://_kms._tcp", or a bare "host:port". Empty means "this deployment has no KMS" — every call then returns ErrKMSNotConfigured and the caller falls back to the environment. An http(s) endpoint is a misconfiguration and is rejected here, where an operator sees it, rather than degrading every read into the env fallback at runtime.

func (*KMSClient) Close

func (c *KMSClient) Close()

Close releases the KMS connection. Safe on a nil or unconfigured client.

func (*KMSClient) DeleteSecret

func (c *KMSClient) DeleteSecret(orgId, secretPath string) error

DeleteSecret removes a secret.

func (*KMSClient) GetSecret

func (c *KMSClient) GetSecret(orgId, secretPath string) (string, error)

GetSecret fetches a secret with caching (1 min TTL).

func (*KMSClient) InvalidateCache

func (c *KMSClient) InvalidateCache(orgId string)

InvalidateCache clears all cached secrets for an org.

func (*KMSClient) SetSecret

func (c *KMSClient) SetSecret(orgId, secretPath, value string) error

SetSecret creates or updates a secret.

type OrgDB

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

OrgDB manages per-org AND per-user SQLite databases.

Directory layout:

{DataDir}/orgs/{orgSlug}/data.db                 ← the org's Base
{DataDir}/orgs/{orgSlug}/users/{userId}/data.db  ← per-user PII + keys

An org's directory is the data dir of a Base, so the file in it is named the way every Base names its data file. It was org.db, which is one name too many: the directory already says whose it is, and the second name made the file look like something other than a Base.

Each file gets its own key, derived by github.com/hanzoai/cek from the master key and the namespace naming whose data it is:

org DEK  = cek.DeriveKey(master, org/{orgSlug},           "org")
user DEK = cek.DeriveKey(master, org/{orgSlug}/{userId},  "user")

Zero data commingling — org data and user PII live in separate files under separate keys. TestOrgDB_DEK_IsCEKDerivation holds these two lines to the code, because a comment about a derivation cannot fail when the derivation drifts and this package has been wrong that way before.

func NewOrgDB

func NewOrgDB(app core.App, masterKey string) *OrgDB

func (*OrgDB) DeleteOrg

func (t *OrgDB) DeleteOrg(orgSlug string) error

DeleteOrg removes an org's entire directory (including all user databases).

func (*OrgDB) DeleteUser

func (t *OrgDB) DeleteUser(orgSlug, userId string) error

DeleteUser removes a user's database directory.

func (*OrgDB) GetOrgDBPath

func (t *OrgDB) GetOrgDBPath(orgSlug string) (string, bool)

GetOrgDBPath returns the database path for an existing org.

func (*OrgDB) GetUserDBPath

func (t *OrgDB) GetUserDBPath(orgSlug, userId string) (string, bool)

GetUserDBPath returns the database path for an existing user.

func (*OrgDB) ListOrgs

func (t *OrgDB) ListOrgs() ([]string, error)

ListOrgs returns all provisioned org slugs.

func (*OrgDB) ListUsers

func (t *OrgDB) ListUsers(orgSlug string) ([]string, error)

ListUsers returns all provisioned user IDs within an org.

func (*OrgDB) OrgDBPath

func (t *OrgDB) OrgDBPath(orgSlug string) string

OrgDBPath returns the org-level SQLite database path.

func (*OrgDB) OrgDEK

func (t *OrgDB) OrgDEK(orgSlug string) (string, error)

OrgDEK derives the org-level data encryption key.

func (*OrgDB) OrgDir

func (t *OrgDB) OrgDir(orgSlug string) string

OrgDir returns the directory for an org. Validates slug to prevent path traversal.

func (*OrgDB) OrgsDir

func (t *OrgDB) OrgsDir() string

OrgsDir returns the base directory for all org databases.

func (*OrgDB) ProvisionOrg

func (t *OrgDB) ProvisionOrg(orgSlug string) (string, error)

ProvisionOrg creates an org's directory and returns it. What goes in it is the Base's business — see [bases.base], which opens one there.

It deliberately does not record the org as having a database. It used to, which meant an org counted as provisioned the moment a directory existed and /v1/bases reported a Base that was not there yet.

func (*OrgDB) ProvisionUser

func (t *OrgDB) ProvisionUser(orgSlug, userId string) (string, error)

ProvisionUser creates the per-user directory and database.

func (*OrgDB) UserDBPath

func (t *OrgDB) UserDBPath(orgSlug, userId string) string

UserDBPath returns the per-user SQLite database path.

func (*OrgDB) UserDEK

func (t *OrgDB) UserDEK(orgSlug, userId string) (string, error)

UserDEK derives the per-user data encryption key. User PII gets its own key, separate from the org's.

The org rides in the namespace alongside the user, so acme/alice and globex/alice stay different keys even where two orgs use the same user id.

namespace.Of is the door here, rather than OrgProject, because these ids are already slugs by this package's own contract — validateSlug, and the org's directory is named with the raw slug. Sanitizing here and not there would key a file by one rendering of a name and place it by another; erroring instead keeps the key and the path reading the same string.

func (*OrgDB) UserDir

func (t *OrgDB) UserDir(orgSlug, userId string) string

UserDir returns the directory for a specific user within an org.

type OrgService

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

OrgService provides per-org configuration, credential resolution, and customer identity management.

func (*OrgService) BindComplianceApp added in v1.5.26

func (s *OrgService) BindComplianceApp(orgId, userId, applicationId string) error

BindComplianceApp records that a compliance application belongs to one org's user, so that a later read of it can be answered.

The vendor's application id is a bare string that arrives in a URL and says nothing about who created it, so without this there is no question to ask and every caller reaches every application.

func (*OrgService) ComplianceApp added in v1.5.26

func (s *OrgService) ComplianceApp(orgId, applicationId string) (string, bool)

ComplianceApp reports which of an org's users holds a compliance application. An org that holds no such application answers false, which is what a caller naming another org's application gets.

func (*OrgService) GetConfig

func (s *OrgService) GetConfig(orgId string) map[string]any

GetConfig returns the org_configs record for an org. Cached 5min.

func (*OrgService) GetCreds

func (s *OrgService) GetCreds(orgId, provider string) map[string]string

GetCreds fetches per-org credentials from KMS. Path convention: /orgs/{orgId}/{provider}/{key} Returns map like {"api_key": "...", "api_secret": "...", "base_url": "..."} Cached 5min per (orgId, provider) pair.

An org that has not configured a provider has no credentials for it, and that is the answer. There used to be a fallback that read os.Getenv(PROVIDER + "_API_KEY") when KMS held no row — and `provider` is a path segment the caller writes, so any org could name openai, anthropic or github and be handed the DEPLOYMENT's key: the process environment, which is where the KMSSecret CRDs put the platform's own secrets. A tenant asked for its own credentials and got the operator's. The read is KMS or nothing.

func (*OrgService) GetCustomer

func (s *OrgService) GetCustomer(orgId, userId string) map[string]any

GetCustomer looks up the org_customers record for (orgId, userId).

func (*OrgService) GetOrProvisionCustomer

func (s *OrgService) GetOrProvisionCustomer(orgId, userId string) (map[string]any, error)

GetOrProvisionCustomer returns existing customer or creates one.

func (*OrgService) InvalidateCreds

func (s *OrgService) InvalidateCreds(orgId string)

InvalidateCreds clears the credential cache for an org (all providers).

func (*OrgService) ProvisionCustomer

func (s *OrgService) ProvisionCustomer(orgId, userId string, opts map[string]any) (map[string]any, error)

ProvisionCustomer creates a new customer identity for a user in an org. Generates a sequential customer_id, creates the record.

func (*OrgService) SetCreds

func (s *OrgService) SetCreds(orgId, provider string, creds map[string]string) error

SetCreds stores credentials in KMS for an org+provider.

type OrgStorage

type OrgStorage struct {
	// Endpoint is the S3 endpoint (e.g., "s3.hanzo.space:9000" or "s3.hanzo.ai").
	Endpoint string

	// Bucket is the root bucket name (e.g., "orgs").
	Bucket string

	// MasterKey for deriving per-org and per-user SSE keys.
	MasterKey string

	// UseSSL enables TLS for the S3 connection.
	UseSSL bool

	// Region for the S3 bucket (default: "us-east-1").
	Region string
}

OrgStorage manages per-org and per-user S3 bucket isolation.

Bucket layout on Hanzo S3 (s3.hanzo.space):

orgs/{orgSlug}/                         ← org bucket prefix
orgs/{orgSlug}/org/                     ← org-level shared data
orgs/{orgSlug}/users/{userId}/          ← per-user isolated storage

Each org gets its own SSE-KMS key derived from the master key. Each user gets their own SSE-C key for client-side encryption.

IAM policy ensures:

  • Org admins can access orgs/{orgSlug}/*
  • Users can only access orgs/{orgSlug}/users/{userId}/*
  • No cross-org or cross-user access possible

func (*OrgStorage) BucketPolicy

func (s *OrgStorage) BucketPolicy(orgSlug, iamUser string) string

BucketPolicy returns a MinIO/S3 bucket policy JSON that enforces per-org path isolation. Uses encoding/json to prevent injection via orgSlug/iamUser.

func (*OrgStorage) OrgDataPrefix

func (s *OrgStorage) OrgDataPrefix(orgSlug string) string

OrgDataPrefix returns the S3 key prefix for org-level shared data.

func (*OrgStorage) OrgPrefix

func (s *OrgStorage) OrgPrefix(orgSlug string) string

OrgPrefix returns the S3 key prefix for an org.

func (*OrgStorage) OrgSSEKey

func (s *OrgStorage) OrgSSEKey(orgSlug string) (string, error)

OrgSSEKey derives a per-org SSE-C encryption key (32 bytes, base64-encoded). Used as the SSE-C CustomerKey header for server-side encryption.

func (*OrgStorage) UserBucketPolicy

func (s *OrgStorage) UserBucketPolicy(orgSlug, userId, iamUser string) string

UserBucketPolicy returns a policy restricting a user to their own prefix only.

func (*OrgStorage) UserPrefix

func (s *OrgStorage) UserPrefix(orgSlug, userId string) string

UserPrefix returns the S3 key prefix for a specific user.

func (*OrgStorage) UserSSEKey

func (s *OrgStorage) UserSSEKey(orgSlug, userId string) (string, error)

UserSSEKey derives a per-user SSE-C encryption key (32 bytes, base64-encoded). Each user's objects are encrypted with a unique key — even if the bucket is shared, objects cannot be decrypted without the user-specific key.

type ScreeningResult

type ScreeningResult struct {
	RiskLevel string `json:"risk_level"` // low, medium, high, critical
	Matches   int    `json:"matches"`
	Cleared   bool   `json:"cleared"`
}

ScreeningResult from AML/sanctions screening.

Jump to

Keyboard shortcuts

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