identity

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

View Source
const (
	HITLMaxTTLSeconds        = 604800 // 7 days
	HITLDefaultTTLSeconds    = 604800
	HITLExpirationApprove    = "approve"
	HITLExpirationReject     = "reject"
	HITLDefaultExpirationAct = HITLExpirationReject
)

HITL constants mirror the CHECK constraints in migration 003_hitl.sql.

View Source
const (
	MessageStatusSent            = "sent"
	MessageStatusPendingApproval = "pending_approval"
	MessageStatusRejected        = "rejected"
	MessageStatusExpiredApproved = "expired_approved"
	MessageStatusExpiredRejected = "expired_rejected"
)

Message status values mirror the CHECK constraint in migration 003_hitl.sql.

View Source
const MessageTTL = 30 * 24 * time.Hour // 30 days
View Source
const SessionTTL = 7 * 24 * time.Hour

Variables

View Source
var ErrDomainHasAgents = fmt.Errorf("cannot delete domain: agents still exist")

ErrDomainHasAgents is returned when a domain delete is blocked by existing agents.

View Source
var ErrDomainNotFound = fmt.Errorf("domain not found or not owned by user")

ErrDomainNotFound is returned when a domain is not found or not owned by the user.

View Source
var ErrMessageNotFound = fmt.Errorf("message not found")

ErrMessageNotFound is returned when a message is not found for the given user (either the ID doesn't exist or the message belongs to another user's agent). Handlers map this to HTTP 404.

View Source
var ErrNotPendingApproval = fmt.Errorf("message is not pending approval")

ErrNotPendingApproval is returned when an approve or reject operation targets a message that is not (or is no longer) in pending_approval status. Handlers map this to HTTP 409 Conflict.

Functions

func NewMessageID

func NewMessageID() string

NewMessageID returns a fresh internal message ID. Callers can use this to generate the ID up-front when they need it before storing — for example, the SMTP relay generates the ID before signing auth headers so the ID is part of the canonical string fed to HMAC.

func ValidateHITLConfig

func ValidateHITLConfig(ttlSeconds int, expirationAction string) error

ValidateHITLConfig returns an error if the TTL or expiration action is invalid. The DB CHECK constraints are the final guard; this mirrors them for a clean, pre-query error path.

Types

type APIKey

type APIKey struct {
	ID           string    `json:"id"`
	UserID       string    `json:"user_id"`
	Name         string    `json:"name"`
	KeyPrefix    string    `json:"key_prefix"`
	PlaintextKey string    `json:"key,omitempty"` // only set once at creation, never stored
	CreatedAt    time.Time `json:"created_at"`
}

type APIKeyExportEntry

type APIKeyExportEntry struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	KeyPrefix  string     `json:"key_prefix"`
	CreatedAt  time.Time  `json:"created_at"`
	LastUsedAt *time.Time `json:"last_used_at,omitempty"`
	RevokedAt  *time.Time `json:"revoked_at,omitempty"`

} // @name APIKeyExportEntry

APIKeyExportEntry is the API-key shape included in the export. We expose only metadata; the hash itself stays internal because (a) it's not useful to the user, (b) it's a credential equivalent for offline dictionary attacks if leaked.

type AgentIdentity

type AgentIdentity struct {
	ID                   string    `json:"id"`
	Domain               string    `json:"domain"`
	Email                string    `json:"email"`
	Name                 string    `json:"name"`
	WebhookURL           string    `json:"webhook_url"`
	AgentMode            string    `json:"agent_mode"`
	DomainVerified       bool      `json:"domain_verified"`
	Public               bool      `json:"public"`
	CreatedAt            time.Time `json:"created_at"`
	UserID               string    `json:"user_id"`
	HITLEnabled          bool      `json:"hitl_enabled"`
	HITLTTLSeconds       int       `json:"hitl_ttl_seconds"`
	HITLExpirationAction string    `json:"hitl_expiration_action"`
}

func (*AgentIdentity) ActualDomain

func (a *AgentIdentity) ActualDomain() string

ActualDomain returns the DNS domain for the agent.

func (*AgentIdentity) EmailAddress

func (a *AgentIdentity) EmailAddress() string

EmailAddress returns the agent's email address (always the ID).

func (*AgentIdentity) IsSharedDomain

func (a *AgentIdentity) IsSharedDomain(sharedDomain string) bool

