schemas

package
v0.0.0-...-43c1654 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// FieldName email is the field name for email
	FieldNameEmail = "email"
	// FieldNamePhoneNumber is the field name for phone number
	FieldNamePhoneNumber = "phone_number"
)

Variables

View Source
var (
	// Prefix for table name / collection names
	Prefix = "authorizer_"
	// Collections / Tables available for authorizer in the database (used for dbs other than gorm)
	Collections = CollectionList{
		User:                   Prefix + "users",
		VerificationRequest:    Prefix + "verification_requests",
		Session:                Prefix + "sessions",
		Env:                    Prefix + "env",
		Webhook:                Prefix + "webhooks",
		WebhookLog:             Prefix + "webhook_logs",
		EmailTemplate:          Prefix + "email_templates",
		OTP:                    Prefix + "otps",
		SMSVerificationRequest: Prefix + "sms_verification_requests",
		Authenticators:         Prefix + "authenticators",
		SessionToken:           Prefix + "session_tokens",
		MFASession:             Prefix + "mfa_sessions",
		OAuthState:             Prefix + "oauth_states",
		AuditLog:               Prefix + "audit_logs",
		Client:                 Prefix + "clients",
		TrustedIssuer:          Prefix + "trusted_issuers",
		Organization:           Prefix + "organizations",
		OrgMembership:          Prefix + "org_memberships",
		FederatedIdentity:      Prefix + "federated_identities",
		ScimEndpoint:           Prefix + "scim_endpoints",
		WebauthnCredential:     Prefix + "webauthn_credentials",
		OrgDomain:              Prefix + "org_domains",
	}
)
View Source
var ErrOrgDomainConflict = errors.New("domain already verified by another organization")

ErrOrgDomainConflict is returned by AddOrgDomain when the normalized domain is already verified by a DIFFERENT organization. It is the atomic first-writer-wins signal — the service layer maps it to "domain_already_verified_by_another_org". A same-org re-verify is NOT a conflict (AddOrgDomain returns the existing row, idempotently).

Functions

This section is empty.

Types

type AuditLog

type AuditLog struct {
	Key          string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID           string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	ActorID      string `gorm:"type:char(36)" json:"actor_id" bson:"actor_id" cql:"actor_id" dynamo:"actor_id,omitempty" index:"actor_id,hash"`
	ActorType    string `gorm:"type:varchar(30)" json:"actor_type" bson:"actor_type" cql:"actor_type" dynamo:"actor_type"`
	ActorEmail   string `gorm:"type:varchar(256)" json:"actor_email" bson:"actor_email" cql:"actor_email" dynamo:"actor_email"`
	Action       string `gorm:"type:varchar(100)" json:"action" bson:"action" cql:"action" dynamo:"action,omitempty" index:"action,hash"`
	ResourceType string `gorm:"type:varchar(50)" json:"resource_type" bson:"resource_type" cql:"resource_type" dynamo:"resource_type"`
	ResourceID   string `gorm:"type:char(36)" json:"resource_id" bson:"resource_id" cql:"resource_id" dynamo:"resource_id"`
	IPAddress    string `gorm:"type:varchar(45)" json:"ip_address" bson:"ip_address" cql:"ip_address" dynamo:"ip_address"`
	UserAgent    string `gorm:"type:text" json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"`
	Metadata     string `gorm:"type:text" json:"metadata" bson:"metadata" cql:"metadata" dynamo:"metadata"`
	CreatedAt    int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
}

AuditLog model for db

func (*AuditLog) AsAPIAuditLog

func (a *AuditLog) AsAPIAuditLog() *model.AuditLog

AsAPIAuditLog converts the database audit log to a GraphQL response object.

type Authenticator

