Documentation
¶
Overview ¶
Package tenant carries the resolved tenant (provider) scope through a request.
A tenant is a provider (see model.Provider). In multi-tenant mode a resolver middleware puts a *Scope in the request context from the Host (browser) or the api_token's user (API/agents); every tenant-scoped read then filters by it. In single-tenant mode no Scope is set and the absence means "unscoped — see everything", so existing behaviour is unchanged.
This package is deliberately tiny and dependency-free (only stdlib) so both the HTTP layer and the scoped store can share it without import cycles.
Index ¶
- Constants
- Variables
- func CheckRealm(s string) error
- func IsReservedRealm(s string) bool
- func NormalizeRealm(s string) string
- func RealmCandidates(base string, n int) []string
- func SplitLogin(s string) (realm, name string, named bool)
- func SuggestRealm(s string) string
- func WithScope(ctx context.Context, s *Scope) context.Context
- type Scope
Constants ¶
const RealmMaxLen = 63
RealmMaxLen bounds a realm: a DNS label's length, because a realm is also the first label of an address domain (`alex@acme.local`).
Variables ¶
var ( ErrRealmEmpty = errors.New("tenant: the realm is empty") ErrRealmInvalid = errors.New("tenant: a realm is lower-case letters, digits and dashes, at most 63 characters, and starts and ends with a letter or a digit") ErrRealmReserved = errors.New("tenant: this realm is reserved") )
Why a realm is refused. The API answers them as `realm_<kind>`.
var DenyAll = &Scope{ProviderID: 0}
DenyAll is a non-nil scope that grants nothing. The resolver attaches it to an authenticated request whose tenant cannot be resolved, so the request fails closed — it sees no storages and no directory (ProviderID 0 never matches a real provider) — instead of falling through to an unscoped "see everything".
Functions ¶
func CheckRealm ¶ added in v0.50.0
CheckRealm reports why a realm cannot be given to a new tenant, nil when it can. The realm is normalised first.
⚠ The character set is narrower than a slug's has ever been checked to be, on purpose: `/` separates the realm in `realm/name`, `@` and `.` would make the derived address ambiguous (`alex@a.b.local`), and `\` is the Windows domain separator a login name may already carry.
func IsReservedRealm ¶ added in v0.50.0
IsReservedRealm reports whether a realm is one no tenant may be given.
func NormalizeRealm ¶ added in v0.50.0
NormalizeRealm folds a realm the way it is stored and compared: trimmed and lower-cased. It does not validate.
func RealmCandidates ¶ added in v0.50.0
RealmCandidates lists the realms to try, in order, for a suggestion that may already be taken or reserved: the suggestion itself, then `-2` … `-n` on it (shortened so the result still fits RealmMaxLen). Every candidate passes CheckRealm except, possibly, the first.
func SplitLogin ¶ added in v0.50.0
SplitLogin reads `realm/name` — the way a protocol user name names a realm when the protocol carries no address. realm is normalised; named says a realm part was written at all.
acme/alex realm "acme", name "alex" acme/alex@x.com realm "acme", name "alex@x.com" (split at the FIRST `/`) /alex realm "" named: the platform's own tenant, said outright alex no realm part
A `/` that comes after an `@` or a `\` is part of the name, not a realm separator.
func SuggestRealm ¶ added in v0.50.0
SuggestRealm turns a slug (or a tenant's name) into the realm the tenant screen offers while a tenant is being created: lower case, letters with marks written without them (`Müşteri` → `musteri`), every run of anything that is not a letter or a digit one dash, no dash at either end, at most RealmMaxLen characters. "" when nothing realm-shaped is left.
⚠ A SUGGESTION, shown before the tenant exists and edited by whoever creates it: the realm is immutable once given, so nothing here is applied without a person seeing it. The API still validates what it is sent (CheckRealm), and a suggestion may be reserved or taken; the caller asks for that (RealmCandidates).
Types ¶
type Scope ¶
type Scope struct {
ProviderID int64
Slug string
IsSupertenant bool
// StorageIDs are the provider's linked storages. Empty for a supertenant,
// which is confine-exempt and reaches every storage (see CanAccessStorage).
StorageIDs []int64
}
Scope is the resolved tenant for a request: which provider it belongs to, whether that provider is the platform supertenant (confine-exempt and cross-tenant), and the storage ids it may reach.
func FromContext ¶
FromContext returns the request's tenant scope, or (nil, false) when none is set — single-tenant mode, or a pre-auth/public path. Callers must treat the absence as "unscoped": in multi-tenant mode a tenant-scoped operation that finds no scope must fail closed rather than fall back to unscoped.
func (*Scope) CanAccessStorage ¶
CanAccessStorage reports whether this scope may reach storage id. A nil scope (single-tenant mode / unscoped) and a supertenant can reach any storage; a regular tenant only its linked storages. This is the file-layer gate — the isolation circuit that, unlike directory scoping, must never leak.