IsSharedDomain returns true if the agent's domain matches the configured shared domain (the host that backs slug-based registration). When sharedDomain is empty, the deployment has slug registration disabled and no agent can be on the shared domain.

type DeleteUserDataResult

type DeleteUserDataResult struct {
	UsageEventsDeleted    int64 `json:"usage_events_deleted"`
	UsageSummariesDeleted int64 `json:"usage_summaries_deleted"`
	MessagesDeleted       int64 `json:"messages_deleted"`
	AgentsDeleted         int64 `json:"agents_deleted"`
	DomainsDeleted        int64 `json:"domains_deleted"`
	APIKeysDeleted        int64 `json:"api_keys_deleted"`
	SessionsDeleted       int64 `json:"sessions_deleted"`
	UserDeleted           bool  `json:"user_deleted"`

} // @name DeleteUserDataResult

DeleteUserDataResult breaks out per-table row counts for audit logs. Operators receiving a deletion request often have to attest to what was removed; returning structured counts beats parsing a log line.

type Domain

type Domain struct {
	Domain            string     `json:"domain"`
	UserID            *string    `json:"user_id,omitempty"`
	Verified          bool       `json:"verified"`
	VerificationToken string     `json:"verification_token"`
	CreatedAt         time.Time  `json:"created_at"`
	VerifiedAt        *time.Time `json:"verified_at,omitempty"`
}

Domain represents a verified or unverified domain registered by a user.

type ExpirationCandidate

type ExpirationCandidate struct {
	MessageID        string
	AgentID          string
	ExpirationAction string // 'approve' or 'reject'
}

ExpirationCandidate is the minimal row the expiration worker needs to decide how to finalize an expired pending message.

type Message

type Message struct {
	ID                string            `json:"id"`
	AgentID           string            `json:"agent_id"`
	Direction         string            `json:"direction"`
	Sender            string            `json:"sender"`
	Recipient         string            `json:"recipient"`
	Subject           string            `json:"subject"`
	EmailMessageID    string            `json:"email_message_id,omitempty"`
	ProviderMessageID string            `json:"provider_message_id,omitempty"`
	Method            string            `json:"method,omitempty"`
	Type              string            `json:"type,omitempty"`
	RawMessage        []byte            `json:"raw_message,omitempty"`
	AuthHeaders       map[string]string `json:"auth_headers,omitempty"`
	ConversationID    string            `json:"conversation_id,omitempty"`
	DeliveryStatus    string            `json:"delivery_status,omitempty"`
	CreatedAt         time.Time         `json:"created_at"`
	ExpiresAt         time.Time         `json:"expires_at"`
	WebhookStatus     string            `json:"webhook_status,omitempty"`
	WebhookError      string            `json:"webhook_error,omitempty"`
	WebhookAttempts   int               `json:"webhook_attempts,omitempty"`

	// Outbound-only multi-recipient fields. Nil for inbound messages.
	ToRecipients []string `json:"to_recipients,omitempty"`
	CC           []string `json:"cc,omitempty"`
	BCC          []string `json:"bcc,omitempty"`

	// HITL approval fields. Status defaults to 'sent'; body and attachments
	// are populated only while a message is in 'pending_approval', and are
	// scrubbed on any terminal transition.
	Status            string          `json:"status,omitempty"`
	ApprovalExpiresAt *time.Time      `json:"approval_expires_at,omitempty"`
	ReviewedAt        *time.Time      `json:"reviewed_at,omitempty"`
	RejectionReason   string          `json:"rejection_reason,omitempty"`
	Edited            bool            `json:"edited,omitempty"`
	BodyText          string          `json:"body_text,omitempty"`
	BodyHTML          string          `json:"body_html,omitempty"`
	AttachmentsJSON   json.RawMessage `json:"attachments,omitempty"`
}

type PendingApprovalEdit

type PendingApprovalEdit struct {
	Subject         *string
	BodyText        *string
	BodyHTML        *string
	To              []string
	CC              []string
	BCC             []string
	AttachmentsJSON []byte
	// AttachmentsSet must be true when the caller intends to override
	// AttachmentsJSON, since nil and empty [] are both valid overrides
	// (empty [] clears attachments; nil preserves).
	AttachmentsSet bool
}

PendingApprovalEdit holds optional overrides a reviewer can apply when approving a pending message. Pointer-typed strings distinguish "not provided" (nil) from "explicitly empty" (pointer to ""). Slice fields distinguish "unset" (nil) from "empty list" (non-nil zero-length slice).

func (PendingApprovalEdit) Apply

