iam

package
v1.832.13 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package iam is ai's INTERNAL IAM client: the small, clean OIDC+REST surface ai needs to talk to Hanzo IAM (hanzo.id), decoupled from the retired SDK module github.com/hanzoai/iam-v1.

It is NOT a published SDK. It exists so ai links only against standard OIDC (JWKS token verification) and the IAM server's JSON REST API (/v1/iam/...), instead of pulling in the whole iam-v1 server module (beego, xorm, ldap, aliyun/aws SDKs, …) that it dragged into ai's build for code ai never ran.

The wire types (User, Claims, Permission, Resource, …) mirror the IAM server's JSON models so requests round-trip losslessly — in particular the full User field set with its json tags is reproduced verbatim, because object/redact.go reflects over every User field by json tag as a fail-secure secret-redaction control. Slimming User would silently weaken that control and break its test, so fidelity is required, not optional.

Endpoint resolution: callers configure a Client explicitly via InitConfig / NewClient (ai's account bootstrap does), and the package-level helpers fall back to a lazily-built client from IAM_ENDPOINT / IAM_ISSUER (default https://hanzo.id) so a read never nil-panics when InitConfig was skipped.

Index

Constants

View Source
const PlatformOwner = "admin"

GetApplication fetches an application by name (owned by admin). PlatformOwner is the reserved org that holds cross-tenant configuration: applications, organizations and the signing certs applications point at. It is not a tenant and nothing customer-facing lives in it.

It is a constant, and named, because these reads MUST agree. An application addressed in one partition and its certificate in another resolves an app and then fails to resolve the key that app's tokens are signed with — which reads as a missing cert rather than as a mis-addressed one.

Variables

This section is empty.

Functions

func AddPermission

func AddPermission(permission *Permission) (bool, error)

func DeletePermission

func DeletePermission(permission *Permission) (bool, error)

func DeleteResourceWithTag

func DeleteResourceWithTag(resource *Resource, tag string) (bool, error)

func GetOAuthToken

func GetOAuthToken(code, state string, opts ...OAuthOption) (*oauth2.Token, error)

GetOAuthToken exchanges a code using the configured (or env-derived) client.

func InitConfig

func InitConfig(endpoint, clientId, clientSecret, certificate, organizationName, applicationName string)

InitConfig sets the package-global Client used by the package-level helpers (GetUser, ParseJwtToken, …). ai's account bootstrap calls this with the deployed IAM endpoint and app credentials.

func SendEmail

func SendEmail(title, content, sender string, receivers ...string) error

SendEmail uses the configured (or env-derived) client.

func SetHttpClient

func SetHttpClient(httpClient HttpClient)

SetHttpClient overrides the shared http client (tests).

func UpdatePermission

func UpdatePermission(permission *Permission) (bool, error)

func UpdateUserForColumns

func UpdateUserForColumns(user *User, columns []string) (bool, error)

func UploadResource

func UploadResource(user, tag, parent, fullFilePath string, fileBytes []byte) (string, string, error)

Types

type Address

type Address struct {
	Tag     string `json:"tag"`
	Line1   string `json:"line1"`
	Line2   string `json:"line2"`
	City    string `json:"city"`
	State   string `json:"state"`
	ZipCode string `json:"zipCode"`
	Region  string `json:"region"`
}

Address is one postal address on a User.

type Application

type Application struct {
	Owner        string   `json:"owner"`
	Name         string   `json:"name"`
	DisplayName  string   `json:"displayName"`
	Cert         string   `json:"cert"`
	RedirectUris []string `json:"redirectUris"`
}

Application is the subset of the IAM Application model ai reads: DisplayName, the signing cert name, and the OAuth redirect allowlist. Read-only response (GetApplication), so a projection is correct.

func GetApplication

func GetApplication(name string) (*Application, error)

GetApplication uses the configured (or env-derived) client.

type AuthConfig

type AuthConfig struct {
	Endpoint         string
	ClientId         string
	ClientSecret     string
	Certificate      string
	OrganizationName string
	ApplicationName  string
}

AuthConfig is the connection config for a Client: the IAM server endpoint and the app credentials/context requests are made under.

type Cert

type Cert struct {
	Owner           string `json:"owner"`
	Name            string `json:"name"`
	DisplayName     string `json:"displayName"`
	Scope           string `json:"scope"`
	Type            string `json:"type"`
	CryptoAlgorithm string `json:"cryptoAlgorithm"`
	Certificate     string `json:"certificate"`
}

Cert is the subset of the IAM Cert model ai reads. ai consumes the PEM in Certificate (to verify token signatures when JWKS is unavailable).

func GetCert

func GetCert(name string) (*Cert, error)

GetCert uses the configured (or env-derived) client.

type Claims

type Claims struct {
	User
	AccessToken string `json:"accessToken"`
	jwt.RegisteredClaims
	TokenType        string `json:"tokenType"`
	RefreshTokenType string `json:"TokenType"`
	SigninMethod     string `json:"signinMethod"`
	// Orgs is the signed membership set. The type is account's, not a local
	// re-declaration: the JSON tags are a wire contract with IAM, and a copy of
	// them here is a copy that can drift one field at a time. If it did, every
	// membership would decode empty and every org switch would fail closed to
	// home — presenting as "the switcher does nothing".
	Orgs []account.OrgRef `json:"orgs,omitempty"`
}

Claims is the verified access-token claim set. It embeds the User (so a token carries the subject's profile) plus the standard registered claims and the Hanzo-specific token/org/billing claims. Shape matches the IAM server so the /get-account response promotes every field identically.

func ParseJwtToken

func ParseJwtToken(token string) (*Claims, error)

ParseJwtToken verifies a JWT's signature against the IAM server's published JWKS (proper OIDC) and returns its claims. RS256/RS512/ES256/ES512 only.

type Client

type Client struct {
	AuthConfig
	CustomHeaders map[string]string
}

Client talks to a Hanzo IAM server over its /v1/iam/ JSON REST API and verifies its tokens via published JWKS. It is the one place the endpoint and app credentials live.

func NewClient

func NewClient(endpoint, clientId, clientSecret, certificate, organizationName, applicationName string) *Client

NewClient builds a Client for an explicit endpoint + app context.

func NewClientWithConf

func NewClientWithConf(config *AuthConfig) *Client

NewClientWithConf builds a Client from an AuthConfig.

func (*Client) AddPermission

func (c *Client) AddPermission(permission *Permission) (bool, error)

func (*Client) DeletePermission

func (c *Client) DeletePermission(permission *Permission) (bool, error)

func (*Client) DeleteResourceWithTag

func (c *Client) DeleteResourceWithTag(resource *Resource, tag string) (bool, error)

DeleteResourceWithTag deletes a resource, defaulting its owner to the client's organization.

func (*Client) DoGetBytes

func (c *Client) DoGetBytes(url string) ([]byte, error)

DoGetBytes GETs url and returns the envelope's data field re-marshaled to JSON bytes, ready to unmarshal into a typed value.

func (*Client) DoGetResponse

func (c *Client) DoGetResponse(url string) (*Response, error)

DoGetResponse GETs url and returns the decoded IAM envelope, erroring on a non-"ok" status.

func (*Client) DoPost

func (c *Client) DoPost(action string, queryMap map[string]string, postBytes []byte, isForm, isFile bool) (*Response, error)

DoPost posts to /v1/iam/<action>. isForm/isFile select multipart file upload, multipart form fields, or a raw text/plain body.

func (*Client) GetApplication

func (c *Client) GetApplication(name string) (*Application, error)

func (*Client) GetCert

func (c *Client) GetCert(name string) (*Cert, error)

GetCert fetches a signing certificate by name from the PLATFORM partition, which is where certs live — the same partition GetApplication reads from.

It used to qualify the id with c.OrganizationName, the caller's own tenant. That is the wrong owner and it was silent: the application read resolves admin/<app>, the application's `cert` field is a bare name, and the cert row is written owner=admin — so a deployment whose IAM_ORG was anything but "admin" asked for <tenant>/<cert>, got "the entity does not exist", and could not establish the key every bearer token is validated against. The store had the cert the whole time; only the question was addressed to the wrong tenant.

Both reads now name the partition through one constant, so an application and its own certificate can no longer be looked up in two different places.

func (*Client) GetId

func (c *Client) GetId(name string) string

GetId returns "<org>/<name>".

func (*Client) GetOAuthToken

func (c *Client) GetOAuthToken(code, state string, opts ...OAuthOption) (*oauth2.Token, error)

GetOAuthToken exchanges an authorization code for a token against the IAM server's OAuth endpoints.

func (*Client) GetOrganization

func (c *Client) GetOrganization(name string) (*Organization, error)

GetOrganization fetches an organization by name.

func (*Client) GetPermission

func (c *Client) GetPermission(name string) (*Permission, error)

GetPermission fetches a permission by name within the client's organization.

func (*Client) GetPermissions

func (c *Client) GetPermissions() ([]*Permission, error)

GetPermissions lists all permissions in the client's organization.

func (*Client) GetProviders

func (c *Client) GetProviders() ([]*Provider, error)

GetProviders lists all providers in the client's organization.

func (*Client) GetResources

func (c *Client) GetResources(owner, user, field, value, sortField, sortOrder string) ([]*Resource, error)

GetResources lists stored resources matching the given filter.

func (*Client) GetUrl

func (c *Client) GetUrl(action string, queryMap map[string]string) string

GetUrl builds a /v1/iam/<action> URL with the given query params. Hanzo IAM serves its JSON API under /v1/iam/ only (the iam-v1 /api/ prefix is retired).

func (*Client) GetUser

func (c *Client) GetUser(name string) (*User, error)

GetUser fetches a single user by name within the client's organization.

func (*Client) GetUsers

func (c *Client) GetUsers() ([]*User, error)

GetUsers lists all users in the client's organization.

func (*Client) ParseJwtToken

func (c *Client) ParseJwtToken(token string) (*Claims, error)

ParseJwtToken verifies token against this client's IAM endpoint JWKS, falling back to a configured certificate PEM only when JWKS is unreachable.

func (*Client) SendEmail

func (c *Client) SendEmail(title, content, sender string, receivers ...string) error

SendEmail sends an email via IAM's configured mail provider (/v1/iam/send-email). This proxies mail through IAM exactly as ai did via the old SDK; it is not identity, but keeping the REST call preserves behavior with no new coupling.

func (*Client) UpdatePermission

func (c *Client) UpdatePermission(permission *Permission) (bool, error)

func (*Client) UpdateUserForColumns

func (c *Client) UpdateUserForColumns(user *User, columns []string) (bool, error)

UpdateUserForColumns updates only the named columns of user.

func (*Client) UploadResource

func (c *Client) UploadResource(user, tag, parent, fullFilePath string, fileBytes []byte) (string, string, error)

UploadResource uploads a file to IAM storage and returns (fileUrl, name).

type FaceId

type FaceId struct {
	Name       string    `json:"name"`
	FaceIdData []float64 `json:"faceIdData"`
	ImageUrl   string    `json:"ImageUrl"`
}

FaceId is enrolled biometric face data.

type HttpClient

type HttpClient interface {
	Do(*http.Request) (*http.Response, error)
}

HttpClient is the minimal http doer a Client uses; *http.Client satisfies it.

type ManagedAccount

type ManagedAccount struct {
	Application string `json:"application"`
	Username    string `json:"username"`
	Password    string `json:"password"`
	SigninUrl   string `json:"signinUrl"`
}

ManagedAccount is a linked downstream account (carries a password).

type MfaAccount

type MfaAccount struct {
	AccountName string `json:"accountName"`
	Issuer      string `json:"issuer"`
	SecretKey   string `json:"secretKey"`
	Origin      string `json:"origin"`
}

MfaAccount is a TOTP account entry (carries a secret key).

type MfaItem

type MfaItem struct {
	Name string `json:"name"`
	Rule string `json:"rule"`
}

MfaItem is one required MFA enrollment item.

type MfaProps

type MfaProps struct {
	Enabled       bool     `json:"enabled"`
	IsPreferred   bool     `json:"isPreferred"`
	MfaType       string   `json:"mfaType"`
	Secret        string   `json:"secret,omitempty"`
	CountryCode   string   `json:"countryCode,omitempty"`
	URL           string   `json:"url,omitempty"`
	RecoveryCodes []string `json:"recoveryCodes,omitempty"`
}

MfaProps is one enrolled multi-factor method. Secret/RecoveryCodes are credentials and are cleared by object/redact.go before a user is returned.

type OAuthOption

type OAuthOption func(*oauthOptions)

OAuthOption configures an OAuth request.

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) OAuthOption

WithHTTPClient sets a custom http client for the OAuth exchange.

type Organization

type Organization struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
}

