Skip to content

Humans

A person is a human principal: a row in sf_principals with type = human, joined one-to-one with its sf_humans extension. There is no separate user table. Every sign-in method (password, social login, federation sync, SCIM) resolves to the same human principal, so one person has one identity.

Schema

sf_principals holds what every principal type shares:

Field Type Description
id UUID Primary key, used as the subject (sub) in tokens
type enum human, application, agent, or service
identifier string Unique lookup key: the email for humans, or human:<id> for a managed principal
display_name string Human-readable name
organization_id UUID Optional owning organization
active bool Whether the principal can authenticate
capabilities JSON Principal capabilities
allowed_scopes JSON Scopes the principal may request
metadata JSON Custom metadata
created_at, updated_at time Timestamps

sf_humans holds the human-specific fields:

Field Type Description
principal_id UUID The owning principal
email string Primary email address; null only for a managed principal
given_name, family_name string Name parts
avatar_url string Profile picture URL
locale, timezone string Preferences
is_platform_admin bool Cross-organization admin access
last_login_at time Last successful login
email_verified_at time When the email was verified

Related records:

  • Passwords are rows in sf_credentials with type password (Argon2id hashes).
  • Upstream logins (GitHub, Google, a federation hub) are rows in sf_external_identities, keyed by provider and subject.
  • Organization memberships are rows in sf_principal_memberships; see Memberships.

Creating Humans

Use the principal service, which creates the principal and its human extension in one transaction:

import "github.com/plexusone/systemforge/identity/principal"

principals := principal.NewService(client)

p, err := principals.CreateHuman(ctx, principal.CreateHumanInput{
    Email:       "user@example.com",
    DisplayName: "Jane Doe",
    GivenName:   "Jane",
    FamilyName:  "Doe",
})
if err != nil {
    return err
}
// p.ID is the principal ID; p.Human holds the extension fields.

Setting a Password

import "github.com/plexusone/systemforge/identity/credential"

creds := credential.NewService(client)

err := creds.CreatePassword(ctx, credential.CreatePasswordInput{
    PrincipalID: p.ID,
    Password:    password,
})

VerifyPassword and UpdatePassword on the same service check and rotate it. A human without a password credential signs in only through an upstream provider.

Email Requirement

Every human has an email unless it is a managed principal: a human whose account is created and controlled by managers with an email, enabled per deployment and per client. Every creation path documented here (the principal service, SystemAuth storage, signup, SCIM, social login, and federation sync) refuses a human without an email; managed principals are created only through identity/managedprincipal. See Managed Principals.

The identifier is a lookup key, not an address: never send mail to it.

Email Uniqueness

Emails are unique ignoring case. Lookups by email are case-insensitive, and SystemAuth's storage rejects a create or update whose email differs only in case from another human's, so Ada@Example.com and ada@example.com cannot both exist.

Querying Humans

By ID or Email

p, err := principals.GetByID(ctx, principalID)

// The identifier of a human principal is its email.
p, err = principals.GetByIdentifier(ctx, "user@example.com")

With Ent Directly

import (
    "github.com/plexusone/systemforge/identity/ent/human"
    entprincipal "github.com/plexusone/systemforge/identity/ent/principal"
)

p, err := client.Principal.Query().
    Where(
        entprincipal.TypeEQ(entprincipal.TypeHuman),
        entprincipal.HasHumanWith(human.EmailEqualFold("user@example.com")),
    ).
    WithHuman().
    Only(ctx)

Updating Humans

name := "Jane Smith"
family := "Smith"

p, err := principals.UpdateHuman(ctx, principalID, principal.UpdateHumanInput{
    UpdateInput: principal.UpdateInput{DisplayName: &name},
    FamilyName:  &family,
})

Deactivate and Reactivate toggle active; a deactivated principal cannot authenticate.

SystemAuth Storage

The SystemAuth server works with humans through its Storage interface using one value type, systemauth.HumanPrincipal, which joins a human principal with its extension:

Method Purpose
CreateHuman(ctx, h) Create a human; assigns h.ID when it is uuid.Nil
GetHuman(ctx, id) Look up by principal ID
GetHumanByEmail(ctx, email) Look up by email, ignoring case
GetHumanByExternalIdentity(ctx, provider, subject) Look up through a linked upstream identity
LinkExternalIdentity(ctx, principalID, provider, subject) Link an upstream identity; relinking the same pair is a no-op
UpdateHuman(ctx, h) Replace the profile fields
DeleteHuman(ctx, id) Delete the human with its extension, external identities, and credentials

Not-found lookups return systemauth.ErrPrincipalNotFound; a taken email returns systemauth.ErrPrincipalExists; an upstream identity already linked to another human returns systemauth.ErrExternalIdentityExists. Both systemauth.NewEntStorage and systemauth.NewMemoryStorage implement these methods.

Password Login

Password login uses an injectable verifier. Because identity IDs are principal IDs, the credential service's VerifyPassword plugs in directly:

creds := credential.NewService(client)

providers := systemauth.NewProvidersFromStorage(
    systemauth.NewEntStorage(client),
    systemauth.WithProviderPasswordVerifier(creds.VerifyPassword),
)

session, err := providers.Authentication.Authenticate(ctx, &systemauth.AuthenticateRequest{
    Method:     systemauth.AuthMethodPassword,
    Identifier: "user@example.com",
    Password:   password,
})

A person who first signed in with a social provider and later sets a password authenticates as the same principal.

Federation Sync

Identities synced from a federation hub are linked as external identities with the provider systemauth.FederationProvider and the federation identity ID as the subject. Creating an identity whose email already exists links the existing human instead of creating a second one.

Platform Admins

is_platform_admin on the human extension grants cross-organization access:

admin := true
_, err := principals.UpdateHuman(ctx, principalID, principal.UpdateHumanInput{
    IsPlatformAdmin: &admin,
})

Edges

A human principal relates to:

Edge Target Description
human Human Human extension
credentials Credential Passwords, API keys, keypairs
external_identities ExternalIdentity Linked upstream logins
principal_memberships PrincipalMembership Organization memberships
principal_tokens PrincipalToken Issued tokens
owned_organizations Organization Personal organizations
owned_oauth_apps OAuthApp OAuth apps it owns
oauth_consents OAuthConsent Consent grants