func (e PendingApprovalEdit) Apply(msg *Message) bool

Apply mutates msg to reflect any fields the reviewer changed. Returns true if any field was actually different from what msg already held (signals the edited flag should be set).

type SendResult

type SendResult struct {
	ProviderMessageID string
	Method            string
	To                []string
	CC                []string
	BCC               []string
}

SendResult carries the outcome of a sender.Send invocation back to the store for final persistence. Handlers wrap their sender.Send call in a closure that returns this type.

type Store

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

func NewStore

func NewStore(pool *pgxpool.Pool) *Store

func (*Store) ApproveAndSend

func (s *Store) ApproveAndSend(
	ctx context.Context,
	messageID, userID string,
	edits PendingApprovalEdit,
	send func(msg *Message) (SendResult, error),
) (*Message, error)

ApproveAndSend finalizes a pending_approval message by running it through a caller-supplied send function inside a transaction that holds a row lock on the pending row. If send returns an error the transaction rolls back and the message remains pending. On success the row is updated to 'sent' with the provider-assigned Message-ID and the body/attachments columns are scrubbed.

edits, if any fields are populated, are applied to the in-memory message before send is called and the 'edited' column is set to true when any field differs from what was stored. Approval-via-magic-link callers pass the zero edits value.

Ownership is enforced by the agent -> user join. Messages owned by another user return ErrMessageNotFound. Messages whose status is not 'pending_approval' return ErrNotPendingApproval.

Concurrency / failure mode notes:

  • The row-level FOR UPDATE lock is held for the duration of the send callback. In practice that is bounded by outbound.SMTPRelay's per- attempt deadline (2min) plus its internal retry backoff (1s/5s/15s) — worst case ~6.5min of lock on this single row. Other rows are unaffected; deadlock is not possible because only one row is ever locked per call.

  • There is a narrow crash window where send() may succeed at SES but the subsequent UPDATE or Commit fails (DB connection drop, pool exhaustion). The transaction rolls back, the row stays pending, and a retry — by the same reviewer or the expiration worker — would re-send to SES. Eliminating this requires SES-side idempotency keys or a separate "send attempts" table; deferred for v1. Callers that see both a successful send callback and a subsequent error from this function should log both rather than silently retry.

func (*Store) BootstrapUser

func (s *Store) BootstrapUser(ctx context.Context, email string) (*User, error)

BootstrapUser finds a user by email, or creates one with a synthetic google_subject if none exists. Used by the -bootstrap-email CLI flag for self-host first-run, where there's no Google OAuth flow yet.

func (*Store) ClaimOrCreateDomain

func (s *Store) ClaimOrCreateDomain(ctx context.Context, domain, userID string) (*Domain, error)

ClaimOrCreateDomain implements the atomic create/claim logic from the design doc. Creates if new, overwrites user_id if unverified, returns if verified+same user, errors if verified+different user. Callers are responsible for rejecting the configured shared domain before invoking this — the store has no concept of a reserved domain.

func (*Store) CreateAPIKey

func (s *Store) CreateAPIKey(ctx context.Context, userID, name string) (*APIKey, error)

func (*Store) CreateAgent

func (s *Store) CreateAgent(ctx context.Context, agentEmail, domain, name, webhookURL, agentMode, userID string) (*AgentIdentity, error)

CreateAgent inserts an agent with a domain FK. Does NOT check domain ownership — that's the API handler's responsibility (shared domain skips the check).

func (*Store) CreateInboundMessage

func (s *Store) CreateInboundMessage(ctx context.Context, id, agentID, senderEmail, recipient, emailMessageID, subject, conversationID, deliveryStatus string, rawMessage []byte, authHeaders map[string]string) (*Message, error)

CreateInboundMessage stores an inbound message. If id is empty a new one is generated; otherwise the caller's pre-generated ID is used so the upstream signer can bind auth headers to the same ID that gets stored.

func (*Store) CreateOrGetUser

func (s *Store) CreateOrGetUser(ctx context.Context, email, name, googleSub string) (*User, error)

func (*Store) CreateOutboundMessage

func (s *Store) CreateOutboundMessage(ctx context.Context, agentID string, toRecipients []string, cc []string, bcc []string, subject, msgType, method, providerMessageID, conversationID string) (*Message, error)

CreateOutboundMessage stores an outbound message with multi-recipient support. The recipient param is kept for backward compat with the singular recipient column; toRecipients, cc, and bcc are the canonical outbound-only multi-recipient fields.