Organization is the subset of the IAM Organization model ai reads. It is a read-only response (GetOrganization), so a projection is correct: json unmarshal ignores the fields ai does not consume.

func GetOrganization

func GetOrganization(name string) (*Organization, error)

GetOrganization uses the configured (or env-derived) client.

type Permission

type Permission struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	CreatedTime string `json:"createdTime"`
	DisplayName string `json:"displayName"`
	Description string `json:"description"`

	Users   []string `json:"users"`
	Groups  []string `json:"groups"`
	Roles   []string `json:"roles"`
	Domains []string `json:"domains"`

	Model        string   `json:"model"`
	Adapter      string   `json:"adapter"`
	ResourceType string   `json:"resourceType"`
	Resources    []string `json:"resources"`
	Actions      []string `json:"actions"`
	Effect       string   `json:"effect"`
	IsEnabled    bool     `json:"isEnabled"`

	Submitter   string `json:"submitter"`
	Approver    string `json:"approver"`
	ApproveTime string `json:"approveTime"`
	State       string `json:"state"`
}

Permission mirrors the IAM server's Permission JSON model. ai marshals a client-supplied permission into this type and posts it back on add/update/delete, so the full field set is kept for a lossless round-trip (a slim struct would drop unknown fields and corrupt the stored permission).

