Documentation
¶
Overview ¶
Package apikey gives users long-lived keys for scripts and integrations: "Authorization: Bearer ubk_…" in place of a session.
A key acts for its user in the app's own routes, with the scopes it was created with; the app checks them with auth.HasScope. The auth operations themselves refuse a key (auth.ErrAPIKeyRefused): a script cannot change the password or read the session list. A key is shown once, at creation; the database keeps its SHA-256, which is safe for a 256-bit random value.
Index ¶
- Constants
- Variables
- type Cache
- type CreateRequest
- type CreateResult
- type Expiry
- type GetRequest
- type Key
- type Limits
- type ListRequest
- type ListResult
- type MemoryCache
- type Option
- func AllowKeyPrefix(max int) Option
- func AllowMetadata() Option
- func Configuration(id string, opts ...Option) Option
- func DefaultLimits(l Limits) Option
- func DefaultPermissions(perms Permissions) Option
- func DefaultPermissionsFunc(fn func(ctx context.Context, ownerID string) Permissions) Option
- func DefaultScopes(scopes ...string) Option
- func DefaultScopesFunc(fn func(ctx context.Context, userID string) []string) Option
- func DeferUpdates() Option
- func DeleteSpentKeys() Option
- func DistinctErrors() Option
- func Generate(fn func() string) Option
- func HintLength(n int) Option
- func MaxKeys(n int) Option
- func NameBounds(min, max int) Option
- func NameLength(runes int) Option
- func OrganizationOwned() Option
- func PreCheck(fn func(ctx context.Context, key string) bool) Option
- func Prefix(prefix string) Option
- func RejectUnknownKeys() Option
- func RequireName() Option
- func Scopes(scopes ...string) Option
- func StampEveryUse() Option
- func WithCache(c Cache, ttl time.Duration) Option
- func WithExpiry(e Expiry) Option
- func WithOrganizations(o Organizations) Option
- func WithPermissions(c PermissionChecker) Option
- type Organizations
- type OrganizationsFunc
- type PermissionChecker
- type Permissions
- type Plugin
- func (p *Plugin) Authorize(ctx context.Context, want Permissions) error
- func (p *Plugin) Create(ctx context.Context, in CreateRequest) (CreateResult, error)
- func (p *Plugin) CreateFor(ctx context.Context, userID string, in CreateRequest, l Limits) (CreateResult, error)
- func (p *Plugin) DeleteOrganizationKeys(ctx context.Context, orgID string) (int64, error)
- func (p *Plugin) Get(ctx context.Context, id string) (Key, error)
- func (p *Plugin) Init(a *auth.Auth) error
- func (p *Plugin) List(ctx context.Context) ([]Key, error)
- func (p *Plugin) ListKeys(ctx context.Context, in ListRequest) (ListResult, error)
- func (p *Plugin) Name() string
- func (p *Plugin) Operations() []auth.Operation
- func (p *Plugin) OptionErr() error
- func (p *Plugin) OwnerRefused() error
- func (p *Plugin) ResolveToken(ctx context.Context, t auth.Token) (auth.Principal, error)
- func (p *Plugin) Revoke(ctx context.Context, id string) error
- func (p *Plugin) SetLimits(ctx context.Context, id string, l Limits) error
- func (p *Plugin) SetPermissions(ctx context.Context, id string, perms Permissions) error
- func (p *Plugin) Sweep(ctx context.Context, cutoff time.Time) (int64, error)
- func (p *Plugin) Tables() []sqlb.Table
- func (p *Plugin) TokenPrefix() string
- func (p *Plugin) TokenPrefixes() []string
- func (p *Plugin) TypedMigrations(engine string, t auth.SQLTypes) ([]auth.Migration, error)
- func (p *Plugin) Update(ctx context.Context, in UpdateRequest) (Key, error)
- func (p *Plugin) UserTables() []auth.UserTable
- func (p *Plugin) Validate(*auth.Config) error
- func (p *Plugin) Verify(ctx context.Context, secret string, scopes ...string) (Verification, error)
- func (p *Plugin) VerifyFor(ctx context.Context, config, secret string, scopes ...string) (Verification, error)
- func (p *Plugin) VerifyPermissions(ctx context.Context, secret string, want Permissions) (Verification, error)
- type RevokeRequest
- type UpdateRequest
- type Verification
- type Vocabulary
Constants ¶
const ( EndpointCreate auth.EndpointID = "apikey.create" EndpointList auth.EndpointID = "apikey.list" EndpointRevoke auth.EndpointID = "apikey.revoke" EndpointUpdate auth.EndpointID = "apikey.update" EndpointGet auth.EndpointID = "apikey.get" )
Operation ids.
const ( //ds:def id=tablenamekey-4q22c5ze owner=@khanakia stability=stable TableNameKey = "auth_api_key" TableAliasKey = "ak" //ds:def id=colid-wve5u4tk owner=@khanakia stability=stable ColID = "id" //ds:def id=coluserid-d85t7bwn owner=@khanakia stability=stable ColUserID = "user_id" //ds:def id=colname-buussu3v owner=@khanakia stability=stable ColName = "name" // ColHint is the first characters of the key, shown in lists so a user // can tell keys apart; never enough to use. //ds:def id=colhint-685gvhrx owner=@khanakia stability=stable ColHint = "hint" //ds:def id=colkeyhash-n9ne59xk owner=@khanakia stability=stable ColKeyHash = "key_hash" //ds:def id=colscopes-nuk5dts4 owner=@khanakia stability=stable ColScopes = "scopes" //ds:def id=colcreatedat-kav6jpyq owner=@khanakia stability=stable ColCreatedAt = "created_at" //ds:def id=collastusedat-5b23p9ng owner=@khanakia stability=stable ColLastUsedAt = "last_used_at" //ds:def id=colexpiresat-hf6aw2fc owner=@khanakia stability=stable ColExpiresAt = "expires_at" )
The plugin's table.
const ( SortByCreatedAt = "createdAt" SortByName = "name" SortByLastUsedAt = "lastUsedAt" SortByExpiresAt = "expiresAt" SortAsc = "asc" SortDesc = "desc" )
ListRequest sort fields and directions. They are the JSON names of the Key fields they sort by, so a client sorts by the name it displays.
const ( // DefaultCacheTTL is used when WithCache gets 0: short, because a key // revoked on another instance (or while a request was filling the cache) // keeps working until its entry expires. //ds:def id=defaultcachettl-w8kc3nzq owner=@khanakia stability=stable DefaultCacheTTL = time.Minute // MaxCacheTTL is the longest WithCache accepts: past it, "revoke" would // stop meaning "now" in any useful sense. //ds:def id=maxcachettl-j4rx7mbs owner=@khanakia stability=stable MaxCacheTTL = 15 * time.Minute )
Cache TTL bounds.
const ( // ColConfigID is the key's configuration id; rows made before 0003 get // DefaultConfig from the column default. //ds:def id=colconfigid-f6np2qwd owner=@khanakia stability=stable ColConfigID = "config_id" // ColPrefix is the key's full prefix in clear; NULL before 0003. //ds:def id=colprefix-z3hk8vmt owner=@khanakia stability=stable ColPrefix = "prefix" // ColUpdatedAt is when the key last changed; NULL before 0003. //ds:def id=colupdatedat-u7cy4rbn owner=@khanakia stability=stable ColUpdatedAt = "updated_at" )
Columns added by migration 0003 (round 6, 2026-10-06): the configuration a key belongs to, its prefix in clear, and when it last changed.
const ( // ColDisabledAt is when the owner disabled the key; NULL for an enabled // key. A disabled key is refused like an unknown one and re-enabled by // update, which revoke-and-recreate could not offer. //ds:def id=coldisabledat-y7kp3qvn owner=@khanakia stability=stable ColDisabledAt = "disabled_at" // ColMetadata is the owner's JSON object, NULL when none was given. //ds:def id=colmetadata-d4xw8mrt owner=@khanakia stability=stable ColMetadata = "metadata" // ColRateMax requests per ColRateWindow; 0 means no per-key rate limit. //ds:def id=colratemax-b2nq6ksj owner=@khanakia stability=stable ColRateMax = "rate_max" //ds:def id=colratewindow-g9tc5lvz owner=@khanakia stability=stable ColRateWindow = "rate_window_seconds" // ColRateCount and ColRateWindowStart are the fixed window's counter and // start; the start is NULL until the first request. //ds:def id=colratecount-m3fh7wpd owner=@khanakia stability=stable ColRateCount = "rate_count" //ds:def id=colratewindowstart-r6jd2bxn owner=@khanakia stability=stable ColRateWindowStart = "rate_window_start" // ColRemaining is the uses left; NULL means unlimited. //ds:def id=colremaining-k8sv4czq owner=@khanakia stability=stable ColRemaining = "remaining" // ColRefillAmount resets ColRemaining every ColRefillInterval seconds; // 0 means no refill. //ds:def id=colrefillamount-p5wm9tyh owner=@khanakia stability=stable ColRefillAmount = "refill_amount" //ds:def id=colrefillinterval-x2bk7nqs owner=@khanakia stability=stable ColRefillInterval = "refill_interval_seconds" // ColLastRefillAt is when the quota was last refilled; NULL means never, // and the refill clock then starts at created_at. //ds:def id=collastrefillat-v4gz8mcj owner=@khanakia stability=stable ColLastRefillAt = "last_refill_at" )
Columns added by migration 0002 (round 2, 2026-10-02): the disable flag, metadata, the per-key rate limit and the usage quota.
const ( ActionCreate = "create" ActionRead = "read" ActionUpdate = "update" ActionDelete = "delete" )
The actions on ResourceAPIKey, better-auth's API_KEY_PERMISSIONS: create checks a new organization key, read its get and list, update its update, delete its revoke.
const ( // ColOrganizationID is the organization that owns the key. A row has // one of two shapes: a user's key (user_id the owner, organization_id // NULL), or an organization's key (organization_id the owner, user_id // ""). The empty user_id is deliberate: every user-side query filters // user_id by the caller, so an organization key is out of reach of them // all, and DeleteUser (which deletes by user_id) leaves it alone. //ds:def id=colorganizationid-q3vx8nkt owner=@khanakia stability=stable ColOrganizationID = "organization_id" // ColPermissions is the key's permissions as a JSON object of resource // to actions, or NULL for none. //ds:def id=colpermissions-h7cw4mzp owner=@khanakia stability=stable ColPermissions = "permissions" )
Columns added by migration 0004 (round 36, 2026-10-07).
const DefaultConfig = "default"
DefaultConfig is the id of the configuration the plugin's own options describe. Keys made before configurations existed carry it (the column's default), and a request that names no configuration means it.
const DefaultPrefix = "ubk_"
DefaultPrefix starts every key, so a leaked key is recognizable by secret scanners and routed to this plugin instead of the session table.
const MaxKeysPerUser = 50
MaxKeysPerUser is the default cap on the keys one account holds, so a leaked session cannot mint keys without end; Create past the cap is ErrTooMany. Fifty is far above one per integration. MaxKeys changes it.
const PluginName = "apikey"
PluginName is the plugin's permanent identity.
const ResourceAPIKey = "apiKey"
ResourceAPIKey is the resource an organization's roles grant API-key actions on, better-auth's "apiKey": an app adds Statements to the access control its organization roles are built from and grants the actions per role (an admin all four, a member "read", in better-auth's example).
Variables ¶
var ( TableKey = sqlb.Table{Name: TableNameKey, Alias: TableAliasKey} KeyID = sqlb.Col[string](TableKey, ColID) KeyUserID = sqlb.Col[string](TableKey, ColUserID) KeyName = sqlb.Col[string](TableKey, ColName) KeyHint = sqlb.Col[string](TableKey, ColHint) KeyHash = sqlb.Col[[]byte](TableKey, ColKeyHash) KeyScopes = sqlb.Col[string](TableKey, ColScopes) KeyCreated = sqlb.Col[time.Time](TableKey, ColCreatedAt) KeyUsed = sqlb.Col[*time.Time](TableKey, ColLastUsedAt) KeyExpires = sqlb.Col[*time.Time](TableKey, ColExpiresAt) )
Typed columns.
var ( ErrUnknownScope = auth.NewError("apikey.unknown_scope", http.StatusBadRequest, "A scope in this request does not exist.") ErrTooMany = auth.NewError("apikey.too_many", http.StatusConflict, "This account has too many keys. Revoke one first.") ErrNotFound = auth.NewError("apikey.not_found", http.StatusNotFound, "Not found.") // ErrNoScopes is a configuration that declares neither scopes // (Scopes) nor permissions (WithPermissions): a key could then carry // nothing the app checks, so auth.New refuses it. ErrNoScopes = errors.New("apikey: declare what a key may carry: scopes with apikey.Scopes, permissions with apikey.WithPermissions, or both") // ErrBadScope is a configuration error: a declared scope that is empty or // contains whitespace, which the scopes column could not store apart. ErrBadScope = errors.New("apikey: a scope is non-empty and contains no whitespace") // ErrBadPrefix is a configuration error: a prefix too short for a secret // scanner to recognize, or one that does not end with "_". ErrBadPrefix = errors.New("apikey: a prefix is at least three characters and ends with _") // ErrBadLimits is a configuration error: MaxKeys, NameLength or // WithExpiry given values that would make every create fail. ErrBadLimits = errors.New("apikey: limits are positive and the expiry default lies within its bounds") // ErrNameRequired is a create without a name when RequireName is set. ErrNameRequired = auth.NewError("apikey.name_required", http.StatusBadRequest, "Give the key a name.") // ErrExpiryOutOfRange is an expiry outside the WithExpiry bounds, // including "never" when a maximum is set. ErrExpiryOutOfRange = auth.NewError("apikey.expiry_out_of_range", http.StatusBadRequest, "The expiry is outside what this app allows.") // ErrExpiryConflict is a request that sets both expiresIn (seconds) // and expiresInDays: two answers to one question, so neither is // guessed at. ErrExpiryConflict = auth.NewError("apikey.expiry_conflict", http.StatusBadRequest, "Give the expiry in seconds or in days, not both.") // ErrBadList is a ListRequest with a limit or offset that is not a // non-negative integer, an unknown sort field or direction. ErrBadList = auth.NewError("apikey.bad_list", http.StatusBadRequest, "The list options are not valid.") // ErrBadName is a name outside the NameBounds, at create or rename. ErrBadName = auth.NewError("apikey.bad_name", http.StatusBadRequest, "The name is too short or too long.") // ErrScopeMissing is a Verify whose key lacks a scope the caller // required; Go only, never answered to a client by this plugin. ErrScopeMissing = auth.NewError("apikey.missing_scope", http.StatusForbidden, "This key does not allow this.") // ErrShortSecret is a Generate function that returned fewer than // minSecretLength characters: a programming error, so create fails // rather than mint a guessable key. ErrShortSecret = errors.New("apikey: Generate returned a secret shorter than 22 characters") )
Errors.
var ( // ErrBadConfig is a configuration error: a Configuration id that is // empty, longer than 32 characters, made of other than letters, digits, // "_" and "-", repeated, or DefaultConfig; or a plugin-wide option inside // a Configuration. ErrBadConfig = errors.New("apikey: a configuration id is unique, at most 32 letters, digits, _ or -, not \"default\", and a configuration holds no plugin-wide option") // ErrUnknownConfig is a request naming a configuration that does not // exist. ErrUnknownConfig = auth.NewError("apikey.unknown_config", http.StatusBadRequest, "This key configuration does not exist.") // ErrBadKeyPrefix is a create with a prefix while AllowKeyPrefix is off, // or one longer than its bound or with other characters than letters, // digits, "_" and "-". ErrBadKeyPrefix = auth.NewError("apikey.bad_prefix", http.StatusBadRequest, "The key prefix is not allowed.") )
Errors of configurations and per-key prefixes.
var ( KeyConfig = sqlb.Col[string](TableKey, ColConfigID) KeyPrefix = sqlb.Col[*string](TableKey, ColPrefix) KeyUpdated = sqlb.Col[*time.Time](TableKey, ColUpdatedAt) )
Typed columns of migration 0003.
var ( KeyDisabledAt = sqlb.Col[*time.Time](TableKey, ColDisabledAt) KeyMetadata = sqlb.Col[*string](TableKey, ColMetadata) KeyRateMax = sqlb.Col[int64](TableKey, ColRateMax) KeyRateWindow = sqlb.Col[int64](TableKey, ColRateWindow) KeyRateCount = sqlb.Col[int64](TableKey, ColRateCount) KeyRateWindowStart = sqlb.Col[*time.Time](TableKey, ColRateWindowStart) KeyRemaining = sqlb.Col[*int64](TableKey, ColRemaining) KeyRefillAmount = sqlb.Col[int64](TableKey, ColRefillAmount) KeyRefillInterval = sqlb.Col[int64](TableKey, ColRefillInterval) KeyLastRefillAt = sqlb.Col[*time.Time](TableKey, ColLastRefillAt) )
Typed columns of migration 0002.
var ( // ErrBadLimitsValue is a Limits that breaks the pairing rules or holds // a negative value: a configuration error from DefaultLimits (through // ErrBadLimits) or a caller error from CreateFor and SetLimits. ErrBadLimitsValue = errors.New("apikey: Limits pair RateMax with RateWindow and RefillAmount with RefillInterval, each at least a second, none negative") // ErrUsageExceeded is a request with a key whose Remaining reached zero // and that has no refill due. ErrUsageExceeded = auth.NewError("apikey.usage_exceeded", http.StatusTooManyRequests, "This key has used up its quota.") // ErrMetadataDisabled is a create or update carrying metadata while // AllowMetadata is not set. ErrMetadataDisabled = auth.NewError("apikey.metadata_disabled", http.StatusBadRequest, "This app does not keep metadata on keys.") // ErrBadMetadata is metadata that is not a JSON object or is longer than // maxMetadataLength bytes. ErrBadMetadata = auth.NewError("apikey.bad_metadata", http.StatusBadRequest, "Metadata must be a JSON object of at most 4096 bytes.") // ErrNoChanges is an update that sets nothing. ErrNoChanges = auth.NewError("apikey.no_changes", http.StatusBadRequest, "Nothing to update.") )
Errors of limits, quotas, metadata and update.
var ( // ErrOrganizationsRequired is a configuration error: an // OrganizationOwned configuration without WithOrganizations, // better-auth's ORGANIZATION_PLUGIN_REQUIRED, caught at auth.New rather // than on the first request. ErrOrganizationsRequired = errors.New("apikey: an OrganizationOwned configuration needs apikey.WithOrganizations") // ErrOrganizationIDRequired is a create under an OrganizationOwned // configuration that names no organization. ErrOrganizationIDRequired = auth.NewError("apikey.organization_id_required", http.StatusBadRequest, "Organization ID is required for organization-owned API keys.") // ErrNotOrganizationConfig is a create naming an organization under a // configuration whose keys belong to users. ErrNotOrganizationConfig = auth.NewError("apikey.not_organization_config", http.StatusBadRequest, "This key configuration does not make organization keys.") // ErrNotOrganizationMember is a create or list naming an organization // the caller is not a member of. Get, update and revoke of such an // organization's key answer ErrNotFound instead, so a key id probe // learns nothing. ErrNotOrganizationMember = auth.NewError("apikey.not_organization_member", http.StatusForbidden, "You are not a member of the organization that owns this API key.") // ErrOrganizationPermission is a member whose role does not grant the // apiKey action asked for. ErrOrganizationPermission = auth.NewError("apikey.organization_permission", http.StatusForbidden, "You do not have permission to perform this action on organization API keys.") // ErrOrganizationKey is an organization-owned key presented as a bearer // credential, under DistinctErrors (auth.ErrSignedOut otherwise): // better-auth's INVALID_REFERENCE_ID_FROM_API_KEY for such a key. ErrOrganizationKey = auth.NewError("apikey.organization_key", http.StatusUnauthorized, "An organization API key does not sign in as a user.") // ErrPermissionsOff is a key given permissions while WithPermissions is // off for its configuration. ErrPermissionsOff = auth.NewError("apikey.permissions_off", http.StatusBadRequest, "This app does not keep permissions on keys.") // ErrBadPermissions is permissions naming a resource or action the // WithPermissions vocabulary does not declare; at auth.New, the same // for DefaultPermissions. ErrBadPermissions = auth.NewError("apikey.bad_permissions", http.StatusBadRequest, "A permission in this request does not exist.") // ErrPermissionDenied is a key whose permissions do not grant what // VerifyPermissions or Authorize asked for, or a request made without a // key; Go only, like ErrScopeMissing. ErrPermissionDenied = auth.NewError("apikey.permission_denied", http.StatusForbidden, "This key does not allow this.") )
Errors of organization-owned keys and key permissions.
var ( KeyOrganization = sqlb.Col[*string](TableKey, ColOrganizationID) KeyPermissions = sqlb.Col[*string](TableKey, ColPermissions) )
Typed columns of migration 0004.
var ( // ErrInvalidKey is a key that does not exist, failed PreCheck, or belongs // to another configuration than VerifyFor named. Verify reports it; over // HTTP an unknown key leaves the request signed out (401 auth.signed_out // on a route that needs a user), because a session token may start with // a key prefix by chance and is then looked up as a session, unless // RejectUnknownKeys makes it this error on every route. ErrInvalidKey = auth.NewError("apikey.invalid_key", http.StatusUnauthorized, "Invalid API key.") // ErrKeyDisabled is a key its owner paused (UpdateRequest.Disabled). ErrKeyDisabled = auth.NewError("apikey.key_disabled", http.StatusUnauthorized, "This API key is disabled.") // ErrKeyExpired is a key past its expiry. ErrKeyExpired = auth.NewError("apikey.key_expired", http.StatusUnauthorized, "This API key has expired.") // ErrKeyOwnerGone is a key whose user no longer exists, better-auth's // INVALID_REFERENCE_ID_FROM_API_KEY. ErrKeyOwnerGone = auth.NewError("apikey.invalid_owner", http.StatusUnauthorized, "The account behind this API key no longer exists.") // ErrOwnerBlocked is a key whose owner exists but may not sign in (an // admin ban, or an app's own SessionChecker), better-auth's USER_BANNED. ErrOwnerBlocked = auth.NewError("apikey.owner_blocked", http.StatusUnauthorized, "The account behind this API key cannot sign in.") )
Errors that say why a key was refused, used only under DistinctErrors. By default every one of these cases is auth.ErrSignedOut, so whoever holds a key cannot learn whether it was revoked, paused or expired.
var ErrBadCacheTTL = errors.New("apikey: the cache TTL is between 0 (the default) and 15 minutes")
ErrBadCacheTTL is a configuration error: a WithCache TTL that is negative or above MaxCacheTTL.
var ErrPrefixNotDistinct = errors.New("apikey: RejectUnknownKeys needs every key prefix to hold a character outside base64url (A-Z a-z 0-9 - _), such as \".\"")
ErrPrefixNotDistinct is a configuration error: RejectUnknownKeys with a key prefix made only of base64url characters, which a core token could start with.
var SortByValues = []string{SortByCreatedAt, SortByName, SortByLastUsedAt, SortByExpiresAt}
SortByValues is every sort field, for validation and documentation.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache interface {
Get(ctx context.Context, key string) (value []byte, ok bool, err error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Delete(ctx context.Context, key string) error
}
Cache keeps looked-up keys for a short time so a busy key is not read from the database on every request: better-auth's secondary storage with fallbackToDatabase, where the database stays the source of truth. It is a plain byte store so any shared store (Redis, memcached) fits in a few lines; NewMemoryCache is the in-process one.
Values are keyed by the SHA-256 of the key, never the key, and hold the key's row without the secret. Get reports a miss as ok false; an error is a failure of the store itself.
type CreateRequest ¶
type CreateRequest struct {
// Config names the key configuration (Configuration); "" is
// DefaultConfig. An unknown one is ErrUnknownConfig.
Config string `json:"configId,omitempty"`
Name string `json:"name,omitempty"`
// Scopes are the key's scopes; nil (the field left out) takes the
// configuration's DefaultScopes, an empty list none. omitzero, not
// omitempty, so a Go client that encodes an empty list still sends it.
Scopes []string `json:"scopes,omitzero"`
// Prefix is the key's own prefix after the configuration's, allowed
// only under AllowKeyPrefix (ErrBadKeyPrefix otherwise).
Prefix string `json:"prefix,omitempty"`
// ExpiresInDays is optional; 0 means the key never expires, unless
// WithExpiry sets a default or a maximum.
ExpiresInDays int `json:"expiresInDays,omitempty"`
// ExpiresIn is the expiry in seconds from now, better-auth's unit, as
// an alternative to ExpiresInDays (setting both is ErrExpiryConflict).
// The WithExpiry bounds apply to it measured in days, as better-auth
// compares expiresIn with its minExpiresIn and maxExpiresIn days; 0
// leaves the choice to ExpiresInDays.
ExpiresIn int64 `json:"expiresIn,omitempty"`
// Metadata is a JSON object kept with the key and returned in
// Key.Metadata; it needs AllowMetadata (ErrMetadataDisabled otherwise).
Metadata json.RawMessage `json:"metadata,omitempty"`
// OrganizationID names the organization that owns the key; required by
// an OrganizationOwned configuration (ErrOrganizationIDRequired) and
// refused by any other (ErrNotOrganizationConfig). The caller needs the
// apiKey "create" permission in it.
OrganizationID string `json:"organizationId,omitempty"`
// Permissions are the key's resource-to-actions permissions, checked
// against WithPermissions (ErrBadPermissions); nil takes the
// configuration's DefaultPermissions. Go only, never read from a client,
// as better-auth makes permissions a server-only property: a user must
// not grant their own key powers the app did not decide.
Permissions Permissions `json:"-"`
}
CreateRequest creates a key. Every scope must be one declared with Scopes (ErrUnknownScope otherwise); repeats are stored once. Name is cut to NameLength runes (64 by default), or required by RequireName.
type CreateResult ¶
CreateResult shows the key once. Secret is a plain string on purpose: this response is the one place the key is meant to be seen.
type Expiry ¶
Expiry bounds CreateRequest.ExpiresInDays, in days. Zero fields are unset. With MaxDays set, a key that never expires is refused, so a policy such as "keys live at most a year" cannot be bypassed by leaving the field empty; DefaultDays fills an empty request instead.
type GetRequest ¶
type GetRequest struct {
ID string `json:"id"`
// Config, when set, must be the key's configuration (ErrNotFound
// otherwise), as better-auth's configId query does.
Config string `json:"configId,omitempty"`
}
GetRequest names a key of the signed-in user. Another user's key id is ErrNotFound, the same as an unknown one. It arrives in a URL query.
type Key ¶
type Key struct {
ID string `json:"id"`
// Config is the configuration the key was made under.
Config string `json:"configId"`
Name string `json:"name,omitempty"`
// Prefix is the key's prefix in clear: the configuration's and, under
// AllowKeyPrefix, its own. Empty for a key made before migration 0003.
Prefix string `json:"prefix"`
Hint string `json:"hint"`
Scopes []string `json:"scopes"`
CreatedAt time.Time `json:"createdAt"`
LastUsedAt *time.Time `json:"lastUsedAt"`
ExpiresAt *time.Time `json:"expiresAt"`
// Disabled is true while the owner has paused the key (UpdateRequest
// .Disabled); a disabled key is refused like an unknown one.
Disabled bool `json:"disabled"`
// Metadata is the owner's JSON object, or null.
Metadata json.RawMessage `json:"metadata"`
// OrganizationID is the owning organization of an organization-owned
// key, "" for a user's key.
OrganizationID string `json:"organizationId"`
// Permissions are the key's permissions (WithPermissions), or null.
Permissions Permissions `json:"permissions"`
// Limits are the key's rate limit and quota; Remaining is live.
Limits Limits `json:"limits"`
// UpdatedAt is when the key last changed: made, edited, or used (a use
// stamps it with last_used_at, at most once a minute, or with the
// counters of a key that has limits). Nil for a key unchanged since
// before migration 0003.
UpdatedAt *time.Time `json:"updatedAt"`
}
Key is one key as its owner sees it, without the secret.
type Limits ¶
type Limits struct {
RateMax int
RateWindow time.Duration
// Remaining uses; nil means unlimited. In a Key it is the live count.
Remaining *int
RefillAmount int
RefillInterval time.Duration
}
Limits are a key's per-key rate limit and usage quota. The zero value is no limit, which every key created before round 2 has.
The rate limit is a fixed window: at most RateMax requests in any window of RateWindow, counted from the first request after a window ends. The quota is a count of uses that only goes down, unless RefillAmount and RefillInterval are set, in which case it is reset (not topped up) to RefillAmount once RefillInterval has passed since the last refill, or since creation. RateMax pairs with RateWindow and RefillAmount with RefillInterval: one without the other is ErrBadLimitsValue, because a cap without a window could never reopen. Windows and intervals are whole seconds; a fraction is truncated.
func (Limits) MarshalJSON ¶
MarshalJSON writes the wire form.
func (Limits) None ¶
None reports whether the limits are the zero value: no rate limit and no quota, so ResolveToken skips every counter write.
func (*Limits) UnmarshalJSON ¶
UnmarshalJSON reads the wire form. The parameter is the JSON boundary the standard library mandates.
type ListRequest ¶
type ListRequest struct {
// Config keeps the keys of one configuration; "" lists them all.
Config string `json:"configId,omitempty"`
// OrganizationID lists that organization's keys instead of the
// caller's own; the caller needs the apiKey "read" permission in it.
OrganizationID string `json:"organizationId,omitempty"`
// Limit is the page size; "" or "0" is no limit.
Limit string `json:"limit,omitempty"`
// Offset is how many keys to skip; "" is none.
Offset string `json:"offset,omitempty"`
// SortBy is one of SortByValues; "" is SortByCreatedAt.
SortBy string `json:"sortBy,omitempty"`
// Sort is SortAsc or SortDesc; "" is SortAsc. A sort on a nullable field
// (lastUsedAt, expiresAt) puts the NULL rows where the engine puts them.
Sort string `json:"sort,omitempty"`
}
ListRequest pages and sorts the list. Every field is a string because it arrives in a URL query; the zero value is the whole list, oldest first (keys made in the same instant in id order, so every engine agrees).
type ListResult ¶
type ListResult struct {
Keys []Key `json:"keys"`
// Total is how many keys the caller holds, whatever the page.
Total int `json:"total"`
// Limit and Offset echo the request as parsed, 0 for none.
Limit int `json:"limit"`
Offset int `json:"offset"`
}
ListResult is the output of list: one page of the caller's keys without secrets, and the count of all of them.
type MemoryCache ¶
type MemoryCache struct {
// contains filtered or unexported fields
}
MemoryCache is an in-process Cache: a map with expiries, safe for concurrent use. Each instance of the app has its own, so a revoke on one instance reaches another only when that instance's entry expires; use a shared store when that matters. Expired entries are removed when read and by Prune.
func NewMemoryCache ¶
func NewMemoryCache() *MemoryCache
NewMemoryCache returns an empty MemoryCache on the wall clock.
func NewMemoryCacheAt ¶
func NewMemoryCacheAt(now func() time.Time) *MemoryCache
NewMemoryCacheAt is NewMemoryCache on the given clock, for tests that move time.
func (*MemoryCache) Delete ¶
func (m *MemoryCache) Delete(_ context.Context, key string) error
Delete forgets key.
func (*MemoryCache) Prune ¶
func (m *MemoryCache) Prune() int
Prune removes every expired entry and reports how many, for an app that wants to bound the map between reads.
type Option ¶
type Option func(*Plugin)
Option configures the plugin; pass options to New.
func AllowKeyPrefix ¶
AllowKeyPrefix lets a create name a prefix of its own for the key (CreateRequest.Prefix), as better-auth's prefix body field does: up to max characters of letters, digits, "_" and "-", placed after the configuration's prefix, which stays first because it routes the key to this plugin. The full prefix is stored in clear (Key.Prefix) and starts the hint. max 0 means maxKeyPrefixLength (32). Off by default: a prefix outside the configuration is refused with ErrBadKeyPrefix.
func AllowMetadata ¶
func AllowMetadata() Option
AllowMetadata lets create and update store a JSON object (at most maxMetadataLength bytes) with each key, returned in Key.Metadata. Off by default, so an app that never reads metadata does not store whatever a client sends.
func Configuration ¶
Configuration adds a key configuration beside the default one, such as "live" and "test" with the prefixes "sk_live_" and "sk_test_": each has its own prefix, scopes, limits, expiry and name rules, and every key records the configuration it was made under (configId), all in the one auth_api_key table. A configuration starts from the plugin's own options and applies opts on top; Scopes adds to the inherited list. A create, get, update, revoke or list names one with configId; none means DefaultConfig. The plugin-wide options (WithCache, DistinctErrors, DeferUpdates) and a nested Configuration are refused inside one (ErrBadConfig).
better-auth's equivalent is apiKey([{ configId, ... }]). Unlike better-auth, an unknown configId is refused (ErrUnknownConfig) instead of falling back to the default configuration, so a typo cannot mint a key under the wrong prefix and limits.
func DefaultLimits ¶
DefaultLimits gives every key made through the create operation these limits; a key made from Go with CreateFor takes the limits passed to it. For an app whose keys should all be throttled the same way.
func DefaultPermissions ¶
func DefaultPermissions(perms Permissions) Option
DefaultPermissions gives a key created without permissions these, better-auth's permissions.defaultPermissions. They must pass the WithPermissions vocabulary, or auth.New fails with ErrBadPermissions. nil clears an inherited default, inside a Configuration.
func DefaultPermissionsFunc ¶
func DefaultPermissionsFunc(fn func(ctx context.Context, ownerID string) Permissions) Option
DefaultPermissionsFunc is DefaultPermissions decided per owner: fn gets the user id, or the organization id of an organization-owned key. A result outside the vocabulary fails the create with ErrBadPermissions. It wins over DefaultPermissions.
func DefaultScopes ¶
DefaultScopes gives a key created without a scopes field these scopes, as better-auth's permissions.defaultPermissions does; a request that sends scopes, even an empty list, gets exactly those. Each must be declared with Scopes, or auth.New fails with ErrBadScope.
func DefaultScopesFunc ¶
DefaultScopesFunc is DefaultScopes decided per owner, better-auth's function form of defaultPermissions: fn gets the user id, or the organization id for an organization-owned key (OrganizationOwned). A scope it returns that was not declared fails the create with ErrUnknownScope: it is the app's bug, and a key must never carry a scope the app does not check. It wins over DefaultScopes.
func DeferUpdates ¶
func DeferUpdates() Option
DeferUpdates moves the writes that do not decide the answer off the request, as better-auth's deferUpdates does: the last_used_at and updated_at stamp and DeleteSpentKeys' delete run through the core's background runner (auth.WithBackground) after the key was checked. The rate-limit and quota writes stay on the request, because they are the limit: deferred, they would let a burst through, which is also why better-auth's database path awaits them.
func DeleteSpentKeys ¶
func DeleteSpentKeys() Option
DeleteSpentKeys deletes a key that can never work again when it is next presented, as better-auth always does: one past its expiry, and one whose quota reached zero with no refill. The request is still refused the same way. Off by default, so the owner's list shows a key that expired or ran out until auth.Sweep removes it.
func DistinctErrors ¶
func DistinctErrors() Option
DistinctErrors answers each refused key with its own code, as better-auth does: apikey.key_disabled, apikey.key_expired, apikey.invalid_owner and apikey.owner_blocked (401) on every route, and apikey.invalid_key from Verify. The risk, which is why it is off by default: whoever holds a stolen key learns whether the owner paused it, let it expire or revoked it, and a request with a refused key fails with that 401 even on a route that would serve an anonymous caller, where the default lets it through signed out.
func Generate ¶
Generate replaces the random part of a new key (the text after the prefix) with fn's result, for an app that needs a particular alphabet or length; the prefix stays, because it routes the key to this plugin. fn must return at least minSecretLength (22) characters from a cryptographic source; a shorter result fails the create with ErrShortSecret. The default is auth.NewToken: 32 random bytes, base64url.
func HintLength ¶
HintLength sets how many characters after the prefix the hint keeps, at most maxHintLength (8); 0 keeps the prefix alone. The default is 4.
func NameBounds ¶
NameBounds refuses a name shorter than min or longer than max runes (ErrBadName) at create and rename, instead of the default of cutting a long name to NameLength. For apps whose key list is a form with its own rules. min 0 allows an empty name (unless RequireName); max must be at least min and positive, or Validate fails.
func NameLength ¶
NameLength overrides the default of 64 runes a key's name is cut to.
func OrganizationOwned ¶
func OrganizationOwned() Option
OrganizationOwned makes a configuration's keys belong to an organization rather than a user, better-auth's references: "organization". A create names the organization (CreateRequest.OrganizationID) and needs the apiKey "create" permission there; get, list, update and revoke need "read", "read", "update" and "delete". Such a key never signs anyone in (no single user stands behind it): a bearer request with one is signed out (ErrOrganizationKey under DistinctErrors), and the app checks it with Verify, which reports Verification.OrganizationID. Off by default; it needs WithOrganizations (ErrOrganizationsRequired), and its prefix may not overlap a user-owned configuration's (ErrBadConfig), so a key's prefix alone says which kind it is.
func PreCheck ¶
PreCheck runs fn on every presented key before the database lookup, as better-auth's customAPIKeyValidator does: a cheap shape or allow-list check that spares the lookup. false refuses the key exactly like an unknown one (auth.ErrSignedOut, or ErrInvalidKey under DistinctErrors). fn sees the whole key, so it must never log or store it. The configuration is chosen by the key's prefix, the longest that matches.
func RejectUnknownKeys ¶
func RejectUnknownKeys() Option
RejectUnknownKeys answers a bearer token that starts with a key prefix but is no key (unknown, revoked, or refused for a reason DistinctErrors would name) with 401 apikey.invalid_key, as better-auth's INVALID_API_KEY does, instead of the default, which lets the request through signed out (401 auth.signed_out only on a route that needs a user). Without DistinctErrors every refusal, a banned owner's included, is that same apikey.invalid_key, so the answer still says nothing about why; with it, only a key that was never found is.
The default exists because the core routes any bearer token that starts with a key prefix to this plugin, and when the plugin does not know the token it falls through to the sessions: a session token that happens to begin with the prefix must still work. Under this option nothing falls through, so auth.New refuses it unless every configuration's prefix holds a character no token the core issues can contain (core session tokens, stateless ones included, are base64url: letters, digits, "-" and "_"), such as "sk.live_" (ErrPrefixNotDistinct). That keeps the guarantee that no session is ever refused as a key.
The cost: a request carrying a bad key now fails with that 401 even on a route that serves anonymous callers. It weakens nothing, so auth.New does not warn about it.
func RequireName ¶
func RequireName() Option
RequireName refuses a create without a name (ErrNameRequired), for apps whose key list must say what each key is for.
func Scopes ¶
Scopes declares every scope a key may carry, such as "orders:read". A request for another scope is refused, so a typo cannot mint a key whose scope the app never checks. An app that decides only through permissions (WithPermissions) may leave it out: a key then carries no scope, and a request for one is refused as unknown.
func StampEveryUse ¶
func StampEveryUse() Option
StampEveryUse makes every use of a key write last_used_at and updated_at, as better-auth's verify stamps updatedAt on every call, and makes that write a check: it is guarded on the key's id and runs on the request (never deferred by DeferUpdates), so a key deleted while its request was being checked is refused (auth.ErrSignedOut, or ErrInvalidKey under DistinctErrors) and its cache entry dropped, even when the key has no limits and so no counter write would notice. A failing write fails the request. The default writes last_used_at at most once a minute and only logs a failure, so a key without limits costs no write per request; this option buys exact timestamps and mid-request revocation for one write per use.
func WithCache ¶
WithCache puts c in front of the key lookup for ttl (0 is DefaultCacheTTL, at most MaxCacheTTL). A hit spares the lookup; the rate limit and quota still run as guarded writes against the database, re-reading the row when the cached counters are stale, so neither can be exceeded through the cache. Revoke, Update and SetLimits delete the entry. What stays true only up to the TTL: a key revoked on another instance with an in-process cache, or while a request was filling the entry, keeps working until the entry expires; and last_used_at moves only when the database is read, so at most once per TTL. A failing cache is logged and the database is read instead.
func WithExpiry ¶
WithExpiry bounds and defaults CreateRequest.ExpiresInDays.
func WithOrganizations ¶
func WithOrganizations(o Organizations) Option
WithOrganizations supplies the organization membership an OrganizationOwned configuration checks. Plugin-wide: refused inside a Configuration (ErrBadConfig).
func WithPermissions ¶
func WithPermissions(c PermissionChecker) Option
WithPermissions lets a key carry permissions, a resource-to-actions set checked by c, better-auth's permissions on a key; nil turns them off (inside a Configuration). c is a Vocabulary for the built-in check, or the adapter of a permission model the app picked (plugin/access's accessapikey). Permissions are set from Go only (CreateRequest.Permissions, CreateFor, SetPermissions, DefaultPermissions), as better-auth makes them a server-only property, and checked with VerifyPermissions or Authorize. Off by default: a key given permissions without it is ErrPermissionsOff. Scopes stay as they are; permissions are the richer check beside them.
type Organizations ¶
type Organizations interface {
APIKeyPermission(ctx context.Context, orgID, userID, action string) (member, allowed bool, err error)
}
Organizations is how the plugin asks about an organization without importing one: plugin/organization is its own module and depends on the core, so the core cannot depend on it. *organization.Plugin implements it; an app with its own organizations writes the method, or uses OrganizationsFunc.
APIKeyPermission reports whether userID is a member of orgID and, if so, whether their role grants action on ResourceAPIKey. The organization's creator role passes every action, as better-auth's allowCreatorAllPermissions does. err is only an infrastructure failure: a non-member is member false, not an error.
type OrganizationsFunc ¶
type OrganizationsFunc func(ctx context.Context, orgID, userID, action string) (member, allowed bool, err error)
OrganizationsFunc adapts a function to Organizations.
func (OrganizationsFunc) APIKeyPermission ¶
func (f OrganizationsFunc) APIKeyPermission(ctx context.Context, orgID, userID, action string) (bool, bool, error)
APIKeyPermission calls f.
type PermissionChecker ¶
type PermissionChecker interface {
// Check returns an error when p names anything the model does not
// declare; the plugin wraps it in ErrBadPermissions.
Check(p Permissions) error
// Grants reports whether held covers want; want is never empty.
Grants(held, want Permissions) bool
}
PermissionChecker is the permission model behind WithPermissions: which permissions a key may carry, and whether a key's permissions cover a check. Vocabulary is the built-in one; plugin/access's accessapikey adapts an access control, and an app may write its own. The plugin keeps the rules every model shares: a key without permissions, an empty check, and stored permissions Check no longer accepts all grant nothing. It must be safe for concurrent use.
type Permissions ¶
Permissions are actions per resource, better-auth's permissions on a key: what a key holds, or what a check asks for. JSON-encoded it is better-auth's permission object. It has the shape of plugin/access.Permissions, so an app holding those converts with apikey.Permissions(p); the plugin keeps its own type because the core module carries no permission engine.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the API-key plugin: it owns auth_api_key, mints keys through its operations, and resolves "Bearer <prefix>…" tokens to their owner. Build it with New; the zero value has no operations.
func New ¶
New builds the plugin and defines its operations. Scopes or WithPermissions is required: auth.New refuses a configuration that declares neither (ErrNoScopes).
func (*Plugin) Authorize ¶
func (p *Plugin) Authorize(ctx context.Context, want Permissions) error
Authorize checks the API key of the current request against want, for an app's own routes: nil when the key's permissions grant every action, ErrPermissionDenied when they do not or when the request was not made with a key (a signed-in browser has no key permissions). It reads the key row once per call. Wrap it in authhttp.RequireFunc to guard a route.
func (*Plugin) Create ¶
func (p *Plugin) Create(ctx context.Context, in CreateRequest) (CreateResult, error)
Create makes a key for the signed-in user. It needs a fresh sign-in, because a key outlives the session that made it.
func (*Plugin) CreateFor ¶
func (p *Plugin) CreateFor(ctx context.Context, userID string, in CreateRequest, l Limits) (CreateResult, error)
CreateFor makes a key for userID from Go only, with the given limits: a provisioning job, an import, a key minted when a customer subscribes. The cap, the scope list, the name and expiry rules apply as in create; metadata is accepted without AllowMetadata, because the caller is the app, not a client. The session requirement does not apply: the caller is trusted code, like organization.CreateFor. Under an OrganizationOwned configuration the key belongs to in.OrganizationID and userID is the member creating it, whose role must grant the apiKey "create" permission, as better-auth checks a server-side create with userId.
func (*Plugin) DeleteOrganizationKeys ¶
DeleteOrganizationKeys deletes every key orgID owns and their cache entries, for an app to call when it deletes the organization (from plugin/organization's Hooks.AfterDeleteOrg): no foreign key does it (ADR-0002), and a key of a deleted organization must stop working. It returns how many keys went.
func (*Plugin) Get ¶
Get returns one key of the signed-in user; ErrNotFound for another user's or an unknown id.
func (*Plugin) ListKeys ¶
func (p *Plugin) ListKeys(ctx context.Context, in ListRequest) (ListResult, error)
ListKeys returns one page of the signed-in user's keys, sorted as asked, with the total.
func (*Plugin) Name ¶
Name is PluginName, the owner recorded on the plugin's migrations; it never changes.
func (*Plugin) Operations ¶
Operations lists create, list, get, revoke and update, for the transports to mount.
func (*Plugin) OptionErr ¶
OptionErr is every error the options recorded, such as auth.ErrNilOption naming an option given nil; auth.New refuses to start while it is not nil (auth.OptionChecker).
func (*Plugin) OwnerRefused ¶
OwnerRefused is auth.OwnerRefusalReporter: under DistinctErrors a banned owner's key answers ErrOwnerBlocked, otherwise nil keeps ErrSignedOut.
func (*Plugin) ResolveToken ¶
ResolveToken finds a key by its hash. An unknown, revoked, expired or disabled key is auth.ErrSignedOut, all alike (each its own code under DistinctErrors, ErrInvalidKey under RejectUnknownKeys alone). A key past its rate limit is auth.ErrRateLimited (with a Retry-After) and one past its quota ErrUsageExceeded: the key was valid, so saying why gives nothing away. last_used_at is written at most once a minute, and a failure to write it is only logged: it is information, not a check.
The lookup runs on the connection for ctx, like every other statement, so an Auth built without a database reports auth.ErrNoDatabase rather than dereferencing a nil handle.
func (*Plugin) SetLimits ¶
SetLimits replaces a key's rate limit and quota from Go only, resetting the counters: the window starts again and Remaining becomes the new value. ErrNotFound for an unknown id. The key's cache entry is dropped.
func (*Plugin) SetPermissions ¶
SetPermissions replaces a key's permissions from Go only; nil clears them. They are checked against the key's configuration's WithPermissions (ErrPermissionsOff, ErrBadPermissions). ErrNotFound for an unknown id. The key's cache entry is dropped, so the change applies to the next request.
func (*Plugin) Sweep ¶
Sweep deletes keys that expired before cutoff. An expired key is already refused on use; deleting it keeps the table, and the owner's list, to keys that can still do something.
func (*Plugin) Tables ¶
Tables declares auth_api_key and its alias, so auth.New can refuse a second owner of either.
func (*Plugin) TokenPrefix ¶
TokenPrefix routes bearer tokens that start with the prefix here instead of to the session table. auth.New refuses two resolvers whose prefixes overlap.
func (*Plugin) TokenPrefixes ¶
TokenPrefixes lists every configuration's prefix, so the core routes a key of any configuration here; overlapping prefixes are this plugin's own affair, because a key is found by its hash and its configuration read from its row.
func (*Plugin) TypedMigrations ¶
TypedMigrations creates auth_api_key and its per-user index for engine. The key hash is UNIQUE: a lookup by hash must find at most one key.
func (*Plugin) Update ¶
Update changes a key of the signed-in user. It needs a fresh sign-in, like create: resuming a key or extending its expiry hands out access that outlives the session.
func (*Plugin) UserTables ¶
UserTables declares that auth_api_key rows belong to the user in user_id, so DeleteUser removes them (no foreign key does, ADR-0002).
func (*Plugin) Validate ¶
Validate checks every configuration (declared scopes the scopes column can store apart, a prefix a secret scanner can recognize, limits under which a create can succeed), the cache TTL, and under RejectUnknownKeys that no core token can start with a key prefix.
func (*Plugin) Verify ¶
Verify checks a key the app received somewhere other than the Authorization header (a webhook signature, a websocket message, a CLI flag) and reports whether it is good and for whom, counting the use against the key's rate limit and quota exactly as a bearer request would. scopes, when given, must all be on the key. A key of any configuration is accepted; VerifyFor names one. Go only: it is the app's check, not a client's. The error is only an infrastructure failure; a bad key is a Verification with Valid false.
func (*Plugin) VerifyFor ¶
func (p *Plugin) VerifyFor(ctx context.Context, config, secret string, scopes ...string) (Verification, error)
VerifyFor is Verify for keys of one configuration, better-auth's verify with configId: a key of another configuration is refused like an unknown one, before its counters move, and an unknown configuration is a Verification with Reason ErrUnknownConfig.
func (*Plugin) VerifyPermissions ¶
func (p *Plugin) VerifyPermissions(ctx context.Context, secret string, want Permissions) (Verification, error)
VerifyPermissions is Verify that also requires the key's permissions to grant every action of want, better-auth's verify with permissions: a key without permissions, or whose permissions fall short, is a Verification with Reason ErrPermissionDenied. The use is counted either way, as Verify's scope check counts it.
type RevokeRequest ¶
type RevokeRequest struct {
ID string `json:"id"`
// Config, when set, must be the key's configuration (ErrNotFound
// otherwise).
Config string `json:"configId,omitempty"`
}
RevokeRequest names a key of the signed-in user. Another user's key id is ErrNotFound, the same as an unknown one.
type UpdateRequest ¶
type UpdateRequest struct {
ID string `json:"id"`
// Config, when set, must be the key's configuration (ErrNotFound
// otherwise). The key's own configuration's rules apply either way.
Config string `json:"configId,omitempty"`
// Name renames the key; under RequireName a blank name is refused.
Name *string `json:"name,omitempty"`
// ExpiresInDays sets a new expiry from now, under the same WithExpiry
// rules as create: 0 is "never" unless a default or a maximum applies.
ExpiresInDays *int `json:"expiresInDays,omitempty"`
// ExpiresIn sets a new expiry in seconds from now, better-auth's unit,
// under the same rules as CreateRequest.ExpiresIn; setting it with
// ExpiresInDays is ErrExpiryConflict.
ExpiresIn *int64 `json:"expiresIn,omitempty"`
// Disabled pauses the key (true) or resumes it (false). A disabled key
// is refused exactly like an unknown one.
Disabled *bool `json:"disabled,omitempty"`
// Metadata replaces the key's metadata; needs AllowMetadata.
Metadata json.RawMessage `json:"metadata,omitempty"`
// Scopes replaces the key's scopes, each one declared with Scopes
// (ErrUnknownScope otherwise); nil leaves them, an empty list clears
// them (omitzero keeps that empty list on the wire). Widening a key's
// scopes needs the same fresh sign-in as minting one with them.
Scopes []string `json:"scopes,omitzero"`
}
UpdateRequest changes a key of the signed-in user. A nil field is left as it is; ErrNoChanges when every field is nil. Another user's key id is ErrNotFound, the same as an unknown one.
type Verification ¶
type Verification struct {
Valid bool
Reason error
Key *Key
UserID string
// OrganizationID is the owner of an organization-owned key, whose
// UserID is "".
OrganizationID string
}
Verification is Verify's answer. Valid is false for every key problem, with Reason saying which, as the bearer path would answer it: auth.ErrSignedOut (unknown, revoked, expired, disabled, or an owner who can no longer sign in; under DistinctErrors ErrInvalidKey, ErrKeyExpired, ErrKeyDisabled or ErrKeyOwnerGone), auth.ErrRateLimited, ErrUsageExceeded, ErrScopeMissing, and from VerifyFor ErrUnknownConfig. Key and UserID are set only when Valid.
type Vocabulary ¶
Vocabulary is the built-in PermissionChecker: the resources a key's permissions may name and the actions each allows. auth.New refuses an empty one, a resource with no action and an empty or repeated action.
func Statements ¶
func Statements() Vocabulary
Statements is the API-key vocabulary for an organization's access control: merge it into the app's statements so its roles can grant the actions. A new map each call, so an app can change its copy.
func (Vocabulary) Check ¶
func (s Vocabulary) Check(p Permissions) error
Check reports whether p names only declared resources and actions.
func (Vocabulary) Grants ¶
func (s Vocabulary) Grants(granted, want Permissions) bool
Grants reports whether granted covers every action of every resource of want: better-auth's role(permissions).authorize(want) with AND. An empty want, or a resource of want with no action, is denied, since asking for nothing proves nothing. A resource or action of want the vocabulary no longer declares is denied even when granted holds it (fails closed), and such stale entries of granted are otherwise ignored, so the key's other permissions keep their answer; accessapikey answers the same.