func (*Store) CreatePendingOutboundMessage

func (s *Store) CreatePendingOutboundMessage(ctx context.Context, agentID string, toRecipients, cc, bcc []string, subject, bodyText, bodyHTML string, attachmentsJSON []byte, msgType, conversationID, replyToEmailMessageID string, ttlSeconds int) (*Message, error)

CreatePendingOutboundMessage stores a fully composed outbound email in pending_approval status, including body_text, body_html, and attachments so that approval can reconstruct the original SendRequest (or accept edits) without the caller needing to retain it. ttlSeconds sets how long the message remains pending before the expiration worker resolves it.

replyToEmailMessageID is the RFC 5322 Message-ID of the inbound being replied to (e.g. "<abc@gmail.com>"), or "" for fresh sends and test emails. It reuses the email_message_id column, which is unused for outbound rows in every other path — the column semantically carries "the Message-ID this row references" in both directions.

attachmentsJSON must be a JSON array matching the public Attachment shape ([{filename, content_type, data}, ...]) or nil. Callers that already have an []outbound.Attachment slice should json.Marshal it before passing in.

func (*Store) CreateUserSession

func (s *Store) CreateUserSession(ctx context.Context, userID string) (string, error)

func (*Store) DeleteAPIKey

func (s *Store) DeleteAPIKey(ctx context.Context, keyID, userID string) error

func (*Store) DeleteAgent

func (s *Store) DeleteAgent(ctx context.Context, agentID, userID string) error

func (*Store) DeleteDomain

func (s *Store) DeleteDomain(ctx context.Context, domain, userID string) error

DeleteDomain deletes a domain only if owned by the user. The handler should check for existing agents first.

func (*Store) DeleteExpiredMessages

func (s *Store) DeleteExpiredMessages(ctx context.Context) (int64, error)

func (*Store) DeleteExpiredUserSessions

func (s *Store) DeleteExpiredUserSessions(ctx context.Context) (int64, error)

func (*Store) DeleteUserData

func (s *Store) DeleteUserData(ctx context.Context, userID string) (*DeleteUserDataResult, error)

DeleteUserData wipes everything tied to a user in a single transaction.

Schema cascades cover most of it (user_sessions, domains, agent_identities, api_keys, usage_summaries all `ON DELETE CASCADE` from users; messages cascade through agent_identities; webhook_deliveries cascade through messages). The one row that doesn't is usage_events: its FK is `ON DELETE SET NULL` so analytics survives, which we explicitly override here for full deletion.

Per-table counts are returned to the caller so an operator can attest to what was removed in audit / compliance contexts.

func (*Store) DeleteUserSession

func (s *Store) DeleteUserSession(ctx context.Context, token string) error

func (*Store) EnsureSharedDomain

func (s *Store) EnsureSharedDomain(ctx context.Context, domain string) error

EnsureSharedDomain inserts a system row for the configured shared mail domain so slug-based agent registration can satisfy the agent_identities.domain → domains.domain foreign key. The row is owned by no user (user_id = NULL) and pre-verified — it represents infrastructure the operator runs, not user-claimed identity.

Called once at server startup. Idempotent via ON CONFLICT DO NOTHING, and a no-op when the operator has not configured a shared domain. Without this, any deployment whose shared_domain differs from the hardcoded migration seed (`agents.e2a.dev`) gets an FK violation the first time a user tries to register a slug-based agent.

func (*Store) ExpireApproveAndSend

func (s *Store) ExpireApproveAndSend(
	ctx context.Context,
	messageID string,
	send func(msg *Message) (SendResult, error),
) (*Message, error)

ExpireApproveAndSend is the worker-side counterpart to ApproveAndSend: no user ownership check (the caller is the expiration worker, which is system-scoped), SELECT ... FOR UPDATE SKIP LOCKED so concurrent workers don't race for the same row, and the terminal status is 'expired_approved' instead of 'sent'. On send failure the transaction rolls back; the worker should then call ExpireReject to move the row to a final state so the row doesn't get picked up on every sweep.

Same concurrency / crash-window notes as ApproveAndSend apply: the row-level lock is held for the duration of the send callback (bounded by SMTPRelay timeouts), and a crash between SES acceptance and commit can leave a pending row that would re-send on the next sweep. SKIP LOCKED means multiple app instances can run the worker without contending on the same row.

func (*Store) ExpireReject

