Documentation
¶
Overview ¶
Package identity is the single source of truth for the canonical user ID used across SpiceDB writes and CheckPermission calls. Construct a Principal via the constructors in principal.go; call .Canonical() to obtain the stable base64-encoded user id.
Index ¶
- Variables
- func DecodeForDisplay(subject string) string
- func IsCanonicalUserIDShape(s string) bool
- type CanonicalUserID
- type Email
- type Kind
- type Principal
- func EmailReference(email Email) Principal
- func FromExternal(kind Kind, teamScope TeamScope, externalID RawExternalID, email Email) Principal
- func IdPUser(email Email, verified bool, displayName string) Principal
- func RawSubject(s string) Principal
- func VerifiedEmail(email Email, displayName string) Principal
- func (p Principal) AllowSynthetic() Principal
- func (p Principal) Canonical() (CanonicalUserID, error)
- func (p Principal) DisplayName() string
- func (p Principal) Email() Email
- func (p Principal) EmailVerified() bool
- func (p Principal) ExternalID() RawExternalID
- func (p Principal) Kind() Kind
- func (p Principal) Subject() (Subject, error)
- func (p Principal) TeamScope() TeamScope
- type RawExternalID
- type Subject
- type TeamScope
Constants ¶
This section is empty.
Variables ¶
var ErrNonUserSubject = errors.New("identity: subject is not a user subject")
ErrNonUserSubject is returned by Subject.CanonicalUserID when the subject's SpiceDB object-type is not "user" (e.g. a group or service-account subject), so a caller that needs a bare user canonical fails closed instead of silently misreading a non-user subject.
var ErrSyntheticSubject = errors.New("identity: refusing to mint a synthetic subject for a principal with no verified email (call AllowSynthetic to opt in)")
ErrSyntheticSubject is returned by Canonical/Subject when a principal has no verified email and did NOT explicitly opt into the synthetic (unforgeable kind:teamScope:externalID) encoding via AllowSynthetic.
Silently minting a synthetic subject for a principal that SHOULD have resolved to a real user:<email> writes/checks a subject that no real grant resolves to — the class of bug that turned owner approvals into denials and denied owners the right to restart their own sessions. Fail loud: the caller must either supply a verified email or explicitly say "yes, synthetic is intended here" via AllowSynthetic.
Functions ¶
func DecodeForDisplay ¶
DecodeForDisplay reverses Principal.Canonical for human-readable rendering: given the canonical id (or "user:<canonical>" SpiceDB-form), it returns the original email (when the canonical was produced from an email) or the "kind:teamScope:externalID" synthetic. Decoding failures fall back to the input unchanged — never mangles a display string.
Only use for display. Identity comparisons, SpiceDB writes, and credential resolution MUST keep the canonical form.
func IsCanonicalUserIDShape ¶
IsCanonicalUserIDShape reports whether s has the shape every CanonicalUserID has — non-empty and a valid base64url (RawURLEncoding, unpadded) string — which is what Principal.Canonical produces for both the email and the synthetic form. It is a SHAPE check, not proof that a principal exists: it exists so a hand-authored config field cannot carry a raw email or external id where a canonical belongs, the "raw id as a SpiceDB object id" class of defect. It does not inspect the decoded bytes (a canonical may encode anything).
Types ¶
type CanonicalUserID ¶
type CanonicalUserID struct {
// contains filtered or unexported fields
}
CanonicalUserID is the BARE canonical user id (base64url body, no "user:" prefix) used inside SpiceDB object ids. It is what Principal.Canonical() returns. The "user:"-prefixed form is Subject (subject.go).
Why this is a struct and not a string ¶
This id becomes a SpiceDB `user:` object, so whether it may be trusted depends ENTIRELY on where its bytes came from — and as a defined string type it recorded nothing about that. Twenty-six non-test sites converted a bare string straight into one, and several of those strings are exactly the inputs a security audit found being trusted without verification: an unsigned X-Admin-Subject header, a self-asserted app-tool Requester, an email copied out of a directory this deployment does not govern.
Principal already tracked provenance properly — unexported fields, an explicit emailVerified, an AllowSynthetic opt-in — and then threw it away at this boundary. With an unexported field the only ways in are the constructors here, which is the same shape plangate.Handle and memory.Approval use, and it makes every act of taking someone's word for an identity a named, greppable call rather than a conversion nobody can see.
The zero value is empty and unverified, so a forgotten assignment fails closed rather than reading as a valid identity.
func CanonicalFromTrusted ¶
func CanonicalFromTrusted(id string, because string) CanonicalUserID
CanonicalFromTrusted accepts an id the platform did not itself verify, naming WHY it is being accepted.
because is not stored — it documents the call site and makes these sites countable. `grep CanonicalFromTrusted` enumerates every place the platform takes someone's word for an identity, and a reason that reads "a header the proxy set" or "a field the caller wrote" is a finding, not a migration artifact.
func (CanonicalUserID) IsZero ¶
func (c CanonicalUserID) IsZero() bool
IsZero reports the absence of an id. The zero value is empty, so a forgotten assignment fails closed rather than reading as a valid identity.
func (CanonicalUserID) MarshalJSON ¶
func (c CanonicalUserID) MarshalJSON() ([]byte, error)
MarshalJSON emits the id.
func (CanonicalUserID) String ¶
func (c CanonicalUserID) String() string
func (CanonicalUserID) Subject ¶
func (c CanonicalUserID) Subject() Subject
Subject returns the full "user:<canonical>" SpiceDB subject reference for c. It is the single typed CanonicalUserID→Subject conversion; call it instead of hand-building `"user:"+canon.String()`, which yields a string that merely looks like a Subject.
func (CanonicalUserID) SubjectRef ¶
func (c CanonicalUserID) SubjectRef() Subject
SubjectRef returns the SpiceDB subject reference for c when c is whatever principal happened to act, which is not always a human.
Use this rather than Subject wherever the canonical came from an inbound. A kind that provides no starting user (a cron firing) assigns the Channel's spec.authzSubject verbatim and RawSubject.Canonical() preserves a qualified value unchanged, so a CanonicalUserID can already BE a full reference like "service:nightly-report". Subject would double-prefix it into "user:service:nightly-report" — a subject naming nothing, in an audit line someone is meant to look up.
The discriminator is sound, not heuristic: a bare canonical is base64 (of an email, or of the synthetic kind:teamScope:externalID tuple) and the base64 alphabet contains no ':', so a colon can only be a type prefix.
Empty stays empty: no principal to name is not the same as "user:".
func (*CanonicalUserID) UnmarshalJSON ¶
func (c *CanonicalUserID) UnmarshalJSON(b []byte) error
UnmarshalJSON decodes an id that arrived over a wire — asserted by whoever sent it, and witnessed by nobody here.
type Kind ¶
type Kind string
Kind is an identity's origin. An OPEN enum: the constants name the known values, but an unknown kind is valid data (channel kinds are pluggable) — there is no Validate/panic. The zero value "" is a valid "unset origin".
type Principal ¶
type Principal struct {
// contains filtered or unexported fields
}
Principal is the single representation of a human user anywhere in the system. Construct via the constructors below — never as a struct literal — so the email-vs-synthetic decision and the "was this identity proven?" judgment live in exactly one package. Fields are unexported; read them through the typed accessors below.
func EmailReference ¶
EmailReference encodes an email that is configuration/reference data (authz CEL expressions, bootstrap relationships, display tooling) — NOT a proven login. Same canonical as VerifiedEmail, EmailVerified stays false.
func FromExternal ¶
func FromExternal(kind Kind, teamScope TeamScope, externalID RawExternalID, email Email) Principal
FromExternal is the Principal for a channel-attributed user. email, when non-empty, MUST have been verified by the channel (e.g. Slack users.info inside the installed workspace) — it wins the canonical and marks the principal verified. Empty email yields the stable synthetic kind:teamScope:externalID canonical.
func IdPUser ¶
IdPUser is the Principal an identity-provider Complete() produces. verified mirrors the provider's email_verified claim verbatim; identityd enforces it before trusting the login.
func RawSubject ¶
RawSubject wraps a pre-formed SpiceDB subject ("user:<id>") that is used verbatim — the bento channel's spec.authzSubject escape hatch. Registered here so even the bypass is visible in the one location.
func VerifiedEmail ¶
VerifiedEmail is the Principal for an identity whose email was PROVEN — an IdP login or a trusted-workspace channel lookup.
func (Principal) AllowSynthetic ¶
AllowSynthetic returns a copy of the principal explicitly permitted to canonicalize to the synthetic kind:teamScope:externalID subject when it has no verified email. Use ONLY where the synthetic subject is the intended identity (guests / foreign-workspace users / bots / local CLI user / channel-native ids without email). For a principal that carries an email this is a no-op (the email always wins the canonical).
func (Principal) Canonical ¶
func (p Principal) Canonical() (CanonicalUserID, error)
Canonical returns the canonical user id used in SpiceDB object ids. Encoding (pinned by canonical_golden_test.go): base64url(lower(email)) when Email is set, else — only when the caller opted in via AllowSynthetic — base64url(kind:teamScope:externalID).
An email-less principal that did NOT opt into the synthetic encoding returns ErrSyntheticSubject: minting a synthetic subject for an identity that should have carried a verified email silently writes/checks a subject no real grant resolves to. Callers must handle the error (surface it, fail closed) rather than proceed with a phantom subject.
func (Principal) DisplayName ¶
DisplayName returns the principal's human-readable display name.
func (Principal) EmailVerified ¶
EmailVerified reports whether the email was PROVEN — an IdP login or a trusted-workspace channel lookup — rather than merely referenced.
func (Principal) ExternalID ¶
func (p Principal) ExternalID() RawExternalID
ExternalID returns the principal's channel-native external id.
type RawExternalID ¶
type RawExternalID string
RawExternalID is a channel-native external id (e.g. a Slack "U…" member id). It is NOT a canonical subject and must not be handed to code that expects one (that mismatch was a production bug — the whole reason this type exists).
func (RawExternalID) String ¶
func (r RawExternalID) String() string
type Subject ¶
type Subject string
Subject is a canonical SpiceDB subject reference, "user:<canonical>".
It is a DEFINED type (deliberately NOT a `= string` alias): it reads and JSON-marshals exactly like a string today, but gives a compiler-enforced seam so the underlying representation can change later (e.g. to a struct) with every call site flagged. Empty means "no subject" (an agent/runner- authored turn, or a non-human inbound).
func (Subject) CanonicalUserID ¶
func (s Subject) CanonicalUserID() (CanonicalUserID, error)
CanonicalUserID returns the bare canonical user id iff this is a user subject; otherwise ErrNonUserSubject (fail-closed). This is the typed replacement for strings.TrimPrefix(subject, "user:").
func (Subject) ObjectType ¶
ObjectType returns the SpiceDB object-type segment (before the first ':'), or "" if the subject has no type prefix.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package agentidentity persists a replacement value for a credential an AgentIdentity owns — the agent's OWN shared secret, as distinct from the per-person credentials pkg/platform/identity/useridentity writes.
|
Package agentidentity persists a replacement value for a credential an AgentIdentity owns — the agent's OWN shared secret, as distinct from the per-person credentials pkg/platform/identity/useridentity writes. |
|
Package authkind defines the per-prefix plug-in surface for AgentIdentity setup routing.
|
Package authkind defines the per-prefix plug-in surface for AgentIdentity setup routing. |
|
cli
Package cli is the authkind impl for the "cli" prefix.
|
Package cli is the authkind impl for the "cli" prefix. |
|
loader
Package loader blank-imports every authkind impl so a binary gets all of them registered with a single import.
|
Package loader blank-imports every authkind impl so a binary gets all of them registered with a single import. |
|
mcp
Package mcp is the authkind impl for the "mcp" prefix.
|
Package mcp is the authkind impl for the "mcp" prefix. |
|
registry
Package registry holds the global authkind registry.
|
Package registry holds the global authkind registry. |
|
sidecartoolbox
Package sidecartoolbox is the authkind impl for the "toolbox" prefix.
|
Package sidecartoolbox is the authkind impl for the "toolbox" prefix. |
|
toolspec
Package toolspec is the authkind impl for the "toolspec" prefix.
|
Package toolspec is the authkind impl for the "toolspec" prefix. |
|
Package broker defines the token broker: the single seam that turns credential descriptors into injectable tool tokens; the in-process implementation lives in ./inproc.
|
Package broker defines the token broker: the single seam that turns credential descriptors into injectable tool tokens; the in-process implementation lives in ./inproc. |
|
inproc
Package inproc is the in-process token broker implementation.
|
Package inproc is the in-process token broker implementation. |
|
Package credhost answers one question: may this credential be sent to this host?
|
Package credhost answers one question: may this credential be sent to this host? |
|
Package credkind is the pluggable driver surface for identity credential types — one Kind per value of AgentCredential.Type / CredentialSource.Type.
|
Package credkind is the pluggable driver surface for identity credential types — one Kind per value of AgentCredential.Type / CredentialSource.Type. |
|
federated
Package federated implements the credkind.Kind for type=federated: a credential minted per resolve via an ID-JAG token exchange rather than read from a Secret.
|
Package federated implements the credkind.Kind for type=federated: a credential minted per resolve via an ID-JAG token exchange rather than read from a Secret. |
|
githubapp
This file implements the credkind.Kind for type=githubApp: a credential minted on demand from a stored GitHub App private key rather than read directly from a Secret.
|
This file implements the credkind.Kind for type=githubApp: a credential minted on demand from a stored GitHub App private key rather than read directly from a Secret. |
|
imports
Package imports blank-imports every credkind so a binary gets the full registry with a single import.
|
Package imports blank-imports every credkind so a binary gets the full registry with a single import. |
|
oauth
Package oauth implements the credkind.Kind for type=oauth: a token bundle stored under a FIXED multi-key Secret shape (access_token, refresh_token, expires_at, token_endpoint, client_id, scope).
|
Package oauth implements the credkind.Kind for type=oauth: a token bundle stored under a FIXED multi-key Secret shape (access_token, refresh_token, expires_at, token_endpoint, client_id, scope). |
|
registry
Package registry is the process-wide credential-kind registry.
|
Package registry is the process-wide credential-kind registry. |
|
registry/registrytest
Package registrytest provides shared credkind registry test helpers, so packages whose tests need to Reset() the registry (to probe an unregistered-type or empty-registry failure mode) don't each hand-roll the same save/restore.
|
Package registrytest provides shared credkind registry test helpers, so packages whose tests need to Reset() the registry (to probe an unregistered-type or empty-registry failure mode) don't each hand-roll the same save/restore. |
|
static
Package static implements the credkind.Kind for type=static: a single value read from one named key of one Secret.
|
Package static implements the credkind.Kind for type=static: a single value read from one named key of one Secret. |
|
descriptors.go — the shared, kind-agnostic credential resolver.
|
descriptors.go — the shared, kind-agnostic credential resolver. |
|
Package credupdate holds the credential-update determination: the decision about whether an agent's claim that a credential has died justifies putting a credential-entry form in front of a human.
|
Package credupdate holds the credential-update determination: the decision about whether an agent's claim that a credential has died justifies putting a credential-entry form in front of a human. |
|
Package externaltoken derives the SpiceDB object id and value hash used by the externaltoken token-use authorization grant.
|
Package externaltoken derives the SpiceDB object id and value hash used by the externaltoken token-use authorization grant. |
|
Package federation derives an upstream resource access token from a user's enterprise identity via the ID-JAG two-legged exchange (RFC 8693 token-exchange at the IdP → JWT authorization grant at the resource AS).
|
Package federation derives an upstream resource access token from a user's enterprise identity via the ID-JAG two-legged exchange (RFC 8693 token-exchange at the IdP → JWT authorization grant at the resource AS). |
|
fake
Package fake is a deterministic federation.Minter for tests.
|
Package fake is a deterministic federation.Minter for tests. |
|
idjag
Package idjag is the vendor-neutral ID-JAG (Identity Assertion JWT Authorization Grant, draft-ietf-oauth-identity-assertion-authz-grant) federation.Minter.
|
Package idjag is the vendor-neutral ID-JAG (Identity Assertion JWT Authorization Grant, draft-ietf-oauth-identity-assertion-authz-grant) federation.Minter. |
|
Package idp defines the pluggable human-login identity-provider seam: cluster config (ClusterIdentityProvider) names a Kind; the Kind constructs a Provider; identityd drives Begin/Complete and enforces email-verified + allowed-domain policy on the result.
|
Package idp defines the pluggable human-login identity-provider seam: cluster config (ClusterIdentityProvider) names a Kind; the Kind constructs a Provider; identityd drives Begin/Complete and enforces email-verified + allowed-domain policy on the result. |
|
fakekind
Package fakekind registers an in-process idp kind for tests and the e2e harness.
|
Package fakekind registers an in-process idp kind for tests and the e2e harness. |
|
googlekind
Package googlekind is the "google" idp kind: the generic oidc kind preset with Google's pinned issuer, an hd= login hint, and a server-side re-check of the hd claim (the hint is UX; the claim check is the enforcement).
|
Package googlekind is the "google" idp kind: the generic oidc kind preset with Google's pinned issuer, an hd= login hint, and a server-side re-check of the hd claim (the hint is UX; the claim check is the enforcement). |
|
idpscreens
Package idpscreens holds the questions more than one identity-provider kind asks — "which client did you register, what is its secret, and who may sign in with it?" — described once instead of once per kind.
|
Package idpscreens holds the questions more than one identity-provider kind asks — "which client did you register, what is its secret, and who may sign in with it?" — described once instead of once per kind. |
|
oidckind
Package oidckind implements the generic OIDC idp kind — issuer discovery + code exchange + ID-token verification via go-oidc.
|
Package oidckind implements the generic OIDC idp kind — issuer discovery + code exchange + ID-token verification via go-oidc. |
|
oidckind/oidctest
Package oidctest provides a fake OIDC issuer (httptest.Server) for testing the oidc and google idp kinds.
|
Package oidctest provides a fake OIDC issuer (httptest.Server) for testing the oidc and google idp kinds. |
|
passwordkind
Package passwordkind implements the "password" idp.Kind: a non-federated, password-only local IdP for the single-user macOS desktop's /admin console.
|
Package passwordkind implements the "password" idp.Kind: a non-federated, password-only local IdP for the single-user macOS desktop's /admin console. |
|
Package passthrough is the canonical resolver for "which credentials does this AgentClass require under identityMode=userPassthrough?"
|
Package passthrough is the canonical resolver for "which credentials does this AgentClass require under identityMode=userPassthrough?" |
|
Package passthroughcatalog provides MCPServer-by-credential-name lookup helpers shared between identityd (which routes credential link clicks to the OAuth or PAT flow based on the source MCPServer's Spec.Auth) and channelsd's credential_request watcher (which partitions the missing credentials into per-credential buttons by the same lookup).
|
Package passthroughcatalog provides MCPServer-by-credential-name lookup helpers shared between identityd (which routes credential link clicks to the OAuth or PAT flow based on the source MCPServer's Spec.Auth) and channelsd's credential_request watcher (which partitions the missing credentials into per-credential buttons by the same lookup). |
|
Package passthroughlink mints + verifies HMAC-signed deep-links that channelsd sends a passthrough-session's starter and identityd consumes.
|
Package passthroughlink mints + verifies HMAC-signed deep-links that channelsd sends a passthrough-session's starter and identityd consumes. |
|
sessionviewlink
Package sessionviewlink mints purpose="session_view" signed deep-links: a long-lived (7-day), shareable bearer capability for the session-view page (pkg/web/webui/sessionview), the shared-transcript mirror of a source channel's conversation.
|
Package sessionviewlink mints purpose="session_view" signed deep-links: a long-lived (7-day), shareable bearer capability for the session-view page (pkg/web/webui/sessionview), the shared-transcript mirror of a source channel's conversation. |
|
viewlink
Package viewlink mints webd artifact-view deep-links from a passthroughlink Signer + a live webd base URL.
|
Package viewlink mints webd artifact-view deep-links from a passthroughlink Signer + a live webd base URL. |
|
Package provider defines the /providers/ library shape and the compile-time embedded catalog of builtin providers.
|
Package provider defines the /providers/ library shape and the compile-time embedded catalog of builtin providers. |
|
Package refresh runs RFC 6749 refresh-token grants against an oauth credential's stored token_endpoint, writing the new access_token / refresh_token / expires_at back to the Secret atomically.
|
Package refresh runs RFC 6749 refresh-token grants against an oauth credential's stored token_endpoint, writing the new access_token / refresh_token / expires_at back to the Secret atomically. |
|
Package setup is the credential-acquisition engine.
|
Package setup is the credential-acquisition engine. |
|
builtins
Package builtins holds Go-coded setup flows for curated providers.
|
Package builtins holds Go-coded setup flows for curated providers. |
|
builtins/anthropic_oauth
Package anthropic_oauth is the builtin Go flow for the anthropic-oauth provider.
|
Package anthropic_oauth is the builtin Go flow for the anthropic-oauth provider. |
|
builtins/flowscreens
Package flowscreens holds the steps more than one credential-setup flow takes: "open the page where this is generated", plus the flow-specific vocabulary a tui.Question is configured with — which address a provider documents itself at, and what shape its token has.
|
Package flowscreens holds the steps more than one credential-setup flow takes: "open the page where this is generated", plus the flow-specific vocabulary a tui.Question is configured with — which address a provider documents itself at, and what shape its token has. |
|
builtins/github_pat
Package github_pat is the builtin Go flow for the github-pat provider.
|
Package github_pat is the builtin Go flow for the github-pat provider. |
|
builtins/kubectl_kubeconfig
Package kubectl_kubeconfig is the builtin Go flow for the kubectl-kubeconfig provider.
|
Package kubectl_kubeconfig is the builtin Go flow for the kubectl-kubeconfig provider. |
|
builtins/loader
Package loader blank-imports every v1 builtin flow so binaries get all of them registered with a single import.
|
Package loader blank-imports every v1 builtin flow so binaries get all of them registered with a single import. |
|
builtins/oauth_mcp
Package oauth_mcp is the builtin Go flow for the oauth-mcp provider.
|
Package oauth_mcp is the builtin Go flow for the oauth-mcp provider. |
|
builtins/onepassword_scim
Package onepassword_scim is the builtin Go flow for the onepassword-scim provider.
|
Package onepassword_scim is the builtin Go flow for the onepassword-scim provider. |
|
builtins/slack_bot_token
Package slack_bot_token is the builtin Go flow for the slack-bot-token provider.
|
Package slack_bot_token is the builtin Go flow for the slack-bot-token provider. |
|
builtins/tailscale_authkey
Package tailscale_authkey is the builtin Go flow for the tailscale-authkey provider.
|
Package tailscale_authkey is the builtin Go flow for the tailscale-authkey provider. |
|
llmagent
Package llmagent runs the LLM-driven setup agent for any requirement that lacks a builtin Go flow.
|
Package llmagent runs the LLM-driven setup agent for any requirement that lacks a builtin Go flow. |
|
llmagent/tools
Package tools holds the LLM-tool implementations for the setup agent.
|
Package tools holds the LLM-tool implementations for the setup agent. |
|
Package subjectresolve resolves an agent-supplied user REFERENCE to a canonical platform user, server-side, against records the platform already trusts — never an agent-claimed mapping.
|
Package subjectresolve resolves an agent-supplied user REFERENCE to a canonical platform user, server-side, against records the platform already trusts — never an agent-claimed mapping. |
|
Package useridentity persists credentials on the cluster-scoped UserIdentity CRD and its master credential Secrets, and holds the naming helpers every component derives those names with.
|
Package useridentity persists credentials on the cluster-scoped UserIdentity CRD and its master credential Secrets, and holds the naming helpers every component derives those names with. |