kratos

package
v0.40.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package kratos provides admin-side provisioning of Ory Kratos identities via the Kratos admin API.

Wire flow:

Admin → POST /api/admin/users
  rivolt API
    └─ kratos.Client.CreateIdentity
         └─ POST {AdminURL}/admin/identities
              ├─ traits.email = email
              ├─ traits.display_name = displayName
              ├─ schema_id = "rivolt_user"
              └─ credentials.password.config.password = <plaintext>

Kratos hashes the password server-side (argon2id by default) and stores the identity in its Postgres DB. No Vault round-trip, no ExternalSecret refresh, no file-watcher polling — the identity is usable in <100 ms.

All operations are idempotent: CreateIdentity returns an error on 409 Conflict (existing email), DeleteIdentity is a no-op on 404.

The package is a no-op when not configured (NewFromEnv returns a nil client when KRATOS_ADMIN_URL is unset). Callers must check Enabled() before relying on it.

Index

Constants

View Source
const (
	RejectTooShort   = "too_short"
	RejectTooLong    = "too_long"
	RejectTooSimilar = "too_similar"
	RejectBreached   = "breached"
	RejectOther      = "other"
)

Rejection codes (RejectedError.Code).

View Source
const (
	// MinPasswordChars is Kratos' default min_password_length. Kratos counts it in bytes; Rivolt counts characters (runes), which is
	// stricter for non-ASCII and matches the words on the screen ("at least 8 characters"): three CJK characters are 9 bytes and
	// would pass a byte count.
	MinPasswordChars = 8
	// MaxPasswordBytes is bcrypt's limit.
	MaxPasswordBytes = 72
)