func GetPermission

func GetPermission(name string) (*Permission, error)

func GetPermissions

func GetPermissions() ([]*Permission, error)

type ProductInfo

type ProductInfo struct {
	Owner       string  `json:"owner"`
	Name        string  `json:"name"`
	DisplayName string  `json:"displayName"`
	Image       string  `json:"image,omitempty"`
	Detail      string  `json:"detail,omitempty"`
	Price       float64 `json:"price"`
	Currency    string  `json:"currency,omitempty"`
	IsRecharge  bool    `json:"isRecharge,omitempty"`
	Quantity    int     `json:"quantity,omitempty"`
	PricingName string  `json:"pricingName,omitempty"`
	PlanName    string  `json:"planName,omitempty"`
}

ProductInfo is one line item on a User's cart.

type Provider

type Provider struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
	Category    string `json:"category"`
	Type        string `json:"type"`
}

Provider is the subset of the IAM Provider model ai reads. ai filters the provider list by Category (e.g. "Storage"). Read-only response (GetProviders), so a projection is correct.

func GetProviders

func GetProviders() ([]*Provider, error)

GetProviders uses the configured (or env-derived) client.

type Resource

type Resource struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	CreatedTime string `json:"createdTime"`
	User        string `json:"user"`
	Provider    string `json:"provider"`
	Application string `json:"application"`
	Tag         string `json:"tag"`
	Parent      string `json:"parent"`
	FileName    string `json:"fileName"`
	FileType    string `json:"fileType"`
	FileFormat  string `json:"fileFormat"`
	FileSize    int    `json:"fileSize"`
	Url         string `json:"url"`
	Description string `json:"description"`
}

Resource mirrors the IAM Resource JSON model (a stored file record). ai constructs one client-side to delete by tag, and reads Name/CreatedTime/ FileSize/Url off the list response, so the full field set is kept.

NOTE: these resource calls proxy file storage through IAM's /v1/iam/ upload/list/delete endpoints — the same behavior ai had via the old SDK. Re-homing file storage to S3 directly is a possible future cleanup, but is out of scope for decoupling from the retired iam-v1 module: keeping the REST calls preserves behavior exactly.

func GetResources

func GetResources(owner, user, field, value, sortField, sortOrder string) ([]*Resource, error)

type Response

type Response struct {
	Status Status      `json:"status"`
	Msg    string      `json:"msg"`
	Data   interface{} `json:"data"`
	Data2  interface{} `json:"data2"`
}

Response is the IAM JSON envelope: {status, msg, data, data2}.

type Role

type Role struct {
	Owner       string   `json:"owner"`
	Name        string   `json:"name"`
	CreatedTime string   `json:"createdTime"`
	DisplayName string   `json:"displayName"`
	Description string   `json:"description"`
	Users       []string `json:"users"`
	Groups      []string `json:"groups"`
	Roles       []string `json:"roles"`
	Domains     []string `json:"domains"`
	IsEnabled   bool     `json:"isEnabled"`
}

Role is an IAM role reference.

type Status added in v1.831.9

type Status string

Status is the IAM envelope's status field.

The server has emitted it BOTH as a string ("ok") and — since iam v1.33.x — as a JSON number (200). A plain `string` field fails the ENTIRE decode on the numeric form, and the one caller that matters treats a decode failure as "IAM unreachable" and continues with auth features switched off. The pod still passes its probes, so nothing crashes and nothing reverts: the binary just serves 401 to every authenticated call. One field's wire drift silently disarms authentication.

Accepting both shapes removes that failure mode at the only place it can occur.

func (*Status) UnmarshalJSON added in v1.831.9

func (s *Status) UnmarshalJSON(b []byte) error

UnmarshalJSON accepts the string form, the numeric form, or null, and normalizes to the string the callers compare against. A 2xx number IS the success code, so it normalizes to "ok"; any other number keeps its digits so a real error still reads as one rather than being laundered into success. Unknown shapes are an error, not a guess — silently accepting them is what caused this.

type User

type User struct {
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	CreatedTime string `json:"createdTime"`
	UpdatedTime string `json:"updatedTime"`
	DeletedTime string `json:"deletedTime"`

	Id                   string     `json:"id"`
	ExternalId           string     `json:"externalId"`
	Type                 string     `json:"type"`
	Password             string     `json:"password"`
	PasswordSalt         string     `json:"passwordSalt"`
	PasswordType         string     `json:"passwordType"`
	DisplayName          string     `json:"displayName"`
	FirstName            string     `json:"firstName"`
	LastName             string     `json:"lastName"`
	Avatar               string     `json:"avatar"`
	AvatarType           string     `json:"avatarType"`
	PermanentAvatar      string     `json:"permanentAvatar"`
	Email                string     `json:"email"`
	EmailVerified        bool       `json:"emailVerified"`
	Phone                string     `json:"phone"`
	CountryCode          string     `json:"countryCode"`
	Region               string     `json:"region"`
	Location             string     `json:"location"`
	Address              []string   `json:"address"`
	Addresses            []*Address `json:"addresses"`
	Affiliation          string     `json:"affiliation"`
	Title                string     `json:"title"`
	IdCardType           string     `json:"idCardType"`
	IdCard               string     `json:"idCard"`
	RealName             string     `json:"realName"`
	IsVerified           bool       `json:"isVerified"`
	Homepage             string     `json:"homepage"`
	Bio                  string     `json:"bio"`
	Tag                  string     `json:"tag"`
	Language             string     `json:"language"`
	Gender               string     `json:"gender"`
	Birthday             string     `json:"birthday"`
	Education            string     `json:"education"`
	Score                int        `json:"score"`
	Karma                int        `json:"karma"`
	Ranking              int        `json:"ranking"`
	Balance              float64    `json:"balance"`
	BalanceCredit        float64    `json:"balanceCredit"`
	Currency             string     `json:"currency"`
	BalanceCurrency      string     `json:"balanceCurrency"`
	IsDefaultAvatar      bool       `json:"isDefaultAvatar"`
	IsOnline             bool       `json:"isOnline"`
	IsAdmin              bool       `json:"isAdmin"`
	IsForbidden          bool       `json:"isForbidden"`
	IsDeleted            bool       `json:"isDeleted"`
	SignupApplication    string     `json:"signupApplication"`
	Hash                 string     `json:"hash"`
	PreHash              string     `json:"preHash"`
	RegisterType         string     `json:"registerType"`
	RegisterSource       string     `json:"registerSource"`
	AccessKey            string     `json:"accessKey"`
	AccessSecret         string     `json:"accessSecret"`
	AccessToken          string     `json:"accessToken"`
	OriginalToken        string     `json:"originalToken"`
	OriginalRefreshToken string     `json:"originalRefreshToken"`

	CreatedIp      string `json:"createdIp"`
	LastSigninTime string `json:"lastSigninTime"`
	LastSigninIp   string `json:"lastSigninIp"`

	GitHub          string `json:"github"`
	Google          string `json:"google"`
	QQ              string `json:"qq"`
	WeChat          string `json:"wechat"`
	Facebook        string `json:"facebook"`
	DingTalk        string `json:"dingtalk"`
	Weibo           string `json:"weibo"`
	Gitee           string `json:"gitee"`
	LinkedIn        string `json:"linkedin"`
	Wecom           string `json:"wecom"`
	Lark            string `json:"lark"`
	Gitlab          string `json:"gitlab"`
	Adfs            string `json:"adfs"`
	Baidu           string `json:"baidu"`
	Alipay          string `json:"alipay"`
	Infoflow        string `json:"infoflow"`
	Apple           string `json:"apple"`
	AzureAD         string `json:"azuread"`
	AzureADB2c      string `json:"azureadb2c"`
	Slack           string `json:"slack"`
	Steam           string `json:"steam"`
	Bilibili        string `json:"bilibili"`
	Okta            string `json:"okta"`
	Douyin          string `json:"douyin"`
	Kwai            string `json:"kwai"`
	Line            string `json:"line"`
	Amazon          string `json:"amazon"`
	Auth0           string `json:"auth0"`
	BattleNet       string `json:"battlenet"`
	Bitbucket       string `json:"bitbucket"`
	Box             string `json:"box"`
	CloudFoundry    string `json:"cloudfoundry"`
	Dailymotion     string `json:"dailymotion"`
	Deezer          string `json:"deezer"`
	DigitalOcean    string `json:"digitalocean"`
	Discord         string `json:"discord"`
	Dropbox         string `json:"dropbox"`
	EveOnline       string `json:"eveonline"`
	Fitbit          string `json:"fitbit"`
	Gitea           string `json:"gitea"`
	Heroku          string `json:"heroku"`
	InfluxCloud     string `json:"influxcloud"`
	Instagram       string `json:"instagram"`
	Intercom        string `json:"intercom"`
	Kakao           string `json:"kakao"`
	Lastfm          string `json:"lastfm"`
	Mailru          string `json:"mailru"`
	Meetup          string `json:"meetup"`
	MicrosoftOnline string `json:"microsoftonline"`
	Naver           string `json:"naver"`
	Nextcloud       string `json:"nextcloud"`
	OneDrive        string `json:"onedrive"`
	Oura            string `json:"oura"`
	Patreon         string `json:"patreon"`
	Paypal          string `json:"paypal"`
	SalesForce      string `json:"salesforce"`
	Shopify         string `json:"shopify"`
	Soundcloud      string `json:"soundcloud"`
	Spotify         string `json:"spotify"`
	Strava          string `json:"strava"`
	Stripe          string `json:"stripe"`
	TikTok          string `json:"tiktok"`
	Tumblr          string `json:"tumblr"`
	Twitch          string `json:"twitch"`
	Twitter         string `json:"twitter"`
	Typetalk        string `json:"typetalk"`
	Uber            string `json:"uber"`
	VK              string `json:"vk"`
	Wepay           string `json:"wepay"`
	Xero            string `json:"xero"`
	Yahoo           string `json:"yahoo"`
	Yammer          string `json:"yammer"`
	Yandex          string `json:"yandex"`
	Zoom            string `json:"zoom"`
	Custom          string `json:"custom"`
	Custom2         string `json:"custom2"`
	Custom3         string `json:"custom3"`
	Custom4         string `json:"custom4"`
	Custom5         string `json:"custom5"`
	Custom6         string `json:"custom6"`
	Custom7         string `json:"custom7"`
	Custom8         string `json:"custom8"`
	Custom9         string `json:"custom9"`
	Custom10        string `json:"custom10"`

	PreferredMfaType  string        `json:"preferredMfaType"`
	RecoveryCodes     []string      `json:"recoveryCodes"`
	TotpSecret        string        `json:"totpSecret"`
	MfaPhoneEnabled   bool          `json:"mfaPhoneEnabled"`
	MfaEmailEnabled   bool          `json:"mfaEmailEnabled"`
	MfaRadiusEnabled  bool          `json:"mfaRadiusEnabled"`
	MfaRadiusUsername string        `json:"mfaRadiusUsername"`
	MfaRadiusProvider string        `json:"mfaRadiusProvider"`
	MfaPushEnabled    bool          `json:"mfaPushEnabled"`
	MfaPushReceiver   string        `json:"mfaPushReceiver"`
	MfaPushProvider   string        `json:"mfaPushProvider"`
	MultiFactorAuths  []*MfaProps   `json:"multiFactorAuths,omitempty"`
	Invitation        string        `json:"invitation"`
	InvitationCode    string        `json:"invitationCode"`
	FaceIds           []*FaceId     `json:"faceIds"`
	Cart              []ProductInfo `json:"cart"`

	Ldap       string            `json:"ldap"`
	Properties map[string]string `json:"properties"`

	Roles       []*Role       `json:"roles"`
	Permissions []*Permission `json:"permissions"`
	Groups      []string      `json:"groups"`

	LastChangePasswordTime string `json:"lastChangePasswordTime"`
	LastSigninWrongTime    string `json:"lastSigninWrongTime"`
	SigninWrongTimes       int    `json:"signinWrongTimes"`

	ManagedAccounts     []ManagedAccount `json:"managedAccounts"`
	MfaAccounts         []MfaAccount     `json:"mfaAccounts"`
	MfaItems            []*MfaItem       `json:"mfaItems"`
	MfaRememberDeadline string           `json:"mfaRememberDeadline"`
	NeedUpdatePassword  bool             `json:"needUpdatePassword"`
	IpWhitelist         string           `json:"ipWhitelist"`

	// BillingAccount is the signed `billing_account` claim: WHO PAYS for this
	// credential, stated by IAM at mint time. Wire is "<kind>:<subject>" (e.g.
	// "org:acme"); empty when IAM could not attribute one, in which case a reader
	// must fall back to Payer's shape rule rather than bill a guess.
	//
	// It lives HERE, on the identity, and not on the Claims envelope that carries
	// it, because every place that spends money holds a *User. When this field sat
	// on Claims it was structurally unreachable from 27 of the 28 account.Payer
	// call sites, so they all silently billed the shape-rule fallback: an org admin
	// paid from their own empty wallet while the company pool sat funded and
	// unused, and no layer could report an error because each one was doing exactly
	// what it was told. Prefer u.Payer(ledger) over reading this directly.
	BillingAccount string `json:"billing_account,omitempty"`
}

