Documentation
¶
Index ¶
- Constants
- Variables
- func Query(db *datastore.Datastore) datastore.Query
- type ByName
- type Organization
- func (o *Organization) AddAdmin(userOrId string)
- func (o *Organization) AddDefaultTokens()
- func (o *Organization) AddOwner(userOrId string)
- func (o *Organization) AfterCreate() error
- func (o *Organization) AfterUpdate(prev *Organization) error
- func (o Organization) AuthorizeNetToken(sandbox bool) integration.AuthorizeNetConnection
- func (o *Organization) BeforeCreate() error
- func (o *Organization) Defaults()
- func (o Organization) GetDefaultApp() (*app.App, error)
- func (o Organization) GetDefaultStore() (*store.Store, error)
- func (o *Organization) GetReferenceToken(usr *user.User) (*oauthtoken.Token, bool, error)
- func (o Organization) GetStripeAccessToken(userId string) (string, error)
- func (o *Organization) Init(db *datastore.Datastore)
- func (o Organization) IsAdmin(userOrId interface{}) bool
- func (o Organization) IsOwner(userOrId interface{}) bool
- func (o Organization) IsTestEmail(email string) bool
- func (o *Organization) Load(ps []datastore.Property) (err error)
- func (o Organization) Namespace() string
- func (o Organization) Namespaced(ctx context.Context) context.Context
- func (o Organization) Pricing() (*pricing.Fees, []pricing.Partner)
- func (o *Organization) ResetReferenceToken(usr *user.User, claims oauthtoken.Claims) (*oauthtoken.Token, error)
- func (o *Organization) RevokeReferenceToken(usr *user.User) (*oauthtoken.Token, bool, error)
- func (o *Organization) Save() (ps []datastore.Property, err error)
- func (o Organization) SquareConfig(sandbox bool) integration.SquareConnection
- func (o Organization) SquareEnvironment() string
- func (o Organization) StripeToken() string
- func (o Organization) TestMode() bool
- func (o *Organization) Validator() *val.Validator
- type StripeAccessTokenNotFound
Constants ¶
const ( DefaultAppName = "Hanzo App" DefaultStoreName = "Default" )
Variables ¶
var ErrSecretLikeName = errors.New("organization: refusing to provision org from a bearer-shaped name")
ErrSecretLikeName is returned when an untrusted, gateway-supplied org name is actually a raw API key / bearer token rather than a real org identifier.
The predicate itself is secret.Like. It lives in package secret because the same list of credential markers also generates the DB backstop trigger on _entities — one definition behind both gates, so neither can drift out from under the other.
var OnSaved = func(name string) {}
OnSaved runs after an org is persisted, with the org's name. pkg/org registers cache invalidation here so a mutation is not masked by the resolve TTL. It is a seam rather than a direct call because pkg/org depends on this package, so this package cannot import it back.
var (
UserNotTopLevel = errors.New("User is not in the top level namespace.")
)
Functions ¶
Types ¶
type ByName ¶
type ByName []*Organization
type Organization ¶
type Organization struct {
mixin.Model[Organization]
mixin.AccessTokens
wallet.WalletHolder
Name string `json:"name"`
FullName string `json:"fullName"`
Owners []string `json:"owners,omitempty" datastore:",noindex"`
Admins []string `json:"admins,omitempty" datastore:",noindex"`
Moderators []string `json:"moderators,omitempty" datastore:",noindex"`
Enabled bool `json:"enabled"`
BillingEmail string `json:"billingEmail,omitempty"`
Phone string `json:"phone,omitempty"`
Address Address `json:"address,omitempty"`
SocialMedia socialmedia.SocialMedia `json:"socialMedia,omitEmpty"`
Websites []website.Website `json:"websites,omitEmpty"`
WalletPassphrase string `json:"-"`
Timezone string `json:"timezone"`
Country string `json:"country"`
TaxId string `json:"taxId"`
// Used in generating email templates
LogoUrl string `json:"logoUrl"`
// Where is the user's dashboard?
DashboardUrl string `json:"dashboardUrl"`
// Fee structure for this organization
Fees pricing.Fees `json:"fees" datastore:",noindex"`
// Partner fees (private, should be up to partner to disclose)
Partners []pricing.Partner `json:"-" datastore:",noindex"`
// Email settings
Email email.Settings `json:"email" datastore:",noindex"`
// Default Store
DefaultStore string `json:"defaultStore"`
// Default App
DefaultApp string `json:"defaultApp"`
// Plan settings
Plan struct {
PlanId string
StartDate time.Time
} `json:"-"`
// Affiliate configuration
Affiliate integration.Affiliate `json:"-" datastore:",noindex"`
// Signup options
SignUpOptions struct {
// Controls the enabled status of account after creation
AccountsEnabledByDefault bool `json:"accountsEnabledByDefault"`
// Allow direct affiliate sign up
AllowAffiliateSignup bool `json:"allowAffiliateSignup"`
// Turns off required backend checks
NoNameRequired bool `json:"noNameRequired"`
NoPasswordRequired bool `json:"noPasswordRequired"`
// Requires password set on create confirmation
TwoStageEnabled bool `json:"twoStageEnabled"`
ImmediateLogin bool `json:"immediateLogin"`
UsernameRequired bool `json:"usernameRequired"`
} `json:"signUpOptions" datastore:",noindex"`
// Whether we use live or test tokens, mostly applicable to stripe
Live bool `json:"live"`
// TODO: Remain to PaymentWhitelist for clarity
// List of comma deliminated email globs that result in charges of 50 cents
EmailWhitelist string `json:"emailWhitelist" datastore:",noindex"`
// integration
Integrations integration.Integrations `json:"-" datastore:"-"`
Integrations_ string `json:"-" datastore:",noindex"`
// Analytics config
Analytics analytics.Analytics `json:"analytics" datastore:",noindex"`
// Bitcoi settings
Bitcoin integration.Bitcoin `json:"-"`
// Ethereum settings
Ethereum integration.Ethereum `json:"-"`
// Mailchimp settings
Mailchimp integration.Mailchimp `json:"-"`
// Mandrill settings
Mandrill integration.Mandrill `json:"-"`
// Mercury bank connection
Mercury integration.Mercury `json:"-"`
// Netlify settings
Netlify integration.Netlify `json:"-"`
// Paypal connection
Paypal integration.Paypal `json:"-"`
// Plaid connection
Plaid integration.Plaid `json:"-"`
// ReAmaze settings
Reamaze integration.Reamaze `json:"-"`
Recaptcha integration.Recaptcha `json:"-" datastore:",noindex"`
// Salesforce settings
Salesforce integration.Salesforce `json:"-"`
// Shipwire settings
Shipwire integration.Shipwire `json:"-"`
// Square connection
Square integration.Square `json:"-"`
// Stripe connection
Stripe integration.Stripe `json:"-"`
// Wire transfer settings
Wire integration.WireTransfer `json:"-"`
// AuthorizeNet connection
AuthorizeNet integration.AuthorizeNet `json:"-"`
// Adyen connection
Adyen integration.Adyen `json:"-"`
// Braintree connection
Braintree integration.Braintree `json:"-"`
// Recurly connection
Recurly integration.Recurly `json:"-"`
// LemonSqueezy connection
LemonSqueezy integration.LemonSqueezy `json:"-"`
SecurityToken integration.SecurityToken `json:"-"`
Currency currency.Type `json:"currency"`
}
func New ¶
func New(db *datastore.Datastore) *Organization
func (*Organization) AddAdmin ¶
func (o *Organization) AddAdmin(userOrId string)
Add admin to organization
func (*Organization) AddDefaultTokens ¶
func (o *Organization) AddDefaultTokens()
Old JWT / AccessToken AUTH
func (*Organization) AddOwner ¶
func (o *Organization) AddOwner(userOrId string)
Add admin to organization
func (*Organization) AfterCreate ¶
func (o *Organization) AfterCreate() error
func (*Organization) AfterUpdate ¶ added in v1.49.3
func (o *Organization) AfterUpdate(prev *Organization) error
AfterUpdate is the one place every full-entity org write passes through (orm.Model.UpdateCtx dispatches it), so invalidating here covers Update and MustUpdate from any handler without each call site having to remember.
func (Organization) AuthorizeNetToken ¶
func (o Organization) AuthorizeNetToken(sandbox bool) integration.AuthorizeNetConnection
func (*Organization) Defaults ¶
func (o *Organization) Defaults()
func (Organization) GetDefaultApp ¶
func (o Organization) GetDefaultApp() (*app.App, error)
Return DefaultApp
func (Organization) GetDefaultStore ¶
func (o Organization) GetDefaultStore() (*store.Store, error)
Return DefaultStore
func (*Organization) GetReferenceToken ¶
func (o *Organization) GetReferenceToken(usr *user.User) (*oauthtoken.Token, bool, error)
func (Organization) GetStripeAccessToken ¶
func (o Organization) GetStripeAccessToken(userId string) (string, error)
func (*Organization) Init ¶
func (o *Organization) Init(db *datastore.Datastore)
func (Organization) IsAdmin ¶
func (o Organization) IsAdmin(userOrId interface{}) bool
func (Organization) IsOwner ¶
func (o Organization) IsOwner(userOrId interface{}) bool
func (Organization) IsTestEmail ¶
func (o Organization) IsTestEmail(email string) bool
func (Organization) Namespace ¶
func (o Organization) Namespace() string
Namespace returns the datastore namespace for this organization.
SECURITY: every org — without exception — is strictly scoped to its own name. There is NO org-name escape hatch. The legacy `Name=="platform" -> "" (global/cross-org namespace)` bypass was REMOVED: it keyed cross-org datastore access on an org-NAME string, fully decoupled from real platform-admin identity, so anyone who could land in (or forge) an org named "platform" read/wrote the global namespace where every org's records live (Red — privilege escalation via the empty namespace). Cross-org / superadmin datastore access is now gated EXCLUSIVELY on auth.IAMClaims.IsSuperAdmin() (owner=="admin") at the handler/middleware layer — never inferred from the org name here.
func (Organization) Namespaced ¶
func (o Organization) Namespaced(ctx context.Context) context.Context
Namespaced returns a context scoped to this organization's namespace. An inbound HTTP request arrives already mint-gated (mintauth.WithGate) — the request middleware stamps the gate ONCE at the HTTP→datastore boundary, so a gated context IS the HTTP boundary signal (the single structural gate point). For such contexts we detach from the request lifecycle with context.WithoutCancel so the database context survives a browser disconnect or upstream proxy timeout mid-query — while PRESERVING the request's context values (trace and, critically, any mintauth grant applied by PlatformOnly / settled-payment middleware). Using WithoutCancel instead of context.Background() is what keeps the mint authorization (a context value) reachable through the detach.
func (*Organization) ResetReferenceToken ¶
func (o *Organization) ResetReferenceToken(usr *user.User, claims oauthtoken.Claims) (*oauthtoken.Token, error)
New JWT / OAUTH
func (*Organization) RevokeReferenceToken ¶
func (o *Organization) RevokeReferenceToken(usr *user.User) (*oauthtoken.Token, bool, error)
func (Organization) SquareConfig ¶
func (o Organization) SquareConfig(sandbox bool) integration.SquareConnection
func (Organization) SquareEnvironment ¶ added in v1.42.44
func (o Organization) SquareEnvironment() string
SquareEnvironment returns "sandbox" or "production" — the value the Square SDK uses to select its API base URL — derived from TestMode (one authority).
func (Organization) StripeToken ¶
func (o Organization) StripeToken() string
func (Organization) TestMode ¶ added in v1.42.44
func (o Organization) TestMode() bool
TestMode reports whether this org transacts in TEST mode. It is the SINGLE authority for BOTH the payment environment (Square sandbox vs production) AND the ledger (trans.Test, balance bucket, pay.Live). Keeping the charge environment and the ledger on ONE authority is what prevents a sandbox charge from crediting the live (spendable) balance and a production charge from booking test (unbilled) revenue.
The authority is the ORG RECORD, and nothing else. It used to be the deployment's SQUARE_ENVIRONMENT, which consulted o.Live only when that variable was UNSET — so on any templated deploy every tenant was forced into one mode and each org's own flag was dead. That is what made a second deployment necessary to serve a sandbox merchant (commerce-api.testnet.hanzo.ai exists for exactly this reason), and why a replica was never freely interchangeable: its behaviour depended on which env block started it. Resolving per org is what lets ONE stateless replica serve a sandbox merchant and a live merchant in the same process, on the same request path.
It is also what "configured per org, not in env files" means here. The credentials were already per org — o.Square.Sandbox / o.Square.Production, o.Stripe.Test / o.Stripe.Live, KMS-backed — and StripeToken() already chose between them from o.Live alone. Only the mode stayed deployment-wide, so the two could disagree. Now both read the same per-org fact.
Still fail-CLOSED, but per tenant instead of per deployment: an org is in production only when its own record says Live, so a new, unset or half-configured org transacts in sandbox and a missing flag can never silently charge real cards. What improves over the env gate is isolation — one org's misconfiguration can no longer drag every other tenant on the pod into the wrong environment, in either direction.
func (*Organization) Validator ¶
func (o *Organization) Validator() *val.Validator
type StripeAccessTokenNotFound ¶
func (StripeAccessTokenNotFound) Error ¶
func (e StripeAccessTokenNotFound) Error() string