Kratos applies its password policy in the self-service flows (sign-up, settings, recovery), not to a password set through the admin API: a 6-character password went straight through SetPassword on preview (qa, Oct 25). Rivolt's password changes use the admin API, so the same rules are enforced here, before the write, for every caller. They are Kratos' defaults, from selfservice/strategy/password/validator.go: a minimum of 8 characters (counted in characters here, where Kratos counts bytes), a maximum of 72 bytes (bcrypt's limit; beyond it the tail is silently ignored), and the identifier-similarity check against the account's email. Our Kratos has haveibeenpwned off, so the breach check is not part of it.

Variables

View Source
var ErrInvalidCredentials = errors.New("kratos: invalid credentials")

ErrInvalidCredentials is returned by LoginByPassword when Kratos rejects the email/password pair. Callers should surface a generic 401 to keep the response opaque.

View Source
var ErrNoSession = errors.New("kratos: no active session")

ErrNoSession is returned by Whoami when there is no active Kratos session for the supplied cookie.

View Source
var ErrNotFound = errors.New("kratos: identity not found")

ErrNotFound is returned by admin lookups (GetIdentity) when the requested identity does not exist (Kratos returns 404). Distinct from ErrNoSession (no active session) and ErrInvalidCredentials (login failed): callers usually treat ErrNotFound as a hard 4xx because it means the subject we got from Hydra is stale.

Functions

func CheckPassword added in v0.39.9

func CheckPassword(password, identifier string) string

CheckPassword applies the policy: "" when the password is acceptable, else a rejection code (RejectTooShort, RejectTooLong, RejectTooSimilar).

func ClassifyRejection added in v0.39.9

func ClassifyRejection(reason string) string

ClassifyRejection maps Kratos' password-policy text to a code. The strings are Kratos' own (text/message_validation.go): "The password must be at least N characters long, but got M.", "... at most N characters ...", "The password can not be used because it is too similar to the identifier.", "The password has been found in data breaches and must no longer be used.". Anything else, or wording a future Kratos changes, is RejectOther: the screen then says only that the password is not accepted.

func GeneratePassword added in v0.39.8

func GeneratePassword() (string, error)

GeneratePassword returns a fresh random password (128 bits, 22 URL-safe characters), for the identity provider's own use: a support reset hands it over once and never stores it.

func ReasonFor added in v0.39.9

func ReasonFor(code string) string

ReasonFor is the plain-language text for a code, in Kratos' own wording, for logs and support; screens use the code.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client talks to the Kratos admin API. The zero value is not usable; build via NewFromEnv or New. A nil *Client is safe to call — Enabled() returns false and all mutating methods return errors.

func New

func New(cfg Config) (*Client, error)

New builds a Client from an explicit Config. Returns an error if AdminURL is empty.

func NewFromEnv

func NewFromEnv() (*Client, error)

NewFromEnv builds a Client from environment variables. Returns a nil *Client (and nil error) when KRATOS_ADMIN_URL is unset, so callers can wire up the IdP unconditionally and rely on Enabled() to gate behavior.

Recognised env vars:

KRATOS_ADMIN_URL          — required to enable
KRATOS_PUBLIC_URL         — optional; enables LoginByPassword/Whoami
KRATOS_SCHEMA_ID          — defaults to "rivolt_user"
KRATOS_TIMEOUT            — Go duration; default 10s
KRATOS_GROUPS_ADMIN       — CSV; default "admins"
KRATOS_GROUPS_USER        — CSV; default "users"

func (*Client) CreateIdentity

func (c *Client) CreateIdentity(ctx context.Context, email, displayName, role, password string) error

CreateIdentity provisions a new identity with a caller-supplied password. Returns an error if an identity with this email already exists (Kratos returns 409 Conflict).

func (*Client) CreateIdentityGeneratePassword

func (c *Client) CreateIdentityGeneratePassword(ctx context.Context, email, displayName, role string) (string, error)

CreateIdentityGeneratePassword provisions a new identity with a freshly generated random password and returns the password to the caller. The password is never stored by Rivolt.

func (*Client) DeleteIdentity

func (c *Client) DeleteIdentity(ctx context.Context, email string) error

DeleteIdentity removes the identity matching the given email. No-op when the identity does not exist (404). Email is the canonical login identifier in our schema.

func (*Client) Enabled

func (c *Client) Enabled() bool

Enabled reports whether the client is configured. Safe on nil.

func (*Client) GetIdentity

func (c *Client) GetIdentity(ctx context.Context, id string) (*Identity, error)

GetIdentity fetches an identity by its Kratos id (UUID). The `id` is the value returned from LoginByPassword and the same value Hydra hands back in the consent challenge's `subject` field. Returns ErrNotFound when the identity does not exist.

Used at consent-accept time to look up the email + display name traits of the just-authenticated identity, so we can pour them into Hydra's id_token Session payload — otherwise downstream OIDC clients only see a bare `sub` claim and have to round-trip /userinfo for anything human.

func (*Client) IdentityIDByEmail added in v0.39.5

func (c *Client) IdentityIDByEmail(ctx context.Context, email string) (string, error)

IdentityIDByEmail finds a Kratos identity's id by its email, or "" when there is none (an account that signs in only with Google, GitHub or Apple has no Kratos identity).

func (*Client) LoginByPassword

func (c *Client) LoginByPassword(ctx context.Context, email, password string) (*Identity, error)

LoginByPassword authenticates a user against Kratos's API login flow (no cookies, no CSRF). On success returns the Kratos identity (the .id is what we feed to Hydra's accept-login as `subject`).

Returns ErrInvalidCredentials when Kratos rejects the password pair, so callers can surface a 401 to the browser without leaking whether the email or the password was wrong.

func (*Client) RevokeIdentitySessions added in v0.39.8

func (c *Client) RevokeIdentitySessions(ctx context.Context, id string) error

RevokeIdentitySessions deletes every Kratos session of the identity (DELETE /admin/identities/{id}/sessions), including the ones LoginByPassword itself creates. Idempotent: an identity with none, or one that is gone, is success.

func (*Client) SetPassword added in v0.39.8

func (c *Client) SetPassword(ctx context.Context, id, newPassword string) error

SetPassword sets the identity's password (PUT /admin/identities/{id}). It reads the identity first and sends back every field it must keep (schema, state, traits, both metadata blocks, external id) byte for byte, so nothing else changes; only credentials.password.config.password is new. The password is never logged. Kratos applies its own password policy: a refusal is a *RejectedError, whose reason has the password and the email removed; any other failure carries the status only, never the body. The read and the write are two calls, so a concurrent edit of the traits in between would be lost; nothing edits traits concurrently today (the identity is only created, and its password changed here).