type Authenticator struct {
	Key           string  `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID            string  `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	UserID        string  `` /* 147-byte string literal not displayed */
	Method        string  `gorm:"type:varchar(64);uniqueIndex:idx_authenticator_user_id_method" json:"method" bson:"method" cql:"method" dynamo:"method"`
	Secret        string  `json:"secret" bson:"secret" cql:"secret" dynamo:"secret"`
	RecoveryCodes *string `json:"recovery_codes" bson:"recovery_codes" cql:"recovery_codes" dynamo:"recovery_codes"`
	VerifiedAt    *int64  `json:"verified_at" bson:"verified_at" cql:"verified_at" dynamo:"verified_at"`
	CreatedAt     int64   `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt     int64   `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

Authenticator model for db

type Client

type Client struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// ClientID is the public OAuth client_id presented at the authorize/token
	// endpoints. It is distinct from the surrogate primary key ID: ID is an
	// internal UUID, ClientID is the externally-referenced identifier and is
	// UNIQUE across the registry. ClientID is IMMUTABLE after creation.
	//
	// For the reserved interactive client seeded at boot, ClientID equals the
	// deployment's Config.ClientID verbatim (BC1) — every token aud, JWKS kid,
	// and introspection client_id continues to key on that exact string.
	//
	// When left empty on create, every provider defaults ClientID to ID so the
	// unique index always holds and lookups keep working for admin-created
	// clients that do not (yet) supply an explicit client_id — ID has
	// historically doubled as the client_id.
	ClientID string `gorm:"uniqueIndex" json:"client_id" bson:"client_id" cql:"client_id" dynamo:"client_id" index:"client_id,hash"`

	// Kind distinguishes the client type. Values: "interactive" (human-facing
	// login clients) | "service_account" (machine/workload clients). It is
	// immutable after creation. This rename step defaults existing and newly
	// created rows to "service_account"; the interactive kind and its
	// additional fields (redirect_uris, grant_types, etc.) land in a later step.
	Kind string `json:"kind" bson:"kind" cql:"kind" dynamo:"kind"`

	// Name is a human-readable label for this service account (e.g. "payments-worker").
	Name string `json:"name" bson:"name" cql:"name" dynamo:"name"`

	// Description is an optional free-text note.
	Description *string `json:"description" bson:"description" cql:"description" dynamo:"description"`

	// ClientSecret holds the bcrypt hash of the credential.
	// Never expose this value in API responses.
	// json:"-" keeps it out of any json.Marshal of this struct (structured
	// logging, webhook payloads, error dumps), matching User.Password.
	ClientSecret string `json:"-" bson:"client_secret" cql:"client_secret" dynamo:"client_secret"`

	// AllowedScopes is a comma-separated list of OAuth2 scopes this service
	// account may request. Scopes in a client_credentials request MUST be a
	// strict subset of this list.
	//
	// SECURITY: the service layer MUST trim whitespace and drop empty segments
	// when parsing this field (e.g. "read, write" → ["read","write"]).
	// Empty string MUST be treated as DENY-ALL, not grant-all, in the token
	// endpoint — an unparseable or empty AllowedScopes must reject the request.
	AllowedScopes string `json:"allowed_scopes" bson:"allowed_scopes" cql:"allowed_scopes" dynamo:"allowed_scopes"`

	// RedirectURIs is a comma-separated allow-list of exact redirect URIs for
	// interactive clients. Empty for service_account clients. The service layer
	// MUST exact-match a presented redirect_uri against a parsed segment of this
	// list — never a prefix or substring match.
	RedirectURIs string `json:"redirect_uris" bson:"redirect_uris" cql:"redirect_uris" dynamo:"redirect_uris"`

	// GrantTypes is a comma-separated list of OAuth2 grant types this client is
	// allowed to use (e.g. "authorization_code,refresh_token" for interactive
	// clients, "client_credentials" for service accounts).
	GrantTypes string `json:"grant_types" bson:"grant_types" cql:"grant_types" dynamo:"grant_types"`

	// TokenEndpointAuthMethod is how the client authenticates at the token
	// endpoint: "client_secret_basic" | "client_secret_post" | "none" (public
	// client authenticating via PKCE only).
	TokenEndpointAuthMethod string `` /* 136-byte string literal not displayed */

	// IsActive controls whether this service account may authenticate.
	// Flipping to false blocks new token issuance immediately; existing
	// tokens remain valid until their exp.
	//
	// GORM NOTE: gorm:"default:true" means db.Create with IsActive=false will
	// persist as true (Go zero-value triggers the column default). The service
	// layer must always set IsActive explicitly and use Save (not Create-only)
	// when creating a disabled account.
	IsActive bool `json:"is_active" bson:"is_active" cql:"is_active" dynamo:"is_active" gorm:"default:true"`

	// OrgID optionally scopes this client to an organization. A nil value means
	// the client is global (not org-scoped). This is a plain nullable column,
	// NOT a cross-table foreign key — five of the six storage providers are
	// NoSQL and cannot enforce an FK constraint, so referential integrity is the
	// service layer's responsibility.
	//
	// No `omitempty` on the json/bson tags: a nil pointer must serialize to
	// null so an update clears a previously-set org (see
	// docs/storage-optional-null-fields.md).
	OrgID *string `json:"org_id" bson:"org_id" cql:"org_id" dynamo:"org_id" gorm:"index"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

Client is a registered OAuth client in the unified client registry.

The Kind discriminator distinguishes interactive, human-facing clients (browser/app login flows) from machine service accounts (the OAuth2 client_credentials grant and workload identity federation). Both kinds share this single registry so client authentication has one authoritative source of truth.

Authentication:

  • client_id = ID (UUID)
  • client_secret = bcrypt hash (cost 12); plaintext returned ONCE at creation and ONCE at rotation — never again.

This struct intentionally omits email, phone, MFA, social-login, and session fields — those belong to User. Mixing human and machine identity in the same table is an anti-pattern explicitly avoided here. See: authorizer-docs repo, specs/WORKLOAD_IDENTITY_PROGRAM.md §3.

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

func (*Client) AsAPIClient

func (s *Client) AsAPIClient() *model.Client

AsAPIClient converts the storage record into the GraphQL model. It never exposes ClientSecret — there is no client_secret field on model.Client by design; the plaintext is surfaced only once via CreateClientResponse at creation/rotation.

func (*Client) ParsedAllowedScopes

func (s *Client) ParsedAllowedScopes() []string

ParsedAllowedScopes returns AllowedScopes as a slice: comma-separated, whitespace trimmed, empty segments dropped. This is the single source of truth for interpreting the stored scope string — the token endpoint uses it to enforce that a client_credentials request stays within the authorized set. An empty or whitespace-only AllowedScopes yields an empty slice, which the token endpoint MUST treat as DENY-ALL (schema § AllowedScopes comment).

type CollectionList

type CollectionList struct {
	User                   string
	VerificationRequest    string
	Session                string
	Env                    string
	Webhook                string
	WebhookLog             string
	EmailTemplate          string
	OTP                    string
	SMSVerificationRequest string
	Authenticators         string
	SessionToken           string
	MFASession             string
	OAuthState             string
	AuditLog               string
	Client                 string
	TrustedIssuer          string
	Organization           string
	OrgMembership          string
	FederatedIdentity      string
	ScimEndpoint           string
	WebauthnCredential     string
	OrgDomain              string
}

CollectionList / Tables available for authorizer in the database

type EmailTemplate

type EmailTemplate struct {
	Key       string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID        string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
	Subject   string `json:"subject" bson:"subject" cql:"subject" dynamo:"subject"`
	Template  string `json:"template" bson:"template" cql:"template" dynamo:"template"`
	Design    string `json:"design" bson:"design" cql:"design" dynamo:"design"`
	CreatedAt int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

EmailTemplate model for database

func (*EmailTemplate) AsAPIEmailTemplate

func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate

AsAPIEmailTemplate to return email template as graphql response object

type Env

type Env struct {
	Key           string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID            string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	EnvData       string `json:"env" bson:"env" cql:"env" dynamo:"env"`
	Hash          string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
	EncryptionKey string `json:"encryption_key" bson:"encryption_key" cql:"encryption_key" dynamo:"encryption_key"` // couchbase has "hash" as reserved keyword so we cannot use it. This will be empty for other dbs.
	UpdatedAt     int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
	CreatedAt     int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
}

Env model for db

type FederatedIdentity

type FederatedIdentity struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// OrgID is the organization whose SSO connection provisioned the identity.
	// Part of the unique (org_id, issuer, subject) constraint.
	OrgID string `` /* 130-byte string literal not displayed */

	// Issuer is the upstream IdP's `iss` claim value.
	// Part of the unique (org_id, issuer, subject) constraint.
	Issuer string `gorm:"uniqueIndex:idx_federated_identity_org_iss_sub" json:"issuer" bson:"issuer" cql:"issuer" dynamo:"issuer"`

	// Subject is the upstream `sub` claim value (stable per-IdP user identifier).
	// Part of the unique (org_id, issuer, subject) constraint.
	Subject string `gorm:"uniqueIndex:idx_federated_identity_org_iss_sub" json:"subject" bson:"subject" cql:"subject" dynamo:"subject"`

	// UserID is the Authorizer user this federated identity maps to.
	UserID string `json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" gorm:"index" index:"user_id,hash"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

FederatedIdentity records that an end user was provisioned (JIT) from a specific upstream identity at a specific organization's SSO connection.

SECURITY (design §4.4, account-takeover defense): a federated login is keyed by the tuple (OrgID, Issuer, Subject) — NEVER by email alone. On callback the broker looks up this row to find the already-provisioned Authorizer user for a returning federated principal; an email that merely collides with some other account is NOT sufficient to link. The (org_id, issuer, subject) triple is UNIQUE: the SQL providers enforce it with a composite unique index; the NoSQL providers enforce it with a compound unique index (Mongo/Arango) or a check-then-insert guard (Cassandra/Couchbase/DynamoDB). The service layer also looks up before inserting so behaviour is uniform across every backend.

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

type MFASession

type MFASession struct {
	Key       string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID        string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	UserID    string `` /* 140-byte string literal not displayed */
	KeyName   string `gorm:"type:varchar(255);index:idx_mfa_session_user_id_key" json:"key_name" bson:"key_name" cql:"key_name" dynamo:"key_name"`
	ExpiresAt int64  `gorm:"index" json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
	CreatedAt int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

MFASession model for storing MFA sessions in database This replaces the in-memory storage for MFA sessions when Redis is not configured

type OAuthState

type OAuthState struct {
	Key       string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID        string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	StateKey  string `` /* 128-byte string literal not displayed */
	State     string `gorm:"type:text" json:"state" bson:"state" cql:"state" dynamo:"state"`
	CreatedAt int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

OAuthState model for storing OAuth state in database This replaces the in-memory storage for OAuth state when Redis is not configured

type OTP

type OTP struct {
	Key         string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID          string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	Email       string `gorm:"index" json:"email" bson:"email" cql:"email" dynamo:"email,omitempty" index:"email,hash"`
	PhoneNumber string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
	Otp         string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
	ExpiresAt   int64  `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
	CreatedAt   int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt   int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

OTP model for database

type OrgDomain

type OrgDomain struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	// ID is the primary key: the normalized domain (punycode, lowercased). Not a
	// uuid — the domain IS the key so uniqueness is enforced by the DB atomically.
	ID string `gorm:"primaryKey;type:varchar(255)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// OrgID is the organization this verified domain routes to. Indexed for
	// ListOrgDomainsByOrg; NOT unique (an org may verify many domains).
	OrgID string `gorm:"index" json:"org_id" bson:"org_id" cql:"org_id" dynamo:"org_id" index:"org_id,hash"`

	// Domain is the normalized domain as a readable column (== ID).
	Domain string `json:"domain" bson:"domain" cql:"domain" dynamo:"domain"`

	// VerifiedAt is when the domain became verified (unix seconds).
	VerifiedAt int64 `json:"verified_at" bson:"verified_at" cql:"verified_at" dynamo:"verified_at"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

OrgDomain is a VERIFIED mapping from a DNS domain to exactly one organization, used for home-realm discovery (routing a login to the right tenant IdP). A row exists ONLY once a domain is verified; a pending verification challenge is ephemeral state in the memory store, never a row here.

Uniqueness is the load-bearing security invariant (one verified domain → one org). It is enforced by the PRIMARY/partition key being the normalized domain itself, NOT a secondary unique index: DynamoDB, Cassandra and Couchbase cannot enforce a unique constraint on a non-key attribute, so a check-then-insert guard would race. With the domain as the key, first-writer-wins is atomic on every backend (unique PK / conditional put / INSERT ... IF NOT EXISTS).

ID holds the normalized domain and is the primary key. Domain holds the same value as a human-readable column (never expose ID directly — on ArangoDB a read populates it with the "collection/key" handle; use Domain / AsAPIOrgDomain).

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

func (*OrgDomain) AsAPIOrgDomain

func (d *OrgDomain) AsAPIOrgDomain() *model.OrgDomain

AsAPIOrgDomain converts the storage record into the GraphQL model. It exposes Domain (never ID — on ArangoDB a read populates ID with the "collection/key" handle).

type OrgMembership

type OrgMembership struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// OrgID references the Organization this membership belongs to.
	// Part of the unique (org_id, user_id) constraint.
	OrgID string `gorm:"uniqueIndex:idx_org_membership_org_user" json:"org_id" bson:"org_id" cql:"org_id" dynamo:"org_id" index:"org_id,hash"`

	// UserID references the User who is a member.
	// Part of the unique (org_id, user_id) constraint; also indexed on its own
	// for the "list an org membership by user" query.
	UserID string `` /* 134-byte string literal not displayed */

	// Roles is a comma-separated list of per-organization roles.
	Roles string `json:"roles" bson:"roles" cql:"roles" dynamo:"roles"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

OrgMembership links a User to an Organization with a set of per-org roles.

A user may belong to many organizations, each with independent roles; an organization has many members. The (OrgID, UserID) pair is UNIQUE — a user can be a member of a given organization at most once. The SQL providers enforce this with a composite unique index; the NoSQL providers enforce it with a compound unique index (Mongo/Arango) or a check-then-insert guard (Cassandra/Couchbase/DynamoDB). The service layer additionally rejects duplicates up-front so behaviour is uniform across every backend.

Roles is a comma-separated list of per-organization role names, mirroring the storage convention used by Client.AllowedScopes. Use ParsedRoles to interpret it; the service layer normalizes on write.

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

func (*OrgMembership) AsAPIOrgMember

func (m *OrgMembership) AsAPIOrgMember() *model.OrgMember

AsAPIOrgMember converts the storage record into the GraphQL model.

func (*OrgMembership) ParsedRoles

func (m *OrgMembership) ParsedRoles() []string

ParsedRoles returns Roles as a slice: comma-separated, whitespace trimmed, empty segments dropped. An empty or whitespace-only Roles yields an empty slice.

type Organization

type Organization struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// Name is the unique organization slug (e.g. "acme-corp").
	Name string `gorm:"uniqueIndex" json:"name" bson:"name" cql:"name" dynamo:"name" index:"name,hash"`

	// DisplayName is the human-readable organization name.
	DisplayName *string `json:"display_name" bson:"display_name" cql:"display_name" dynamo:"display_name"`

	// Enabled controls whether the organization is active.
	//
	// GORM NOTE: gorm:"default:true" means db.Create with Enabled=false will
	// persist as true (Go zero-value triggers the column default). The service
	// layer must always set Enabled explicitly and use Save (not Create-only)
	// when creating a disabled organization.
	Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled" gorm:"default:true"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

Organization is a tenant grouping that owns per-org memberships (and, in later phases, SSO/SCIM connections and (org, client) grants). It is managed by platform admins through the `_organization`/`_organizations` admin API.

Name is a unique, URL-safe slug (the stable external identifier); DisplayName is the human-readable label. Enabled gates whether the organization is active; disabling it does not delete its memberships.

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

func (*Organization) AsAPIOrganization

func (o *Organization) AsAPIOrganization() *model.Organization

AsAPIOrganization converts the storage record into the GraphQL model.

type Paging

type Paging struct {
	ID string `json:"id,omitempty" dynamo:"id,hash"`
}

type ScimEndpoint

type ScimEndpoint struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// OrgID is the organization this SCIM endpoint provisions into. Unique:
	// one SCIM endpoint per org.
	OrgID string `gorm:"uniqueIndex" json:"org_id" bson:"org_id" cql:"org_id" dynamo:"org_id" index:"org_id,hash"`

	// TokenHash is the bcrypt hash of the endpoint's bearer-token secret.
	// json:"-" keeps it out of any json.Marshal (structured logging, webhook
	// payloads, error dumps), matching User.Password / Client.ClientSecret.
	TokenHash string `json:"-" bson:"token_hash" cql:"token_hash" dynamo:"token_hash"`

	// Enabled gates whether the endpoint accepts requests. Disabling it stops
	// provisioning without deleting the row (rotate/re-enable later).
	//
	// GORM NOTE: gorm:"default:true" means Create with Enabled=false persists as
	// true (Go zero-value triggers the column default). The service layer always
	// sets Enabled explicitly and uses Save when creating a disabled endpoint.
	Enabled bool `gorm:"default:true" json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

ScimEndpoint is the per-organization inbound SCIM 2.0 connection credential. A customer's IdP (Okta, Entra, …) authenticates to /scim/v2/ with a bearer token; the org it may provision is derived ONLY from the matched endpoint (design §4.4 H6 — never from the URL or payload).

Authentication:

  • The presented bearer token is "<endpointID>.<hexSecret>". The endpointID selects this row; the secret is verified (constant-time bcrypt) against TokenHash. The plaintext is revealed ONCE at create and ONCE at rotate.

One endpoint per org: OrgID is unique.

Note: any field addition must also be reflected in the cassandradb provider; it does not use GORM AutoMigrate for collection creation.

func (*ScimEndpoint) AsAPIScimEndpoint

func (s *ScimEndpoint) AsAPIScimEndpoint() *model.ScimEndpoint

AsAPIScimEndpoint converts the storage record into the GraphQL model. The token secret is never included — it is revealed only at create/rotate.

type Session

type Session struct {
	Key       string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID        string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	UserID    string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"`
	UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"`
	IP        string `json:"ip" bson:"ip" cql:"ip" dynamo:"ip"`
	CreatedAt int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

Session model for db

type SessionToken

type SessionToken struct {
	Key       string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID        string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	UserID    string `` /* 142-byte string literal not displayed */
	KeyName   string `gorm:"type:varchar(255);index:idx_session_token_user_id_key" json:"key_name" bson:"key_name" cql:"key_name" dynamo:"key_name"`
	Token     string `gorm:"type:text" json:"token" bson:"token" cql:"token" dynamo:"token"`
	ExpiresAt int64  `gorm:"index" json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
	CreatedAt int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

SessionToken model for storing user session tokens in database This replaces the in-memory storage for session tokens when Redis is not configured

type TrustedIssuer

type TrustedIssuer struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// Kind discriminates the trust relationship this row represents (design §4.3 /
	// §5 K1): constants.TrustKindClientAssertion (default), TrustKindSSOOIDC, or
	// TrustKindSSOSAML (reserved). Immutable after creation.
	//
	// SECURITY (design §5.2 CR1): the client_assertion resolver accepts ONLY
	// client_assertion_trust rows (with an empty OrgID). An sso_oidc row — which
	// has NO subject pin — must never be reachable on the client-authentication
	// path. A pre-existing row written before this column existed reads back as ""
	// and is interpreted as client_assertion_trust by EffectiveKind, so upgrades
	// don't break existing trust rows.
	Kind string `json:"kind" bson:"kind" cql:"kind" dynamo:"kind" gorm:"default:'client_assertion_trust'"`

	// OrgID scopes an SSO connection (sso_oidc/sso_saml) to one Organization.
	// EMPTY for client_assertion_trust rows (which are instance-global). Immutable.
	// Part of the (kind, org_id) lookup that finds an org's SSO connection.
	OrgID string `json:"org_id" bson:"org_id" cql:"org_id" dynamo:"org_id" gorm:"index" index:"org_id,hash"`

	// ClientID links this issuer to the Client it authenticates. Empty on
	// sso_oidc/sso_saml rows (which federate users, not clients). client_id is a
	// DynamoDB GSI key and DynamoDB rejects an empty-string key value, so
	// dynamo:"...,omitempty" omits it when empty — the row simply doesn't appear
	// in the client_id GSI (sparse index), which is correct for SSO rows.
	ClientID string `json:"client_id" bson:"client_id" cql:"client_id" dynamo:"client_id,omitempty" gorm:"index" index:"client_id,hash"`

	// Name is a human-readable label (e.g. "prod-k8s-cluster").
	Name string `json:"name" bson:"name" cql:"name" dynamo:"name"`

	// IssuerURL is the `iss` claim value expected in presented JWTs.
	// For SPIFFE issuers this is the trust domain URI (e.g. "spiffe://example.org").
	// Unique per Authorizer instance — one URL maps to one TrustedIssuer.
	IssuerURL string `gorm:"uniqueIndex" json:"issuer_url" bson:"issuer_url" cql:"issuer_url" dynamo:"issuer_url" index:"issuer_url,hash"`

	// KeySourceType determines how the JWKS key set is fetched. See type-level docs.
	KeySourceType string `json:"key_source_type" bson:"key_source_type" cql:"key_source_type" dynamo:"key_source_type"`

	// JWKSUrl is required when KeySourceType is "static_jwks_url".
	// Strongly preferred over "oidc_discovery" for private K8s clusters (S10).
	JWKSUrl *string `json:"jwks_url" bson:"jwks_url" cql:"jwks_url" dynamo:"jwks_url"`

	// ExpectedAud is the audience value the presented token MUST contain (S1).
	// For K8s SA tokens this MUST be set to Authorizer's own issuer URL so that
	// tokens minted for other services cannot be replayed here.
	ExpectedAud string `json:"expected_aud" bson:"expected_aud" cql:"expected_aud" dynamo:"expected_aud"`

	// SubjectClaim is the JWT claim used to identify the workload. Defaults to "sub".
	SubjectClaim string `json:"subject_claim" bson:"subject_claim" cql:"subject_claim" dynamo:"subject_claim"`

	// AllowedSubjects is a comma-separated allow-list of the exact subject values
	// (the value of the SubjectClaim claim) permitted to authenticate as this
	// issuer's Client — e.g. "system:serviceaccount:prod:payments".
	//
	// SECURITY (design §5.2 C1): an empty AllowedSubjects is DENY-ALL, mirroring
	// Client.AllowedScopes. A row with no configured subjects authenticates
	// nobody; the client_assertion resolver MUST reject when the parsed list is
	// empty. Matching is EXACT (never prefix/substring) to defeat subject
	// confusion (H3): "prod-evil" must not satisfy a pin of "prod".
	AllowedSubjects string `json:"allowed_subjects" bson:"allowed_subjects" cql:"allowed_subjects" dynamo:"allowed_subjects"`

	// IssuerType categorises the issuer. See type-level docs for valid values.
	IssuerType string `json:"issuer_type" bson:"issuer_type" cql:"issuer_type" dynamo:"issuer_type"`

	// AuthMethod selects the client-authentication mechanism. See type-level docs.
	// Defaults to "jwt_assertion".
	AuthMethod string `json:"auth_method" bson:"auth_method" cql:"auth_method" dynamo:"auth_method" gorm:"default:'jwt_assertion'"`

	// IsActive controls whether this issuer is accepted for authentication.
	IsActive bool `json:"is_active" bson:"is_active" cql:"is_active" dynamo:"is_active" gorm:"default:true"`

	// EnableTokenReview activates online K8s TokenReview validation (kubernetes_sa only).
	// When true, Authorizer calls the K8s API server after offline JWT verification to
	// confirm the bound Pod/ServiceAccount still exists.
	// Default false — offline JWKS validation only (same as Keycloak default).
	// SECURITY NOTE (S7): offline-only validation accepts tokens for deleted objects
	// until exp. Enable this flag for high-security workloads.
	EnableTokenReview bool `json:"enable_token_review" bson:"enable_token_review" cql:"enable_token_review" dynamo:"enable_token_review"`

	// KubernetesAPIServerURL is required when EnableTokenReview is true.
	// Example: "https://kubernetes.default.svc:443"
	KubernetesAPIServerURL *string `` /* 132-byte string literal not displayed */

	// SpiffeRefreshHintSeconds controls the JWKS refresh cadence for
	// "spiffe_bundle_endpoint" key sources. Default 300 (5 min) when 0.
	// HARD RUNTIME REQUIREMENT: failure to refresh at this cadence will reject
	// valid SVIDs after a trust-bundle key rotation.
	SpiffeRefreshHintSeconds int64 `` /* 140-byte string literal not displayed */

	// TrustedProxyHeader is the HTTP header name from which to read a
	// forwarded client certificate (PEM or DER base64) in Model B deployments.
	// Empty means direct TLS only (Model A).
	TrustedProxyHeader *string `json:"trusted_proxy_header" bson:"trusted_proxy_header" cql:"trusted_proxy_header" dynamo:"trusted_proxy_header"`

	// TrustedProxyCIDRs is a comma-separated list of CIDR ranges whose requests
	// are permitted to supply TrustedProxyHeader.
	// MANDATORY when TrustedProxyHeader is set — requests from outside this list
	// that carry the header MUST be rejected to prevent certificate spoofing.
	TrustedProxyCIDRs *string `json:"trusted_proxy_cidrs" bson:"trusted_proxy_cidrs" cql:"trusted_proxy_cidrs" dynamo:"trusted_proxy_cidrs"`

	// SSOClientID is the client_id Authorizer was issued AT the upstream IdP.
	SSOClientID string `json:"sso_client_id" bson:"sso_client_id" cql:"sso_client_id" dynamo:"sso_client_id"`

	// SSOClientSecretEnc is the upstream client_secret, AES-encrypted at rest
	// (crypto.EncryptAES keyed on Config.ClientSecret — reversible because the
	// value is replayed to the upstream token endpoint; a bcrypt hash could not be
	// used). json:"-" so it NEVER serializes into an API/webhook/log projection.
	SSOClientSecretEnc string `json:"-" bson:"sso_client_secret_enc" cql:"sso_client_secret_enc" dynamo:"sso_client_secret_enc"`

	// SSOScopes is the space-separated scope set requested at the upstream IdP.
	// Defaults to "openid profile email" when empty.
	SSOScopes string `json:"sso_scopes" bson:"sso_scopes" cql:"sso_scopes" dynamo:"sso_scopes"`

	// SSORedirectURI is the redirect_uri registered at the upstream IdP that points
	// back at Authorizer's broker callback. When empty the broker derives
	// {scheme}://{host}/oauth/sso/{org}/callback from the request host.
	SSORedirectURI string `json:"sso_redirect_uri" bson:"sso_redirect_uri" cql:"sso_redirect_uri" dynamo:"sso_redirect_uri"`

	// SAMLSSOURL is the upstream IdP Single Sign-On endpoint the signed
	// AuthnRequest is sent to (HTTP-Redirect binding).
	SAMLSSOURL *string `json:"saml_sso_url" bson:"saml_sso_url" cql:"saml_sso_url" dynamo:"saml_sso_url"`

	// SAMLIDPCertPEM is the IdP's X.509 signing certificate (PEM). Assertion
	// signatures are validated ONLY against this certificate — the sole trust
	// anchor for this org's connection.
	SAMLIDPCertPEM *string `json:"saml_idp_cert_pem" bson:"saml_idp_cert_pem" cql:"saml_idp_cert_pem" dynamo:"saml_idp_cert_pem"`

	// SAMLSPEntityID is the SP entity ID Authorizer advertises for this org (the
	// assertion Audience an incoming assertion MUST target). When empty the SP
	// derives {scheme}://{host}/oauth/saml/{org}/metadata from the request host.
	SAMLSPEntityID *string `json:"saml_sp_entity_id" bson:"saml_sp_entity_id" cql:"saml_sp_entity_id" dynamo:"saml_sp_entity_id"`

	// SAMLACSURL is the Assertion Consumer Service URL for this org (the
	// Recipient/Destination an incoming assertion MUST target). When empty the SP
	// derives {scheme}://{host}/oauth/saml/{org}/acs from the request host.
	SAMLACSURL *string `json:"saml_acs_url" bson:"saml_acs_url" cql:"saml_acs_url" dynamo:"saml_acs_url"`

	// SAMLAttributeMapping is a JSON object mapping Authorizer profile fields to
	// upstream SAML attribute names, e.g.
	// {"email":"email","given_name":"firstName","family_name":"lastName"}.
	// The NameID is ALWAYS the federated-identity subject; this map only governs
	// optional profile-attribute extraction. Empty means "use default names".
	SAMLAttributeMapping *string `json:"saml_attribute_mapping" bson:"saml_attribute_mapping" cql:"saml_attribute_mapping" dynamo:"saml_attribute_mapping"`

	// SAMLAllowIDPInitiated permits IdP-initiated SSO (a POST to ACS with no
	// matching pending AuthnRequest). DEFAULT FALSE — SP-initiated only, with
	// InResponseTo bound to a pending request. Enable only if the org's IdP does
	// not support SP-initiated flows and the operator accepts the reduced CSRF
	// protection. NOTE: enabling this disables InResponseTo validation for ALL
	// responses on this connection (crewjam limitation), including SP-initiated
	// ones — the assertion then relies solely on the single-use AssertionID cache
	// for replay defence.
	SAMLAllowIDPInitiated bool `` /* 128-byte string literal not displayed */

	CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

TrustedIssuer registers an external JWT issuer whose tokens are accepted as client credentials for a Client (RFC 7523 client_assertion flow).

One Client may have multiple TrustedIssuers (e.g. K8s SA tokens AND a SPIFFE JWT-SVID from the same workload). Each TrustedIssuer maps to exactly one Client.

Supported issuer types (IssuerType field):

  • "kubernetes_sa" — Kubernetes projected ServiceAccount tokens (Phase 4)
  • "spiffe_jwt" — SPIFFE JWT-SVIDs (Phase 5, preview)
  • "oidc" — Generic OIDC / cloud-provider tokens (Phase 3)
  • "cloud_oidc" — AWS/GCP/Azure workload identity tokens (Phase 3)

Key-source types (KeySourceType field):

  • "oidc_discovery" — fetch JWKS via <IssuerURL>/.well-known/openid-configuration
  • "static_jwks_url" — fetch JWKS directly from JWKSUrl (required for private clusters)
  • "spiffe_bundle_endpoint" — fetch from a SPIFFE bundle endpoint (Phase 5)

Authentication methods (AuthMethod field):

  • "jwt_assertion" — RFC 7523 client_assertion JWT (Phases 3–5, default)
  • "x509_mtls" — SPIFFE X.509-SVID via mTLS (Phase 6)

Security invariants enforced at the service layer (not here):

S1  — aud claim in presented token MUST equal ExpectedAud.
S2  — alg allow-list: RS/ES/PS 256/384/512 only; none and HS* rejected.
S3  — exp claim MUST be present and valid.
S7  — offline JWKS validation does not confirm the bound K8s object still
      exists; EnableTokenReview provides online hardening for Phase 4.
S10 — static_jwks_url is always available; OIDC discovery is never forced.

Note: any field addition must also be reflected in the cassandradb provider.

func (*TrustedIssuer) AsAPITrustedIssuer

func (t *TrustedIssuer) AsAPITrustedIssuer() *model.TrustedIssuer

AsAPITrustedIssuer converts the storage record into the GraphQL model. The Phase 4 Kubernetes TokenReview fields (EnableTokenReview, KubernetesAPIServerURL) are surfaced in the admin API; the remaining Phase 5/6 fields (SPIFFE bundle refresh, mTLS proxy) are intentionally not surfaced.

func (*TrustedIssuer) EffectiveKind

func (t *TrustedIssuer) EffectiveKind() string

EffectiveKind returns the row's Kind, treating an empty value as TrustKindClientAssertion. A row written before the kind column existed reads back with an empty Kind; interpreting that as client_assertion_trust keeps existing trust rows working across an upgrade AND keeps the CR1 guard correct (an sso_oidc row always carries an explicit non-empty Kind, so it can never be mistaken for a client_assertion row).

func (*TrustedIssuer) ParsedAllowedSubjects

func (t *TrustedIssuer) ParsedAllowedSubjects() []string

ParsedAllowedSubjects returns AllowedSubjects as a slice: comma-separated, whitespace trimmed, empty segments dropped. This is the single source of truth for interpreting the stored subject allow-list — the client_assertion resolver uses it to exact-match the presented token's subject claim. An empty or whitespace-only AllowedSubjects yields an empty slice, which the resolver MUST treat as DENY-ALL (schema § AllowedSubjects comment).

type User

type User struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID  string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	Email                    *string `gorm:"index" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
	EmailVerifiedAt          *int64  `json:"email_verified_at" bson:"email_verified_at" cql:"email_verified_at" dynamo:"email_verified_at"`
	Password                 *string `json:"-" bson:"password" cql:"password" dynamo:"password"`
	SignupMethods            string  `json:"signup_methods" bson:"signup_methods" cql:"signup_methods" dynamo:"signup_methods"`
	GivenName                *string `json:"given_name" bson:"given_name" cql:"given_name" dynamo:"given_name"`
	FamilyName               *string `json:"family_name" bson:"family_name" cql:"family_name" dynamo:"family_name"`
	MiddleName               *string `json:"middle_name" bson:"middle_name" cql:"middle_name" dynamo:"middle_name"`
	Nickname                 *string `json:"nickname" bson:"nickname" cql:"nickname" dynamo:"nickname"`
	Gender                   *string `json:"gender" bson:"gender" cql:"gender" dynamo:"gender"`
	Birthdate                *string `json:"birthdate" bson:"birthdate" cql:"birthdate" dynamo:"birthdate"`
	PhoneNumber              *string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
	PhoneNumberVerifiedAt    *int64  `` /* 128-byte string literal not displayed */
	Picture                  *string `json:"picture" bson:"picture" cql:"picture" dynamo:"picture"`
	Roles                    string  `json:"roles" bson:"roles" cql:"roles" dynamo:"roles"`
	RevokedTimestamp         *int64  `json:"revoked_timestamp" bson:"revoked_timestamp" cql:"revoked_timestamp" dynamo:"revoked_timestamp"`
	IsMultiFactorAuthEnabled *bool   `` /* 144-byte string literal not displayed */
	// HasSkippedMFASetupAt is set the moment a user explicitly skips the
	// optional MFA setup prompt shown at login (never set when EnforceMFA is
	// on — skip is not offered in that mode). Nil means "never skipped."
	HasSkippedMFASetupAt *int64  `` /* 128-byte string literal not displayed */
	UpdatedAt            int64   `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
	CreatedAt            int64   `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	AppData              *string `json:"app_data" bson:"app_data" cql:"app_data" dynamo:"app_data"`

	// ExternalID is the stable key an external IdP (SCIM/SSO) assigns to this
	// user. It is nullable — only IdP-provisioned users carry one. For SCIM it
	// is namespaced per org as "<orgID>:<idpExternalId>" (see
	// Provider.GetUserByExternalID) so one org's IdP can never resolve another
	// org's user by external id (design §4.4 H6).
	ExternalID *string `gorm:"index" json:"external_id" bson:"external_id" cql:"external_id" dynamo:"external_id" index:"external_id,hash"`

	// IsActive controls whether the user is provisioned/active. SCIM
	// deprovisioning (active:false / DELETE) sets this false and revokes the
	// user's sessions. Existing rows default to true (gorm column default);
	// the service layer always sets it explicitly so the GORM zero-value
	// default:true quirk never silently re-activates a deprovisioned user.
	IsActive bool `gorm:"default:true" json:"is_active" bson:"is_active" cql:"is_active" dynamo:"is_active"`
}

User model for db

func (*User) AsAPIUser

func (user *User) AsAPIUser() *model.User

func (*User) MatchesSearch

func (user *User) MatchesSearch(query string) bool

MatchesSearch reports whether the user matches a case-insensitive substring query across id, email, given_name, family_name and nickname. An empty query matches everything. Used by storage backends without a native substring index (DynamoDB, Cassandra/ScyllaDB) to filter in application code.

func (*User) ToMap

func (user *User) ToMap() map[string]interface{}

type VerificationRequest

type VerificationRequest struct {
	Key         string `json:"_key,omitempty" bson:"_key" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID          string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	Token       string `json:"token" bson:"token" cql:"jwt_token" dynamo:"token" index:"token,hash"`
	Identifier  string `` /* 129-byte string literal not displayed */
	ExpiresAt   int64  `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
	Email       string `gorm:"uniqueIndex:idx_email_identifier;type:varchar(256)" json:"email" bson:"email" cql:"email" dynamo:"email"`
	Nonce       string `json:"nonce" bson:"nonce" cql:"nonce" dynamo:"nonce"`
	RedirectURI string `json:"redirect_uri" bson:"redirect_uri" cql:"redirect_uri" dynamo:"redirect_uri"`
	CreatedAt   int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt   int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

VerificationRequest model for db

func (*VerificationRequest) AsAPIVerificationRequest

func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest

type WebauthnCredential

type WebauthnCredential struct {
	Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // ArangoDB document key

	ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`

	// UserID links the credential to its owning User. Indexed to list a user's
	// passkeys and to authorize deletion against the authenticated caller.
	UserID string `json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" gorm:"index" index:"user_id,hash"`

	// CredentialID is the base64-standard-encoded WebAuthn credential ID. It is
	// globally UNIQUE and is the only key that resolves a user during a
	// usernameless (discoverable) login, so it must be indexed and unique.
	CredentialID string `` /* 130-byte string literal not displayed */

	// PublicKey is the base64-standard-encoded COSE public key used to verify
	// assertions.
	PublicKey string `json:"public_key" bson:"public_key" cql:"public_key" dynamo:"public_key"`

	// SignCount is the authenticator signature counter. go-webauthn rejects a
	// login whose counter regresses below this value (cloned-authenticator
	// detection); it is written back on every successful login.
	SignCount int64 `json:"sign_count" bson:"sign_count" cql:"sign_count" dynamo:"sign_count"`

	// Flags is the raw single-byte go-webauthn CredentialFlags (UP/UV/BE/BS).
	// NOT in the original design spec but REQUIRED for correctness: go-webauthn's
	// login validation rejects a credential whose stored BackupEligible flag
	// disagrees with the assertion, and synced passkeys report BE=1 — so the flag
	// captured at registration must be persisted and restored, or every synced
	// passkey login fails.
	Flags int64 `json:"flags" bson:"flags" cql:"flags" dynamo:"flags"`

	// Transports is the comma-separated list of authenticator transports the
	// credential supports (e.g. "internal,hybrid").
	Transports string `json:"transports" bson:"transports" cql:"transports" dynamo:"transports"`

	// AAGUID is the base64-standard-encoded authenticator model identifier.
	AAGUID string `json:"aaguid" bson:"aaguid" cql:"aaguid" dynamo:"aaguid"`

	// Name is a user-supplied label (e.g. "MacBook Touch ID").
	Name string `json:"name" bson:"name" cql:"name" dynamo:"name"`

	CreatedAt  int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt  int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
	LastUsedAt *int64 `json:"last_used_at" bson:"last_used_at" cql:"last_used_at" dynamo:"last_used_at"`
}

WebauthnCredential is a single WebAuthn/passkey credential registered to a User. One User may own many credentials (multiple devices/passkeys).

Binary WebAuthn values (credential id, COSE public key, AAGUID) are stored as base64-standard strings and Transports as a comma-separated string so the row persists uniformly across all backends (SQL text, Cassandra text, DynamoDB S) — the same []byte/[]string-avoidance convention used by every other schema.

Note: any field addition must also be reflected in the cassandradb provider.

func (*WebauthnCredential) AsAPIWebauthnCredential

func (w *WebauthnCredential) AsAPIWebauthnCredential() *model.WebauthnCredentialInfo

AsAPIWebauthnCredential converts the storage record into the GraphQL model. It never exposes the public key, sign count or AAGUID — only the metadata a user needs to manage their passkeys.

func (*WebauthnCredential) ParsedTransports

func (w *WebauthnCredential) ParsedTransports() []string

ParsedTransports returns Transports as a slice: comma-separated, trimmed, empty segments dropped.

type Webhook

type Webhook struct {
	Key              string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID               string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	EventName        string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
	EventDescription string `json:"event_description" bson:"event_description" cql:"event_description" dynamo:"event_description"`
	EndPoint         string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
	Headers          string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
	Enabled          bool   `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
	CreatedAt        int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt        int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

Webhook model for db

func (*Webhook) AsAPIWebhook

func (w *Webhook) AsAPIWebhook() *model.Webhook

AsAPIWebhook to return webhook as graphql response object

type WebhookLog

type WebhookLog struct {
	Key        string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
	ID         string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
	HttpStatus int64  `json:"http_status" bson:"http_status" cql:"http_status" dynamo:"http_status"`
	Response   string `json:"response" bson:"response" cql:"response" dynamo:"response"`
	Request    string `json:"request" bson:"request" cql:"request" dynamo:"request"`
	WebhookID  string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id" index:"webhook_id,hash"`
	CreatedAt  int64  `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
	UpdatedAt  int64  `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
}

WebhookLog model for db

func (*WebhookLog) AsAPIWebhookLog

func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog

AsAPIWebhookLog to return webhook log as graphql response object

Jump to

Keyboard shortcuts

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