Documentation
¶
Index ¶
- Constants
- Variables
- func BatchRowOf(ctx context.Context) (int, bool)
- func IdempotencyIdOf(ctx context.Context) (string, bool)
- func NewIdempotencyId() (string, error)
- func ParseIdempotencyId(raw string) (string, error)
- func ReplaceInner(ctx Context, inner context.Context) bool
- func RequestActionOf(ctx context.Context) string
- func SetBatchRow(ctx Context, row int)
- func SetRequestAction(ctx Context, actionCode string)
- func SetRequestMeta(ctx Context, meta RequestMeta)
- func WithIdempotencyId(ctx context.Context, id string) context.Context
- func WithRequestMeta(ctx context.Context, meta RequestMeta) context.Context
- type Context
- func AsRequestContext(echoCtx *echo.Context) (Context, error)
- func CloneRequestContext(ctx Context) Context
- func CloneWithInner(ctx Context, inner context.Context) Context
- func NewRequestContext(ctx context.Context) Context
- func NewRequestContextF(ctx context.Context, moduleName string, domainConstraints dmodel.DynamicFields) Context
- func NewRequestContextM(ctx context.Context, moduleName string) Context
- type ContextPermissions
- type Principal
- type PrincipalKind
- type RequestContext
- func (this *RequestContext) Deadline() (deadline time.Time, ok bool)
- func (this *RequestContext) Done() <-chan struct{}
- func (this *RequestContext) Err() error
- func (this *RequestContext) GetDbTranx() db.DbTransaction
- func (this *RequestContext) GetDomainConstraints() dmodel.DynamicFields
- func (this *RequestContext) GetLogger() logging.LoggerService
- func (this *RequestContext) GetModuleName() string
- func (this *RequestContext) GetPermissions() ContextPermissions
- func (this *RequestContext) GetUser() dmodel.DynamicFields
- func (this *RequestContext) InnerContext() context.Context
- func (this *RequestContext) OnCommitted(fn func())
- func (this *RequestContext) SetDbTranx(trx db.DbTransaction)
- func (this *RequestContext) SetDomainConstraints(constraints dmodel.DynamicFields)
- func (this *RequestContext) SetLogger(logger logging.LoggerService)
- func (this *RequestContext) SetPermissions(permissions ContextPermissions)
- func (this *RequestContext) SetUser(user dmodel.DynamicFields)
- func (this *RequestContext) Value(key any) any
- func (this *RequestContext) WithValue(key, val any)
- type RequestMeta
- type RequestSource
Constants ¶
const ( // PrincipalKindUser is a person acting through a client. PrincipalKindUser = PrincipalKind("user") // PrincipalKindService is a workload acting on the system's behalf: a scheduled job, a // message consumer, a device. It authorizes exactly like a user, on entitlements it was // granted - never by virtue of being internal. PrincipalKindService = PrincipalKind("service") // PrincipalKindSystem is reserved for future platform-level operations. It deliberately // carries NO bypass behaviour; treat it as unauthorized until that is designed. PrincipalKindSystem = PrincipalKind("system") )
const ( RequestSourceHttp = RequestSource("http") RequestSourceJob = RequestSource("job") RequestSourceEvent = RequestSource("event") RequestSourceSystem = RequestSource("system") )
Variables ¶
var ( ErrIdempotencyIdLength = errors.New("idempotency id: not 36 characters") ErrIdempotencyIdFormat = errors.New("idempotency id: not a canonical hex UUID") ErrIdempotencyIdVersion = errors.New("idempotency id: not UUID version 7") ErrIdempotencyIdVariant = errors.New("idempotency id: not the RFC 9562 variant") )
var CtxKeyDomainConstraints = contextKey{"domain_constraints"}
Functions ¶
func BatchRowOf ¶
BatchRowOf returns the row SetBatchRow recorded; false outside a batch.
func IdempotencyIdOf ¶
IdempotencyIdOf never generates one: an ID minted here would differ per call and hide a lost ID.
func NewIdempotencyId ¶
func ParseIdempotencyId ¶
ParseIdempotencyId accepts only the canonical 8-4-4-4-12 form of a UUIDv7 and returns it lowercased. URN, braces, a list and the nil UUID all fail one of the checks below.
func ReplaceInner ¶
ReplaceInner swaps ctx's inner context in place and reports whether it could. It is for a layer that scopes a value to the call it wraps, such as the current span, on a context its callee mutates in place (acting org, request action): deriving a clone instead would hide those mutations from the caller. The owning-goroutine rule of the setters applies.
func RequestActionOf ¶
RequestActionOf returns the custom action code ctx was labelled with, or "".
func SetBatchRow ¶
SetBatchRow records that ctx is now writing the row-th (1-based) record of a batch, such as a bulk create or an import; 0 clears it. One request writing many records needs it to tell their side effects apart deterministically, e.g. the operation key of each record's sequence number, so that a retry with the same idempotency id addresses the same rows again. The owning-goroutine rule of the setters applies.
func SetRequestAction ¶
SetRequestAction labels the writes ctx makes from now on with a custom action code, such as "suspend": the audit trail records an update made under it as that action. The request guard sets it when a custom permission check passes, so a custom action needs no audit code of its own. Stored in place, like SetRequestMeta.
func SetRequestMeta ¶
func SetRequestMeta(ctx Context, meta RequestMeta)
SetRequestMeta stores meta on ctx in place. It lives in the inner context's values, so CloneRequestContext and every context derived from the inner one carry it along.
func WithIdempotencyId ¶
WithIdempotencyId derives a context carrying id. Set it on the inner context before a RequestContext wraps it, so CloneRequestContext and every derived context carry it too.
func WithRequestMeta ¶
func WithRequestMeta(ctx context.Context, meta RequestMeta) context.Context
WithRequestMeta derives a plain context carrying meta, for an entry point that has no RequestContext yet: a corectx built on top of it sees the same meta.
Types ¶
type Context ¶
type Context interface {
context.Context
InnerContext() context.Context
GetLogger() logging.LoggerService
SetLogger(logger logging.LoggerService)
GetDbTranx() db.DbTransaction
SetDbTranx(trx db.DbTransaction)
// OnCommitted runs fn after the context's transaction commits, or right away when there is
// none, because the write it follows has already autocommitted. Use it for side effects
// (event publishes, cache invalidation) that must not escape a rolled-back write.
OnCommitted(fn func())
GetDomainConstraints() dmodel.DynamicFields
SetDomainConstraints(constraints dmodel.DynamicFields)
GetModuleName() string
GetPermissions() ContextPermissions
SetPermissions(permissions ContextPermissions)
GetUser() dmodel.DynamicFields
SetUser(user dmodel.DynamicFields)
// Replace current inner context with a new one that has the given key and value.
WithValue(key, val any)
}
func AsRequestContext ¶
Returns pointer to an instance of RequestContext if it exists, otherwise returns an error.
func CloneRequestContext ¶
CloneRequestContext returns a copy of ctx that can be given a different transaction without disturbing the original. Use it to derive a scoped context from a live request; the New* constructors above build an empty one, for code with no caller behind it.
It copies every field the copy might be used with, the caller's identity above all: basemodel stamps created_by, and the audit log its actor, from GetPermissions().UserId, so a clone that dropped permissions would write them null and report nothing. Domain constraints live in the inner context's values, so copying Context carries them along.
A field added to RequestContext must be added here too.
func CloneWithInner ¶
CloneWithInner is CloneRequestContext with the inner context replaced, for a caller that has derived a cancellation or deadline from the request's own context and needs the identity, logger and transaction to travel with it. Passing the bare inner context instead would drop all of those; mutating the original would impose the deadline on the caller's remaining work.
func NewRequestContext ¶
func NewRequestContextF ¶
func NewRequestContextF(ctx context.Context, moduleName string, domainConstraints dmodel.DynamicFields) Context
NewRequestContextF builds an empty context carrying domain constraints.
The constraints go through SetDomainConstraints rather than a struct field: GetDomainConstraints reads them back off the inner context's values, so a field set here would never be read.
type ContextPermissions ¶
type ContextPermissions struct {
IsOwner bool
Entitlements ds.Set[string]
UserId model.Id `json:"user_id"`
// The actor executing this request: a user, or a service acting for the system.
Principal Principal `json:"principal"`
// The orgs that user belongs to (if any)
UserOrgIds ds.Set[model.Id] `json:"user_org_ids"`
// The org unit that user belongs to (if any)
OrgUnitId *model.Id `json:"org_unit_id"`
// The org that the org unit belongs to (if user belongs to an org unit)
OrgUnitOrgId *model.Id `json:"org_unit_org_id"`
}
type Principal ¶
type Principal struct {
Kind PrincipalKind `json:"kind"`
Id model.Id `json:"id"`
// OrgId is the single org a service principal acts within. A service holds no org
// membership, so this is what org-scoped work is checked against; nil for a user, whose
// reach comes from UserOrgIds instead.
OrgId *model.Id `json:"org_id"`
// DisplayName is for audit and logs only. Never make a security decision from it.
DisplayName string `json:"display_name"`
}
Principal is the authenticated actor behind the current execution.
A zero Principal means "nobody was authenticated". Authorization must fail closed on it rather than reading absence as permission - see requestguard.AssertPermission.
type PrincipalKind ¶
type PrincipalKind string
PrincipalKind says what sort of actor is executing, so that a job or a consumer is distinguishable from an unauthenticated caller rather than looking identical to one.
This is not IAM's PrincipalType (`nikkiuser` | `custom`), which describes where a login credential came from - a different axis, and in a module core cannot import.
type RequestContext ¶
type RequestContext struct {
// contains filtered or unexported fields
}
RequestContext is owned by one goroutine, the one serving the request, which alone may call its setters. It is nevertheless read from others: database/sql watches Done/Err of every query's context from its own goroutine, and so does anything else handed the context to cancel on. Those readers only use the context.Context methods, so the inner context - the one field the setters replace and those methods read - is held atomically. Embedding it as a plain interface field let a reader see a torn value mid-WithValue, which crashed the process (core-001).
func (*RequestContext) Deadline ¶
func (this *RequestContext) Deadline() (deadline time.Time, ok bool)
func (*RequestContext) Done ¶
func (this *RequestContext) Done() <-chan struct{}
func (*RequestContext) Err ¶
func (this *RequestContext) Err() error
func (*RequestContext) GetDbTranx ¶
func (this *RequestContext) GetDbTranx() db.DbTransaction
func (*RequestContext) GetDomainConstraints ¶
func (this *RequestContext) GetDomainConstraints() dmodel.DynamicFields
func (*RequestContext) GetLogger ¶
func (this *RequestContext) GetLogger() logging.LoggerService
func (*RequestContext) GetModuleName ¶
func (this *RequestContext) GetModuleName() string
func (*RequestContext) GetPermissions ¶
func (this *RequestContext) GetPermissions() ContextPermissions
func (*RequestContext) GetUser ¶
func (this *RequestContext) GetUser() dmodel.DynamicFields
func (*RequestContext) InnerContext ¶
func (this *RequestContext) InnerContext() context.Context
func (*RequestContext) OnCommitted ¶
func (this *RequestContext) OnCommitted(fn func())
func (*RequestContext) SetDbTranx ¶
func (this *RequestContext) SetDbTranx(trx db.DbTransaction)
func (*RequestContext) SetDomainConstraints ¶
func (this *RequestContext) SetDomainConstraints(constraints dmodel.DynamicFields)
func (*RequestContext) SetLogger ¶
func (this *RequestContext) SetLogger(logger logging.LoggerService)
func (*RequestContext) SetPermissions ¶
func (this *RequestContext) SetPermissions(permissions ContextPermissions)
func (*RequestContext) SetUser ¶
func (this *RequestContext) SetUser(user dmodel.DynamicFields)
func (*RequestContext) Value ¶
func (this *RequestContext) Value(key any) any
func (*RequestContext) WithValue ¶
func (this *RequestContext) WithValue(key, val any)
Replace current inner context with a new one that has the given key and value.
The load and store are separate, which is safe only because the setters are called by the owning goroutine alone; concurrent readers always see either the old or the new inner context.
type RequestMeta ¶
type RequestMeta struct {
Source RequestSource
}
RequestMeta says what started the execution. Records one call writes are correlated by the active span's trace id instead (see auditlog), so the meta carries no id of its own.
func EnsureRequestMeta ¶
func EnsureRequestMeta(ctx Context, fallbackSource RequestSource) RequestMeta
EnsureRequestMeta returns the meta ctx carries, or one with fallbackSource stored on ctx, so later writes through the same ctx share it. An entry point should set the meta itself: a meta stored here does not reach a context cloned from ctx before this call.
func NewRequestMeta ¶
func NewRequestMeta(source RequestSource) RequestMeta
NewRequestMeta returns a meta for an execution started by source.
func RequestMetaOf ¶
func RequestMetaOf(ctx context.Context) (RequestMeta, bool)
RequestMetaOf returns the meta ctx carries, and false when none was set.
type RequestSource ¶
type RequestSource string
RequestSource says what started the execution: an HTTP request, a scheduled job, a message consumer, or the platform itself.