func (s *Store) ExpireReject(ctx context.Context, messageID, reason string) (*Message, error)

ExpireReject transitions a pending_approval message to expired_rejected and scrubs body columns. No user ownership check — this is the worker path. If the row is no longer pending (racing worker, already handled) returns ErrNotPendingApproval; caller can treat as a no-op.

func (*Store) ExportUserData

func (s *Store) ExportUserData(ctx context.Context, userID string) (*UserExport, error)

ExportUserData gathers everything a user owns into a single struct for the right-of-access flow. Reads run inside a REPEATABLE READ transaction so the snapshot is internally consistent even if writes arrive while the export is being assembled.

func (*Store) GetAgentByEmail

func (s *Store) GetAgentByEmail(ctx context.Context, email string) (*AgentIdentity, error)

GetAgentByEmail looks up an agent by email address (same as GetAgentByID since ID = email).

func (*Store) GetAgentByID

func (s *Store) GetAgentByID(ctx context.Context, id string) (*AgentIdentity, error)

GetAgentByID looks up an agent by its ID (full email) with domain verification status.

func (*Store) GetInboundMessage

func (s *Store) GetInboundMessage(ctx context.Context, id string) (*Message, error)

func (*Store) GetMessageWithContent

func (s *Store) GetMessageWithContent(ctx context.Context, messageID, agentID string) (*Message, error)

GetMessageWithContent returns a full message including raw_message and auth_headers. Marks the message as 'read' if it was 'unread'.

func (*Store) GetMessagesByAgent

func (s *Store) GetMessagesByAgent(ctx context.Context, agentID, status string, limit int, afterTime time.Time, afterID string) ([]Message, error)

GetMessagesByAgent returns messages for a poll-mode agent, filtered by status.

func (*Store) GetOutboundMessageForUser

func (s *Store) GetOutboundMessageForUser(ctx context.Context, messageID, userID string) (*Message, error)

GetOutboundMessageForUser returns a full message row (including body, HITL fields, and attachments) if it exists and is owned by userID (via the agent the row belongs to). Inbound messages and cross-user access both return ErrMessageNotFound — the caller should not be able to distinguish "does not exist" from "belongs to someone else".

func (*Store) GetUserByAPIKey

func (s *Store) GetUserByAPIKey(ctx context.Context, apiKey string) (*User, error)

func (*Store) GetUserByID

func (s *Store) GetUserByID(ctx context.Context, id string) (*User, error)

func (*Store) GetUserSession

func (s *Store) GetUserSession(ctx context.Context, token string) (*User, error)

func (*Store) HasAgentsOnDomain

func (s *Store) HasAgentsOnDomain(ctx context.Context, domain, userID string) (bool, error)

HasAgentsOnDomain checks whether the owned domain still has agents.

func (*Store) ListAPIKeys

func (s *Store) ListAPIKeys(ctx context.Context, userID string) ([]APIKey, error)

func (*Store) ListActivityByAgent

func (s *Store) ListActivityByAgent(ctx context.Context, agentID string, limit int) ([]Message, error)

func (*Store) ListAgentsByUser

func (s *Store) ListAgentsByUser(ctx context.Context, userID string) ([]AgentIdentity, error)

ListAgentsByUser returns all agents owned by the user, joined with domain verification.

func (*Store) ListDomainsByUser

func (s *Store) ListDomainsByUser(ctx context.Context, userID string) ([]Domain, error)

ListDomainsByUser returns all domains owned by the user (excludes system rows).

func (*Store) ListExpiredPending

func (s *Store) ListExpiredPending(ctx context.Context, limit int) ([]ExpirationCandidate, error)

ListExpiredPending returns pending_approval messages whose approval_expires_at is in the past, joined with their agent's hitl_expiration_action. Ordered by approval_expires_at ASC so earliest-expired are handled first.

func (*Store) ListPendingOutboundForUser

func (s *Store) ListPendingOutboundForUser(ctx context.Context, userID string, limit int) ([]Message, error)

ListPendingOutboundForUser returns pending-approval messages across all of the user's agents, sorted by approval_expires_at ASC (expiring-soonest first). Body columns are not returned from this path — callers should use GetOutboundMessageForUser for detail.

func (*Store) LookupConversationID

func (s *Store) LookupConversationID(ctx context.Context, agentID string, messageIDs []string) (string, error)