func (*Client) Whoami

func (c *Client) Whoami(ctx context.Context, cookieHeader string) (*Identity, error)

Whoami calls /sessions/whoami with the supplied cookie header (forwarded verbatim from the inbound request). Returns the identity if the session is active; ErrNoSession when Kratos returns 401 or the cookie is missing.

The cookie value is the raw "Cookie:" header from the user's request — Kratos pulls its own session cookie out of it.

type Config

type Config struct {
	// AdminURL is the Kratos admin API base, e.g.
	// http://ory-kratos-admin.ory.svc:80. Required (and the only
	// required field for in-cluster identity-provisioning setups).
	AdminURL string

	// PublicURL is the Kratos public API base, e.g.
	// http://ory-kratos-public.ory.svc:80. Optional; only needed
	// when the caller uses LoginByPassword or Whoami (the Hydra
	// custom login UI in internal/api/hydra.go does).
	PublicURL string

	// SchemaID is the identity schema to use; matches the id in
	// kratos.config.identity.schemas[].id in the Helm values.
	SchemaID string

	// Timeout for individual HTTP calls to the admin API.
	Timeout time.Duration

	// AdminGroups / UserGroups are kept here so call-sites have a
	// single place to map roles to OIDC group strings. Kratos schemas
	// don't have a built-in "groups" concept, so we store the role
	// in a metadata_public
	// field. Use the first entry of each slice as the canonical name.
	AdminGroups []string
	UserGroups  []string

	// HTTPClient is optional; one is constructed from Timeout when nil.
	HTTPClient *http.Client
}

Config holds runtime knobs for the Kratos provisioning client.

Defaults match our in-cluster layout (see rivolt-infra apps/ory/kratos/values.yaml) so a typical deployment only needs to set AdminURL.

type Identity

type Identity struct {
	ID             string             `json:"id"`
	Traits         IdentityTraits     `json:"traits"`
	MetadataPublic IdentityMetaPublic `json:"metadata_public,omitempty"`
	// VerifiableAddresses carries Kratos's per-address verification
	// state; see EmailVerified.
	VerifiableAddresses []VerifiableAddress `json:"verifiable_addresses,omitempty"`
}

Identity is the subset of an identity object we surface from the public Kratos API. It mirrors the fields the admin POST /admin/ identities response contains, but only the parts the Hydra login handler needs to make a "subject" decision.

func (Identity) EmailVerified added in v0.32.1

func (i Identity) EmailVerified() bool

EmailVerified reports whether the identity's trait email is a verified address. False when Kratos doesn't list it (verification disabled) or lists it unverified.

type IdentityMetaPublic

type IdentityMetaPublic struct {
	Role string `json:"role,omitempty"`
}

IdentityMetaPublic carries fields we attach to the identity for display / authorization but don't want users to mutate via self-service flows. Kratos guarantees `metadata_public` is only writable from the admin API.

Today we use it for `role` (admin / user), which the consent handler projects into a `groups` claim on the id_token so downstream OIDC clients (ArgoCD, Grafana, Rivolt) can run their own RBAC off it.

type IdentityTraits

type IdentityTraits struct {
	Email       string `json:"email"`
	DisplayName string `json:"display_name,omitempty"`
}

IdentityTraits matches our rivolt_user schema (email + display).

type RejectedError added in v0.39.8

type RejectedError struct {
	Reason string
	// Code is a stable classification of Reason, so a screen can use its own words and never show Kratos' text: too_short,
	// too_long, too_similar (to the email), breached, or other.
	Code string
}

RejectedError is Kratos refusing a change as invalid (HTTP 400), most often the password policy: too short, too common, too like the email. Reason is Kratos' own text with the password (as sent, and as JSON escapes it) and the account's email removed, redacted before it is shortened.

func (*RejectedError) Error added in v0.39.8

func (e *RejectedError) Error() string

type VerifiableAddress added in v0.32.1

type VerifiableAddress struct {
	Value    string `json:"value"`
	Via      string `json:"via,omitempty"`
	Verified bool   `json:"verified"`
}

VerifiableAddress is one of an identity's contact addresses and whether Kratos has verified it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL