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 ¶
- Variables
- func CreateOrgCollections(app core.App, orgSlug string, templates []CollectionTemplate) error
- func DeleteOrgCollections(app core.App, orgSlug string) error
- func ExchangeOAuth2Token(code, redirectURI string, config Config) (accessToken, refreshToken string, err error)
- func IDVEndpoint() string
- func IsAPIKey(token string) bool
- func IsAnalyticsKey(token string) bool
- func IsPublishableKey(token string) bool
- func IsSecretKey(token string) bool
- func IsWidgetKey(token string) bool
- func ListOrgCollections(app core.App, orgSlug string) ([]string, error)
- func MustRegister(app core.App, config Config)
- func OrgPrefix(slug string) string
- func Register(app core.App, config Config) error
- func ScopedQuery(orgSlug, collection string) string
- type AdminCreds
- type CollectionTemplate
- type ComplianceClient
- func (c *ComplianceClient) CreateApplication(givenName, familyName, email, country string) (string, error)
- func (c *ComplianceClient) Enabled() bool
- func (c *ComplianceClient) GetKYCStatus(applicationID string) (*ComplianceStatus, error)
- func (c *ComplianceClient) InitiateKYC(applicationID, provider string) (verificationID, redirectURL string, err error)
- func (c *ComplianceClient) ScreenIndividual(givenName, familyName, country string) (*ScreeningResult, error)
- func (c *ComplianceClient) ValidatePayment(fromID, toID string, amount float64, currency, jurisdiction string) (approved bool, reason string, err error)
- type ComplianceStatus
- type Config
- type EnsureUserSpec
- type IAMClient
- func (c *IAMClient) EnsureUser(ctx context.Context, spec EnsureUserSpec) (*IAMUser, error)
- func (c *IAMClient) InvalidateToken(token string)
- func (c *IAMClient) LookupByAttribute(ctx context.Context, attr, value, org string, maxResults int) ([]IAMUser, error)
- func (c *IAMClient) ResolveAPIKey(accessKey string) (*IAMUser, error)
- func (c *IAMClient) SetAdminCreds(creds AdminCreds)
- func (c *IAMClient) ValidateToken(token string) (*IAMUser, error)
- type IAMKey
- type IAMUser
- type KMSClient
- type OrgDB
- func (t *OrgDB) DeleteOrg(orgSlug string) error
- func (t *OrgDB) DeleteUser(orgSlug, userId string) error
- func (t *OrgDB) GetOrgDBPath(orgSlug string) (string, bool)
- func (t *OrgDB) GetUserDBPath(orgSlug, userId string) (string, bool)
- func (t *OrgDB) ListOrgs() ([]string, error)
- func (t *OrgDB) ListUsers(orgSlug string) ([]string, error)
- func (t *OrgDB) OrgDBPath(orgSlug string) string
- func (t *OrgDB) OrgDEK(orgSlug string) (string, error)
- func (t *OrgDB) OrgDir(orgSlug string) string
- func (t *OrgDB) OrgsDir() string
- func (t *OrgDB) ProvisionOrg(orgSlug string) (string, error)
- func (t *OrgDB) ProvisionUser(orgSlug, userId string) (string, error)
- func (t *OrgDB) UserDBPath(orgSlug, userId string) string
- func (t *OrgDB) UserDEK(orgSlug, userId string) (string, error)
- func (t *OrgDB) UserDir(orgSlug, userId string) string
- type OrgService
- func (s *OrgService) BindComplianceApp(orgId, userId, applicationId string) error
- func (s *OrgService) ComplianceApp(orgId, applicationId string) (string, bool)
- func (s *OrgService) GetConfig(orgId string) map[string]any
- func (s *OrgService) GetCreds(orgId, provider string) map[string]string
- func (s *OrgService) GetCustomer(orgId, userId string) map[string]any
- func (s *OrgService) GetOrProvisionCustomer(orgId, userId string) (map[string]any, error)
- func (s *OrgService) InvalidateCreds(orgId string)
- func (s *OrgService) ProvisionCustomer(orgId, userId string, opts map[string]any) (map[string]any, error)
- func (s *OrgService) SetCreds(orgId, provider string, creds map[string]string) error
- type OrgStorage
- func (s *OrgStorage) BucketPolicy(orgSlug, iamUser string) string
- func (s *OrgStorage) OrgDataPrefix(orgSlug string) string
- func (s *OrgStorage) OrgPrefix(orgSlug string) string
- func (s *OrgStorage) OrgSSEKey(orgSlug string) (string, error)
- func (s *OrgStorage) UserBucketPolicy(orgSlug, userId, iamUser string) string
- func (s *OrgStorage) UserPrefix(orgSlug, userId string) string
- func (s *OrgStorage) UserSSEKey(orgSlug, userId string) (string, error)
- type ScreeningResult
Constants ¶
This section is empty.
Variables ¶
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 ¶
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 IsAnalyticsKey ¶
IsAnalyticsKey returns true if the token is an insights or analytics key.
func IsPublishableKey ¶
IsPublishableKey returns true if the token has a publishable key prefix.
func IsSecretKey ¶
IsSecretKey returns true if the token has a secret key prefix.
func IsWidgetKey ¶
IsWidgetKey returns true if the token is a widget embed key.
func ListOrgCollections ¶
ListOrgCollections returns all collection names belonging to an org.
func MustRegister ¶
MustRegister registers the platform plugin to the provided app instance and panics if it fails.
func Register ¶
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 ¶
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 ¶
NewIAMClient creates a new IAM client pointed at the given base URL with the default cache capacity (10,000 entries).
func NewIAMClientWithCache ¶
NewIAMClientWithCache creates a new IAM client with a custom cache capacity. cacheSize must be > 0; values <= 0 fall back to defaultCacheSize.
func (*IAMClient) EnsureUser ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
DeleteSecret removes a secret.
func (*KMSClient) InvalidateCache ¶
InvalidateCache clears all cached secrets for an org.
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 (*OrgDB) DeleteOrg ¶
DeleteOrg removes an org's entire directory (including all user databases).
func (*OrgDB) DeleteUser ¶
DeleteUser removes a user's database directory.
func (*OrgDB) GetOrgDBPath ¶
GetOrgDBPath returns the database path for an existing org.
func (*OrgDB) GetUserDBPath ¶
GetUserDBPath returns the database path for an existing user.
func (*OrgDB) OrgDir ¶
OrgDir returns the directory for an org. Validates slug to prevent path traversal.
func (*OrgDB) ProvisionOrg ¶
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 ¶
ProvisionUser creates the per-user directory and database.
func (*OrgDB) UserDBPath ¶
UserDBPath returns the per-user SQLite database path.
func (*OrgDB) UserDEK ¶
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.
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.
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.