LookupConversationID finds a conversation_id by matching In-Reply-To / References message IDs against stored messages. Checks both email_message_id (inbound) and provider_message_id (outbound). Uses prefix matching because SES bare IDs stored in provider_message_id (e.g. <010f...>) may lack the @region.amazonses.com suffix that appears in the actual email headers sent to recipients.

func (*Store) LookupDomain

func (s *Store) LookupDomain(ctx context.Context, domain, userID string) (*Domain, error)

LookupDomain returns a domain if it exists and is owned by the given user.

func (*Store) RejectPending

func (s *Store) RejectPending(ctx context.Context, messageID, userID, reason string) (*Message, error)

RejectPending transitions a pending_approval message to rejected, records the reviewer's reason (empty string allowed), and scrubs body_text / body_html / attachments_json. Ownership checked; missing rows return ErrMessageNotFound. Non-pending rows return ErrNotPendingApproval.

func (*Store) ResolveOutboundOwner

func (s *Store) ResolveOutboundOwner(ctx context.Context, messageID string) (userID, agentID string, err error)

ResolveOutboundOwner looks up the user_id and agent_id for an outbound message without requiring the caller to know the user_id up-front. It exists for token-authenticated paths (magic-link approve/reject) where the HMAC token itself is the authorization and the handler just needs enough context to dispatch into the existing user-scoped store methods.

Returns ErrMessageNotFound if the message doesn't exist or isn't outbound. The returned user_id is guaranteed to own the message's agent (via the agent_identities.user_id join).

func (*Store) UpdateAgentHITL

func (s *Store) UpdateAgentHITL(ctx context.Context, agentID, userID string, enabled bool, ttlSeconds int, expirationAction string) error

UpdateAgentHITL updates all three HITL settings on an agent owned by userID. The TTL and expiration action are validated against the same rules as the DB CHECK constraints so callers get a clean error rather than a raw SQL error.

func (*Store) UpdateAgentMode

func (s *Store) UpdateAgentMode(ctx context.Context, agentID, userID, agentMode, webhookURL string) error

func (*Store) UpdateAgentWebhook

func (s *Store) UpdateAgentWebhook(ctx context.Context, agentID, userID, webhookURL string) error

func (*Store) UpdateMessageDeliveryStatus

func (s *Store) UpdateMessageDeliveryStatus(ctx context.Context, messageID, agentID, status string) error

UpdateMessageDeliveryStatus sets the inbox_status on a message.

func (*Store) VerifyDomain

func (s *Store) VerifyDomain(ctx context.Context, domain, userID string) error

VerifyDomain marks a domain as verified, only if owned by the given user.

type UsageEventEntry

type UsageEventEntry struct {
	ID        string    `json:"id"`
	AgentID   string    `json:"agent_id"`
	Domain    string    `json:"domain"`
	Direction string    `json:"direction"`
	EventType string    `json:"event_type"`
	CreatedAt time.Time `json:"created_at"`

} // @name UsageEventEntry

UsageEventEntry is one row of the usage_events table for the user.

type User

type User struct {
	ID            string    `json:"id"`
	Email         string    `json:"email"`
	Name          string    `json:"name"`
	GoogleSubject string    `json:"-"`
	CreatedAt     time.Time `json:"created_at"`
}

type UserExport

type UserExport struct {
	GeneratedAt   time.Time           `json:"generated_at"`
	SchemaVersion string              `json:"schema_version"`
	User          UserExportUser      `json:"user"`
	Domains       []Domain            `json:"domains"`
	Agents        []AgentIdentity     `json:"agents"`
	APIKeys       []APIKeyExportEntry `json:"api_keys"`
	Messages      []Message           `json:"messages"`
	UsageEvents   []UsageEventEntry   `json:"usage_events,omitempty"`

} // @name UserExport

UserExport is the structured dump returned by ExportUserData. It's designed to be a complete, machine-readable record of everything a single user owns in the system — the right-of-access counterpart to DeleteUserData below. Sensitive secrets are deliberately excluded:

  • API key plaintexts are not stored anywhere (only hashes), so they can't be exported even in principle.
  • google_subject is an internal OAuth identifier with no value to the user and is omitted on purpose.
  • User session tokens are transient and excluded.

type UserExportUser

type UserExportUser struct {
	ID        string    `json:"id"`
	Email     string    `json:"email"`
	Name      string    `json:"name"`
	CreatedAt time.Time `json:"created_at"`

} // @name UserExportUser

UserExportUser mirrors User but omits the google_subject internal identifier from the export payload.

Jump to

Keyboard shortcuts

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