User mirrors the Hanzo IAM server's User JSON model. The FULL field set is reproduced deliberately (not a slim projection): object/redact.go reflects over every field by its json tag as a fail-secure secret-redaction control, and it must see every credential field (password*, *Secret, *Token, totp, social-login ids, custom1..10, …) to zero it before a user is returned to a client. A slim User would silently narrow that control and break its test. The json tags are therefore load-bearing and match the server verbatim; the server's xorm/db tags are dropped (ai has no ORM).

func GetUser

func GetUser(name string) (*User, error)

func GetUsers

func GetUsers() ([]*User, error)

func (*User) GetId

func (u *User) GetId() string

GetId returns "<owner>/<name>", the IAM user identifier.

func (*User) Payer added in v1.831.13

func (u *User) Payer(ledger string) account.Account

Payer is THE money address a credential spends from — the one place that turns an authenticated identity into the (org, subject) pair a balance read, a spend gate and a usage debit all have to agree on.

WHY THIS EXISTS. account.Payer already owns the RULE (org pool vs personal wallet). What kept going wrong was not the rule but its INPUT: every caller built its own account.Credential by hand, and a caller that forgets one field gets a confidently wrong answer instead of a compile error. Twenty-eight sites built that struct; exactly one passed Account, so the signed billing_account claim was discarded almost everywhere. The result was the same bug this codebase has now hit five times — two layers deriving one address two ways — except distributed across the whole surface: a spend gate read the personal wallet while the funded org pool went untouched, and nothing errored, because each layer was faithfully answering the question it was asked.

So the fix is not "remember to pass the claim" — it is to stop asking callers to assemble the credential at all. Hand this method a ledger and it answers with the identity's own money address, claim included, every time.

ledger names the org whose ledger pays (the X-Org-Id namespace). Empty means "this user's home org", which is the behavior that predates org switching. A nil receiver yields the zero Account — unattributable, which every caller must treat as "refuse", never as "free".

func (*User) PayerSubject added in v1.831.13

func (u *User) PayerSubject(ledger string) string

PayerSubject is Payer(...).Subject() — the string form the balance, gate and usage paths pass around as ?user=. It exists so those call sites read as one step rather than two, and so a subject can never be built from a Credential assembled somewhere other than Payer above.

Jump to

Keyboard shortcuts

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