Documentation
¶
Overview ¶
Package admin gives an app's operators the user operations every product needs: find a user, see their identities, ban and unban, end their sessions, and grant or remove admin rights.
An admin is a user whose admin_granted_at column is set. The first admin is granted from Go (Grant), for example from a one-off command, because no one can grant it over HTTP before an admin exists. A ban is a session check: the banned user's sessions stop working on their next request, on every instance. A ban ends the user's sessions, which evicts their entries from a server-side SessionCache (auth.EvictCachedSessions); a client-held cache copy keeps working until it expires, within auth.MaxSessionCacheTTL.
Example (PolicyAuthorizer) ¶
Example_policyAuthorizer plugs a Casbin-style policy into the admin plugin: a support role that may read users but not ban them.
package main
import (
"context"
"fmt"
"slices"
"github.com/ubgo/auth/plugin/admin"
)
// policyAuthorizer decides from Casbin-style policy lines (subject,
// resource, action), the shape a casbin.Enforcer's Enforce(sub, obj, act)
// answers; with Casbin itself, Authorize calls the enforcer instead of
// scanning the lines. Every action of every resource asked for must be
// granted to one of the user's roles; Or asks for one resource.
type policyAuthorizer struct{ lines [][3]string }
func (p policyAuthorizer) enforce(sub, obj, act string) bool {
return slices.Contains(p.lines, [3]string{sub, obj, act})
}
// Authorize is the whole adapter: the plugin passes the user's roles and
// the permissions asked for.
//
//ds:def id=policyauthorizer-authorize-cad7rbjj owner=@khanakia stability=stable
func (p policyAuthorizer) Authorize(_ context.Context, req admin.AccessRequest) (bool, error) {
if len(req.Permissions) == 0 {
return false, nil // asking for nothing proves nothing
}
anyRes := false
for res, acts := range req.Permissions {
ok := len(acts) > 0 && slices.ContainsFunc(req.Roles, func(role string) bool {
return !slices.ContainsFunc(acts, func(act string) bool { return !p.enforce(role, res, act) })
})
if !ok && req.Connector != admin.Or {
return false, nil
}
anyRes = anyRes || ok
}
return anyRes, nil
}
// DefinesRole lets set-role store the roles the policy names.
func (p policyAuthorizer) DefinesRole(name string) bool {
return slices.ContainsFunc(p.lines, func(l [3]string) bool { return l[0] == name }) || name == admin.RoleUser
}
// Example_policyAuthorizer plugs a Casbin-style policy into the admin plugin:
// a support role that may read users but not ban them.
//
//ds:def id=example-policyauthorizer-27fwf8au owner=@khanakia stability=stable
func main() {
authz := policyAuthorizer{lines: [][3]string{
{"admin", admin.ResourceUser, admin.ActionBan},
{"admin", admin.ResourceUser, admin.ActionGet},
{"support", admin.ResourceUser, admin.ActionGet},
}}
p := admin.New(admin.WithAuthorizer(authz))
ctx := context.Background()
fmt.Println(p.CheckRolePermission(ctx, "support", admin.Permissions{admin.ResourceUser: {admin.ActionGet}}))
fmt.Println(p.CheckRolePermission(ctx, "support", admin.Permissions{admin.ResourceUser: {admin.ActionBan}}))
fmt.Println(p.CheckRolePermission(ctx, "support,admin", admin.Permissions{admin.ResourceUser: {admin.ActionBan}}))
}
Output: true false true
Index ¶
- Constants
- Variables
- func Bindings(c *AdminColumns) []sqlb.Binding
- func DefaultRoles() map[string]Permissions
- func Grants(granted, want Permissions, connector Connector) bool
- type AccessRequest
- type AdminColumns
- type AllSessionsRequest
- type AllSessionsResult
- type Authorizer
- type AuthorizerFunc
- type Ban
- type BanRequest
- type Connector
- type CreateUserRequest
- type CreateUserResult
- type EditableColumn
- type EmailSetter
- type EventQuerier
- type EventRecorder
- type FilterField
- type FilterOperator
- type Flag
- type FleetSession
- type IdentityView
- type ImpersonateRequest
- type ImpersonateResult
- type Option
- func AdminIDs(ids ...string) Option
- func AdminRoles(names ...string) Option
- func AllowImpersonatingAdmins() Option
- func BannedMessage(fn func(ctx context.Context, b Ban) string) Option
- func DefaultBanDuration(d time.Duration) Option
- func DefaultBanReason(reason string) Option
- func DefaultRole(name string) Option
- func EditableColumns(cols ...EditableColumn) Option
- func ImpersonationTTL(d time.Duration) Option
- func SearchRate(limit int, window time.Duration) Option
- func WithAuthorizer(a Authorizer) Option
- type PasswordSetter
- type PermissionRequest
- type PermissionResult
- type Permissions
- type Plugin
- func (p *Plugin) AllSessions(ctx context.Context, in AllSessionsRequest) (AllSessionsResult, error)
- func (p *Plugin) Audit(ctx context.Context, q audit.Query) (audit.Page, error)
- func (p *Plugin) Ban(ctx context.Context, userID, reason string) error
- func (p *Plugin) BanFor(ctx context.Context, userID, reason string, until time.Time) error
- func (p *Plugin) CheckRolePermission(ctx context.Context, role string, perms Permissions) bool
- func (p *Plugin) Columns() []auth.ColumnExtension
- func (p *Plugin) CreateUser(ctx context.Context, in CreateUserRequest) (auth.UserView, error)
- func (p *Plugin) DataHooks() auth.DataHooks
- func (p *Plugin) DisableTwoFactor(ctx context.Context, userID string) error
- func (p *Plugin) EnableTwoFactor(ctx context.Context, userID string) error
- func (p *Plugin) Grant(ctx context.Context, userID string) error
- func (p *Plugin) HasPermission(ctx context.Context, in PermissionRequest) (bool, error)
- func (p *Plugin) Impersonate(ctx context.Context, in ImpersonateRequest) (ImpersonateResult, error)
- func (p *Plugin) Init(a *auth.Auth) error
- func (p *Plugin) Name() string
- func (p *Plugin) Operations() []auth.Operation
- func (p *Plugin) OptionErr() error
- func (p *Plugin) RateLimits() []auth.RateRule
- func (p *Plugin) RemoveUser(ctx context.Context, userID string) error
- func (p *Plugin) ResetTwoFactor(ctx context.Context, userID string) error
- func (p *Plugin) ResetTwoFactorFor(ctx context.Context, userID string) error
- func (p *Plugin) RevokeSession(ctx context.Context, userID, sessionID string) error
- func (p *Plugin) RevokeSessions(ctx context.Context, userID string) error
- func (p *Plugin) RolesOf(ctx context.Context, userID string) ([]string, error)
- func (p *Plugin) SearchUsers(ctx context.Context, in SearchUsersRequest) (UsersResult, error)
- func (p *Plugin) SessionPredicates() []sqlb.Predicate
- func (p *Plugin) Sessions(ctx context.Context, userID string) ([]auth.SessionView, error)
- func (p *Plugin) SetAdmin(ctx context.Context, userID string, admin bool) error
- func (p *Plugin) SetRole(ctx context.Context, in SetRoleRequest) error
- func (p *Plugin) SetRoleFor(ctx context.Context, userID string, roles ...string) error
- func (p *Plugin) SetUserEmail(ctx context.Context, in SetEmailRequest) error
- func (p *Plugin) SetUserPassword(ctx context.Context, userID string, password auth.Token) error
- func (p *Plugin) StopImpersonating(ctx context.Context) (StopResult, error)
- func (p *Plugin) TwoFactor(ctx context.Context, userID string) (TwoFactorResult, error)
- func (p *Plugin) Unban(ctx context.Context, userID string) error
- func (p *Plugin) UpdateUser(ctx context.Context, in UpdateUserRequest) error
- func (p *Plugin) User(ctx context.Context, userID string) (UserDetail, error)
- func (p *Plugin) Users(ctx context.Context, in UsersRequest) (UsersResult, error)
- func (p *Plugin) Validate(*auth.Config) error
- type RevokeSessionRequest
- type RoleCatalog
- type Roles
- type SearchField
- type SearchOperator
- type SearchUsersRequest
- type SessionsResult
- type SetAdminRequest
- type SetEmailRequest
- type SetPasswordRequest
- type SetRoleRequest
- type SortDirection
- type SortField
- type StopResult
- type TwoFactorManager
- type TwoFactorResult
- type UpdateUserRequest
- type UserDetail
- type UserRequest
- type UserSort
- type UserSummary
- type UsersRequest
- type UsersResult
Examples ¶
Constants ¶
const ( EndpointUsers auth.EndpointID = "admin.users" EndpointSearchUsers auth.EndpointID = "admin.search-users" EndpointUser auth.EndpointID = "admin.user" EndpointBan auth.EndpointID = "admin.ban" EndpointUnban auth.EndpointID = "admin.unban" EndpointRevokeSessions auth.EndpointID = "admin.revoke-sessions" EndpointSetAdmin auth.EndpointID = "admin.set-admin" EndpointImpersonate auth.EndpointID = "admin.impersonate" EndpointStopImpersonating auth.EndpointID = "admin.stop-impersonating" EndpointUpdateUser auth.EndpointID = "admin.update-user" EndpointSessions auth.EndpointID = "admin.sessions" EndpointRevokeSession auth.EndpointID = "admin.revoke-session" EndpointRemoveUser auth.EndpointID = "admin.remove-user" EndpointCreateUser auth.EndpointID = "admin.create-user" EndpointSetPassword auth.EndpointID = "admin.set-password" EndpointSetEmail auth.EndpointID = "admin.set-email" EndpointAudit auth.EndpointID = "admin.audit" )
Operation ids. They are permanent: rate rules, hooks and every transport route address an operation by id, so renaming one breaks deployed clients.
const ( ColGrantedAt = "admin_granted_at" ColBannedAt = "admin_banned_at" ColBanReason = "admin_ban_reason" ColBanExpiresAt = "admin_ban_expires_at" )
Columns this plugin adds to auth_user, prefixed with the plugin name so no other plugin's column can collide with them.
const ( // DefaultPageSize is the page an admin screen gets when it names none // and the app set no auth.WithDefaultPageSize: one screenful of rows. //ds:def id=defaultpagesize-k5wq5psj owner=@khanakia stability=stable DefaultPageSize = 50 // MaxPageSize caps a requested page, so one request cannot read the whole // user table; a larger limit is clamped rather than refused. //ds:def id=maxpagesize-9qpk2wp4 owner=@khanakia stability=stable MaxPageSize = 200 )
Paging bounds for the user list.
const ( // MaxSearchLength is the longest search value, in characters: the widest // text column the search reads (an address in auth_identity.value) holds // 320 characters on MySQL, so a longer fragment can match nothing. //ds:def id=maxsearchlength-r7d4kq2n owner=@khanakia stability=stable MaxSearchLength = 320 // DefaultSearchLimit and DefaultSearchWindow bound search-users per client // IP: 30 searches a minute is a person typing into a search box, and keeps // a stolen admin session from walking the user table by fragment at // machine speed. SearchRate replaces them. Go API calls carry no IP and // are not counted, as with every ByIP rule. //ds:def id=defaultsearchlimit-p3w8zn6c owner=@khanakia stability=stable DefaultSearchLimit = 30 DefaultSearchWindow = time.Minute )
Search bounds and its rate rule.
const ( ResourceUser = "user" ResourceSession = "session" // ResourceTwoFactor is another user's second factor: read it, pause it, // switch it back on, or reset it so the user enrols again. Its own // resource rather than more user actions, so a support role can be // granted it without ban or set-password. ResourceTwoFactor = "two-factor" )
Resources of DefaultStatements.
const ( ActionCreate = "create" ActionList = "list" ActionSetRole = "set-role" ActionBan = "ban" ActionImpersonate = "impersonate" ActionImpersonateAdmins = "impersonate-admins" ActionDelete = "delete" ActionSetPassword = "set-password" ActionSetEmail = "set-email" ActionGet = "get" ActionUpdate = "update" ActionRevoke = "revoke" // ActionEnable, ActionDisable and ActionReset are the two-factor // actions beside ActionGet: switch a paused factor back on, pause it, // and remove it with its recovery codes. ActionEnable = "enable" ActionDisable = "disable" ActionReset = "reset" )
Actions of DefaultStatements, better-auth's names.
const ( // RoleAdmin may do every admin operation except impersonate another // admin, which stays opt-in (AllowImpersonatingAdmins, or a role with // impersonate-admins). RoleAdmin = "admin" // RoleUser grants nothing: the role of everyone else. RoleUser = "user" )
Built-in role names.
const ( EndpointSetRole auth.EndpointID = "admin.set-role" EndpointHasPermission auth.EndpointID = "admin.has-permission" )
Operation ids of the role operations.
const ( EndpointTwoFactor auth.EndpointID = "admin.two-factor" EndpointDisableTwoFactor auth.EndpointID = "admin.disable-two-factor" EndpointEnableTwoFactor auth.EndpointID = "admin.enable-two-factor" EndpointResetTwoFactor auth.EndpointID = "admin.reset-two-factor" )
Operation ids of the two-factor routes.
const ( DataName = "name" DataImage = "image" )
Keys of update-user's data that are the profile fields, set as Name and Image are.
const ColRole = "admin_role"
ColRole is the column holding a user's admin roles.
const DefaultBannedMessage = "This account is banned."
DefaultBannedMessage is what a banned user reads at sign-in when BannedMessage is not configured.
const EndpointAllSessions auth.EndpointID = "admin.all-sessions"
EndpointAllSessions lists every live session of every user, for an operator's session monitor (better-auth's dashboard view of all sessions). One session is ended with revoke-session and all of a user's with revoke-sessions, as before.
const FilterListSeparator = ","
FilterListSeparator splits the value of in and not_in: "a,b" is the two values a and b. A query string carries one value per parameter, so the list is one comma-joined value.
const MaxFilterValues = MaxPageSize
MaxFilterValues bounds the values of one in or not_in filter: a page of MaxPageSize ids is the largest list an admin screen has a reason to send, and the bound keeps one request from becoming a statement with thousands of parameters.
const PluginName = "admin"
PluginName is the plugin's permanent identity.
Variables ¶
var ( GrantedAt = sqlb.Col[*time.Time](auth.TableUser, ColGrantedAt) BannedAt = sqlb.Col[*time.Time](auth.TableUser, ColBannedAt) BanReason = sqlb.Col[string](auth.TableUser, ColBanReason) BanExpiresAt = sqlb.Col[*time.Time](auth.TableUser, ColBanExpiresAt) )
Typed columns on auth_user, for an app that filters or shows users by admin or ban state in its own queries.
var ( // ErrForbidden refuses a caller who is not an admin. It is checked before // the target is looked up, so it never reveals whether a user exists. ErrForbidden = auth.NewError("admin.forbidden", http.StatusForbidden, "Only an administrator can do this.") // ErrAdminTarget refuses to ban or impersonate an admin: a stolen admin // session must not be able to lock out or act as the other admins. // AllowImpersonatingAdmins lifts it for impersonation only. ErrAdminTarget = auth.NewError("admin.admin_target", http.StatusConflict, "Remove this user's admin rights first.") // ErrBannedTarget refuses to impersonate a banned user, whose sessions the // ban exists to stop. ErrBannedTarget = auth.NewError("admin.banned_target", http.StatusConflict, "This user is banned.") // ErrLastAdmin refuses to remove the last admin, which would leave nobody // able to grant the rights back over HTTP. ErrLastAdmin = auth.NewError("admin.last_admin", http.StatusConflict, "Keep at least one administrator.") // ErrBadSort refuses a users sort outside UserSortValues. ErrBadSort = auth.NewError("admin.bad_sort", http.StatusBadRequest, "This sort order does not exist.") // ErrBadFilter refuses a users filter that is neither FlagTrue, FlagFalse // nor empty. ErrBadFilter = auth.NewError("admin.bad_filter", http.StatusBadRequest, "A filter is true, false or absent.") // ErrBanned refuses a banned user at sign-in, on every sign-in method. It // is raised only after the credential was proven (the session is about to // be created), so it tells a stranger nothing: without the password, the // code or the link they never get this far. BannedMessage changes the text. ErrBanned = auth.NewError("admin.banned", http.StatusForbidden, DefaultBannedMessage) // ErrNoPasswordPlugin refuses set-password, and create-user with a // password, when no registered plugin can store one. ErrNoPasswordPlugin = auth.NewError("admin.no_password_plugin", http.StatusConflict, "No password plugin is registered.") // ErrNoEmailPlugin refuses set-email and create-user when no registered // plugin can give a user an email address. ErrNoEmailPlugin = auth.NewError("admin.no_email_plugin", http.StatusConflict, "No email plugin is registered.") // ErrEmailRequired refuses create-user without an address: a user with no // identifier could never sign in or be found. ErrEmailRequired = auth.NewError("admin.email_required", http.StatusBadRequest, "An email address is required.") // ErrNoAuditPlugin refuses the audit route when no audit log with a read // side (EventQuerier) is registered: there is no log to read. ErrNoAuditPlugin = auth.NewError("admin.no_audit_plugin", http.StatusConflict, "No audit plugin is registered.") // ErrBadSearchField refuses a search field outside SearchFieldValues. ErrBadSearchField = auth.NewError("admin.bad_search_field", http.StatusBadRequest, "This search field does not exist.") // ErrBadSearchOperator refuses a search operator outside // SearchOperatorValues. ErrBadSearchOperator = auth.NewError("admin.bad_search_operator", http.StatusBadRequest, "This search operator does not exist.") // ErrSearchValue refuses an empty search, which would be the plain list // under another name, and one longer than MaxSearchLength, which no // stored value can contain. ErrSearchValue = auth.NewError("admin.search_value", http.StatusBadRequest, "Search for one to 320 characters.") // ErrSelfTarget refuses an admin impersonating themselves once // AllowImpersonatingAdmins is on (without it, ErrAdminTarget answers // first): it shows nothing new and would only rotate their own session. ErrSelfTarget = auth.NewError("admin.self_target", http.StatusConflict, "You cannot do this to yourself.") )
Errors a client may see. Each carries a stable code for programmatic checks.
var ( // ErrBadFilterField refuses a filter field outside FilterFieldValues. ErrBadFilterField = auth.NewError("admin.bad_filter_field", http.StatusBadRequest, "This filter field does not exist.") // ErrBadFilterOperator refuses an operator outside the field's // FilterOperatorsOf. ErrBadFilterOperator = auth.NewError("admin.bad_filter_operator", http.StatusBadRequest, "This filter operator does not apply to this field.") // ErrBadFilterValue refuses a value the field cannot hold: not true or // false for a flag, not an RFC 3339 time for a time, empty, longer than // MaxSearchLength, or more than MaxFilterValues values for in and not_in. ErrBadFilterValue = auth.NewError("admin.bad_filter_value", http.StatusBadRequest, "This filter value does not fit the field.") )
Errors of the list filter.
var ( // ErrNoUserData refuses a data object with no keys, better-auth's // NO_DATA_TO_UPDATE: an empty edit is a client mistake, not a success. ErrNoUserData = auth.NewError("admin.no_user_data", http.StatusBadRequest, "There is nothing to update.") // ErrFieldNotEditable refuses a data key that is neither a profile field // nor an EditableColumns column. Passwords, addresses and roles have // their own operations (set-password, set-email, set-role), where their // rules live, so they are refused here too. ErrFieldNotEditable = auth.NewError("admin.field_not_editable", http.StatusBadRequest, "This field cannot be changed here.") // ErrBadUserData refuses a data value of the wrong type, or a name or // image that is not a string. ErrBadUserData = auth.NewError("admin.bad_user_data", http.StatusBadRequest, "A value does not fit its field.") )
Errors of update-user's data.
var DefaultStatements = Permissions{ ResourceUser: {ActionCreate, ActionList, ActionSetRole, ActionBan, ActionImpersonate, ActionImpersonateAdmins, ActionDelete, ActionSetPassword, ActionSetEmail, ActionGet, ActionUpdate}, ResourceSession: {ActionList, ActionRevoke, ActionDelete}, ResourceTwoFactor: {ActionGet, ActionDisable, ActionEnable, ActionReset}, }
DefaultStatements is better-auth's admin vocabulary, plus the two-factor resource: every permission the plugin asks for. An access control built for accessadmin starts from it.
var ( // ErrBadRole is a role the plugin does not define, or no role at all. ErrBadRole = auth.NewError("admin.bad_role", http.StatusBadRequest, "This role does not exist.") )
Errors of the role operations.
var ErrNoAuthorizer = errors.New("admin: WithAuthorizer needs an authorizer")
ErrNoAuthorizer was WithAuthorizer(nil).
Deprecated: no longer returned. WithAuthorizer(nil) fails auth.New with auth.ErrNilOption naming the option, as every option given nil does.
var ErrNoTwoFactorPlugin = auth.NewError("admin.no_two_factor_plugin", http.StatusConflict, "No two-factor plugin is registered.")
ErrNoTwoFactorPlugin refuses the two-factor routes when no registered plugin manages second factors (TwoFactorManager). It is answered after the permission check, so it tells a caller without the permission nothing.
var ErrNotImpersonating = auth.NewError("admin.not_impersonating", http.StatusConflict, "This session is not an impersonation.")
ErrNotImpersonating is returned by stop-impersonating on an ordinary session.
var ErrSessionsNotListable = auth.NewError("admin.sessions_not_listable", http.StatusConflict, "Sessions are not kept in the database, so they cannot be listed together.")
ErrSessionsNotListable refuses all-sessions when sessions do not live in the database: a memory, Redis or custom store (auth.WithSessionStore), a mirror of two stores, or stateless sessions keep no table to page through, and an empty page would read as "nobody is signed in".
var FilterFieldValues = []FilterField{FilterEmail, FilterEmailVerified, FilterID, FilterName, FilterRole, FilterBanned, FilterBanReason, FilterBanExpires, FilterCreatedAt, FilterUpdatedAt}
FilterFieldValues is the canonical list of filter fields.
var FilterOperatorValues = []FilterOperator{FilterEq, FilterNe, FilterLt, FilterLte, FilterGt, FilterGte, FilterIn, FilterNotIn, FilterContains, FilterStartsWith, FilterEndsWith}
FilterOperatorValues is the canonical list of filter operators.
var FlagValues = []Flag{FlagTrue, FlagFalse}
FlagValues are the two set values; empty is the third state.
var SearchFieldValues = []SearchField{SearchEmail, SearchName}
SearchFieldValues is the canonical list of search fields.
var SearchOperatorValues = []SearchOperator{SearchContains, SearchStartsWith, SearchEndsWith}
SearchOperatorValues is the canonical list of search operators.
var SortDirectionValues = []SortDirection{SortAsc, SortDesc}
SortDirectionValues is the canonical list of sort directions.
var SortFieldValues = []SortField{SortByCreatedAt, SortByUpdatedAt, SortByName, SortByID}
SortFieldValues is the canonical list of sort fields.
UserRole is the typed admin_role column.
var UserSortValues = []UserSort{UserSortNewest, UserSortOldest, UserSortNameAsc, UserSortNameDesc}
UserSortValues is the canonical list of sort orders.
Functions ¶
func Bindings ¶
func Bindings(c *AdminColumns) []sqlb.Binding
Bindings binds AdminColumns, for a composite user query or LoadUserColumns.
func DefaultRoles ¶
func DefaultRoles() map[string]Permissions
DefaultRoles returns the built-in roles, the default Authorizer's: RoleAdmin may do everything in DefaultStatements except impersonate-admins, which stays opt-in; RoleUser grants nothing. A new map each call.
func Grants ¶
func Grants(granted, want Permissions, connector Connector) bool
Grants reports whether granted covers want under connector, better-auth's role.authorize for a request requiring every listed action: a resource granted nothing fails at once whatever the connector, every listed action of a resource must be granted (and at least one listed), And needs every resource and Or one, and an empty request is denied because asking for nothing proves nothing. Resources are visited in sorted order, so the answer never depends on map iteration. It is exported for an Authorizer that keeps a role table of its own.
Types ¶
type AccessRequest ¶
type AccessRequest struct {
// UserID is the user whose permission is decided: the caller, or
// has-permission's userId. Empty for CheckRolePermission, which decides
// for a role and nobody in particular.
UserID string
// Roles are UserID's admin roles as the plugin resolved them: the stored
// admin_role, the first of AdminRoles for a user the staff flag or
// AdminIDs makes an admin, or DefaultRole when that leaves none. A model
// that does not use roles ignores them.
Roles []string
// Permissions are what is asked for; every action of a resource is
// required.
Permissions Permissions
// Connector joins the resources; And when empty.
Connector Connector
// TargetUserID is the user the operation acts on (ban, set-role,
// impersonate, …) as the request names it, or "" when there is none
// (list, create-user, a check of oneself). It is the input's value: the
// decision comes before the target is looked up, so a refusal never
// reveals whether the user exists.
TargetUserID string
// Operation is the operation asking, or "" outside one
// (CheckRolePermission, RolesOf callers).
Operation auth.EndpointID
// Call is the operation's call (client IP, session, decoded input) for
// an attribute-based rule, or nil outside an operation. Read it; never
// change it.
Call *auth.Call
}
AccessRequest is one permission decision. It is a struct rather than bare strings so it can carry more context later without breaking an Authorizer: a field added here is a zero value to an implementation that ignores it.
type AdminColumns ¶
type AdminColumns struct {
AdminGrantedAt *time.Time
BannedAt *time.Time
BanReason string
// BanExpiresAt is nil for a ban that lasts until unban. A ban whose
// expiry has passed no longer blocks sessions; the row keeps the dates so
// the history stays readable.
BanExpiresAt *time.Time
// Role is the stored admin roles, comma-joined; "" for none (the flag
// and DefaultRole then decide, see RolesOf).
Role string
}
AdminColumns is this plugin's flat part of a composite user. Flat rather than nested because the columns live on auth_user itself (ADR-0001); nil times mean "never granted" and "not banned".
type AllSessionsRequest ¶
type AllSessionsRequest struct {
UserID string `json:"userId,omitempty"`
Page int `json:"page,omitempty,string"`
Limit int `json:"limit,omitempty,string"`
Offset int `json:"offset,omitempty,string"`
}
AllSessionsRequest pages through every live session, most recently used first. UserID, when set, narrows the list to one user's sessions. Strings in JSON, because a Query operation's input arrives as URL query values.
type AllSessionsResult ¶
type AllSessionsResult struct {
Sessions []FleetSession `json:"sessions"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
Offset int `json:"offset"`
}
AllSessionsResult is one page; Total counts every match, for a pager.
type Authorizer ¶
type Authorizer interface {
Authorize(ctx context.Context, req AccessRequest) (bool, error)
}
Authorizer decides whether an AccessRequest is allowed. Any error refuses: the plugin never reads the boolean of a call that returned an error, so a policy engine that is down fails closed. It must be safe for concurrent use.
type AuthorizerFunc ¶
type AuthorizerFunc func(ctx context.Context, req AccessRequest) (bool, error)
AuthorizerFunc adapts a function to Authorizer.
func (AuthorizerFunc) Authorize ¶
func (f AuthorizerFunc) Authorize(ctx context.Context, req AccessRequest) (bool, error)
Authorize calls f.
type BanRequest ¶
type BanRequest struct {
UserID string `json:"userId"`
Reason string `json:"reason,omitempty"`
Minutes int `json:"minutes,omitempty"`
}
BanRequest bans a user. Reason is cut to whole characters that fit the column on every engine rather than refused. Minutes, when positive, makes the ban lift itself after that long; zero means DefaultBanDuration, or until unban when none is configured.
func (BanRequest) TargetUserID ¶
func (r BanRequest) TargetUserID() string
type CreateUserRequest ¶
type CreateUserRequest struct {
Name string `json:"name,omitempty"`
Image string `json:"image,omitempty"`
Email string `json:"email"`
Verified bool `json:"verified,omitempty"`
Password auth.Token `json:"password,omitempty"`
}
CreateUserRequest is the input of create-user: a profile, one email address, and optionally a password. Email is required because a user with no identifier could never sign in or be found. Verified true marks the address verified and primary at once, for an admin who knows it is the person's; false sends the usual verify link. Password is optional: without one the user sets theirs through the forgot flow. The password is an auth.Token so a logged request never shows it.
type CreateUserResult ¶
CreateUserResult is the created user.
type EditableColumn ¶
type EditableColumn struct {
// contains filtered or unexported fields
}
EditableColumn is one of the app's own auth_user columns (auth.WithUserColumns) that update-user may set from its data object (better-auth's update-user data). Build it with Editable; the zero value edits nothing.
func Editable ¶
func Editable[T any](col sqlb.Expr[T], check func(T) error) EditableColumn
Editable lets an admin set col, an app column, through update-user's data object under the column's name. The JSON value must decode into T exactly (an unknown shape is ErrBadUserData), then check, when not nil, may refuse it with its own error; the write goes through auth.SetUserColumn under auth.AppName, so a column the app did not register is refused there (auth.ErrNotColumnOwner) and a plugin's columns stay that plugin's.
func (EditableColumn) Name ¶
func (e EditableColumn) Name() string
Name is the data key the column is set by: the column name.
type EmailSetter ¶
type EmailSetter interface {
auth.Plugin
SetFor(ctx context.Context, userID, address string, verified bool) error
}
EmailSetter is what the admin plugin needs from an identifier plugin to give a user an address: `*email.Plugin` has it. Found through auth.PluginOf.
type EventQuerier ¶
type EventQuerier interface {
auth.Plugin
Events(ctx context.Context, q audit.Query) (audit.Page, error)
}
EventQuerier is what the admin plugin needs from an audit log to serve the audit route: the method `*audit.Plugin` already has. Found through auth.PluginOf at Init; without one the route answers ErrNoAuditPlugin.
type EventRecorder ¶
EventRecorder is what the admin plugin needs from an audit log to record a ban made from Go (BanFor), which no operation hook sees: the method `*audit.Plugin` already has. Found through auth.PluginOf at Init, so any audit implementation with this method is used, and an app without one records nothing extra.
type FilterField ¶
type FilterField string
FilterField is the user field a list filter compares (better-auth's filterField). A closed set rather than a column name, for the reason UserSort is one: an admin filter can never read a column the admin should not see, and each field has the operators that mean something for its type (FilterOperatorsOf).
const ( // FilterEmail compares the user's email addresses (auth_identity rows of // kind email), verified or not. The value is folded as addresses are // stored, so the comparison ignores case. The default when FilterField is // empty, as in better-auth. FilterEmail FilterField = "email" // FilterEmailVerified is true for a user holding at least one verified // address. FilterEmailVerified FilterField = "emailVerified" // FilterID compares the user id exactly. FilterID FilterField = "id" // FilterName compares the display name as stored, case included, as // SearchName does. FilterName FilterField = "name" // FilterRole compares the roles a user holds (RolesOf): a stored role, the // first of AdminRoles for a user the flag or AdminIDs makes an admin, and // DefaultRole for a user with neither. eq means "holds this role". FilterRole FilterField = "role" // FilterBanned is true for a user whose ban is in force, the banned= // flag's meaning. FilterBanned FilterField = "banned" // FilterBanReason compares the stored ban reason, case included. FilterBanReason FilterField = "banReason" // FilterBanExpires compares the end of a ban; a user with no ban end // (none, or a permanent one) never matches. FilterBanExpires FilterField = "banExpires" // FilterCreatedAt and FilterUpdatedAt compare the user row's times. FilterCreatedAt FilterField = "createdAt" FilterUpdatedAt FilterField = "updatedAt" )
Filter fields, better-auth's user field names.
type FilterOperator ¶
type FilterOperator string
FilterOperator is how a list filter compares (better-auth's filterOperator, its where operators).
const ( // FilterEq is the default when FilterOperator is empty. FilterEq FilterOperator = "eq" FilterNe FilterOperator = "ne" FilterLt FilterOperator = "lt" FilterLte FilterOperator = "lte" FilterGt FilterOperator = "gt" FilterGte FilterOperator = "gte" FilterIn FilterOperator = "in" FilterNotIn FilterOperator = "not_in" FilterContains FilterOperator = "contains" FilterStartsWith FilterOperator = "starts_with" FilterEndsWith FilterOperator = "ends_with" )
Filter operators. The text operators contains, starts_with and ends_with treat every character of the value as text, as search-users does.
func FilterOperatorsOf ¶
func FilterOperatorsOf(field FilterField) []FilterOperator
FilterOperatorsOf is the operators field accepts, nil for an unknown field.
type Flag ¶
type Flag string
Flag is a three-valued filter in a query string: FlagTrue, FlagFalse or empty for "do not filter". A string rather than *bool because the input arrives as URL query values.
type FleetSession ¶
type FleetSession struct {
ID string `json:"id"`
UserID string `json:"userId"`
Impersonated bool `json:"impersonated"`
IP string `json:"ip"`
UserAgent string `json:"userAgent"`
CreatedAt time.Time `json:"createdAt"`
LastSeenAt time.Time `json:"lastSeenAt"`
ExpiresAt time.Time `json:"expiresAt"`
}
FleetSession is one row of all-sessions: the user it belongs to and what the user's own session list shows of it, plus its expiry.
type IdentityView ¶
type IdentityView struct {
Kind auth.IdentifierKindName `json:"kind"`
Value string `json:"value"`
Verified bool `json:"verified"`
Primary bool `json:"primary"`
}
IdentityView is one identity as an admin sees it.
type ImpersonateRequest ¶
type ImpersonateRequest struct {
UserID string `json:"userId"`
Minutes int `json:"minutes,omitempty"`
}
ImpersonateRequest names the user to act as. Minutes is optional and capped by auth.MaxImpersonationTTL.
func (ImpersonateRequest) TargetUserID ¶
func (r ImpersonateRequest) TargetUserID() string
type ImpersonateResult ¶
type ImpersonateResult struct {
User auth.UserView `json:"user"`
ExpiresAt time.Time `json:"expiresAt"`
Token auth.Token `json:"-"`
AdminToken auth.Token `json:"-"`
}
ImpersonateResult is the user now being acted as. The session arrives as an effect, replacing the admin's own in the session cookie, while the admin's own session (under a new token) is bound to the client in the flow cookie, for stop-impersonating to re-issue. Token and AdminToken are for Go callers and bearer transports, never encoded into JSON.
type Option ¶
type Option func(*Plugin)
Option configures the plugin; pass options to New.
func AdminIDs ¶
AdminIDs makes the listed users admins without a grant row, for a deploy whose operators are fixed in configuration. They may still be granted and removed like anyone else; the list only adds to what admin_granted_at says. A listed user cannot be banned, impersonated or removed, like any admin.
func AdminRoles ¶
AdminRoles names the roles that make a user an admin (better-auth's adminRoles): protected from bans, impersonation and removal, counted for the last-admin rule, and in sync with the staff flag. ["admin"] by default; the first one is the role the flag stands for.
func AllowImpersonatingAdmins ¶
func AllowImpersonatingAdmins() Option
AllowImpersonatingAdmins lets an admin impersonate another admin, which is refused by default (ErrAdminTarget). Everything else about impersonation stays: a fresh sign-in, the TTL and its cap, the audit record, no FreshSignIn operation while acting, and the way back to the admin's own session. The impersonation session cannot use the admin operations either, so acting as an admin never chains into acting as a third user.
The risk is the reason it is opt-in: with it, one stolen admin session can act as every other admin, so a compromise of the least careful admin is a compromise of all of them. Turn it on only where every admin is equally trusted and an admin needs to see another admin's account as they see it.
func BannedMessage ¶
BannedMessage sets the text a banned user reads at sign-in, from the ban itself: "Banned for chargeback until 3 May", say. The error code stays admin.banned. An empty result falls back to DefaultBannedMessage, so a function that has nothing to say never produces a blank refusal.
func DefaultBanDuration ¶
DefaultBanDuration bounds a ban whose request names no minutes: it lifts itself after d. Without it such a ban lasts until unban, which is today's behaviour and stays the default.
func DefaultBanReason ¶
DefaultBanReason is stored when a ban request names no reason, so a list of banned users never shows an empty cell. It is cut like a given reason.
func DefaultRole ¶
DefaultRole is the role of a user with none stored and no flag (better-auth's defaultRole); RoleUser by default.
func EditableColumns ¶
func EditableColumns(cols ...EditableColumn) Option
EditableColumns declares the app columns update-user's data may set. A later declaration of the same name replaces the earlier one.
func ImpersonationTTL ¶
ImpersonationTTL is how long an impersonation lasts when the request names no minutes. The core still caps every impersonation at auth.MaxImpersonationTTL, so this can only shorten the default, never extend the cap.
func SearchRate ¶
SearchRate replaces the per-IP limit on search-users: at most limit searches per window from one client address. auth.New refuses a limit below one, so the search can be loosened but never left unlimited.
func WithAuthorizer ¶
func WithAuthorizer(a Authorizer) Option
WithAuthorizer replaces the built-in roles as the decider of every admin permission. nil is refused by auth.New. Implement RoleCatalog as well when set-role should accept roles other than admin and user.
type PasswordSetter ¶
type PasswordSetter interface {
auth.Plugin
SetPassword(ctx context.Context, userID string, password auth.Token) error
}
PasswordSetter is what the admin plugin needs from a password plugin to set a user's password: the method `*password.Plugin` already has. Found through auth.PluginOf at Init, so the admin plugin imports no other module and an app registers nothing extra.
type PermissionRequest ¶
type PermissionRequest struct {
UserID string `json:"userId,omitempty"`
Permissions Permissions `json:"permissions"`
Connector Connector `json:"connector,omitempty"`
}
PermissionRequest is the input of has-permission: the permissions asked for, of the caller, or of UserID, which needs user:get.
type PermissionResult ¶
type PermissionResult struct {
Success bool `json:"success"`
}
PermissionResult is the output of has-permission.
type Permissions ¶
Permissions are actions per resource: what a role grants, or what a check asks for ({"user": ["ban"]}). JSON-encoded it is better-auth's permission object, the body of has-permission. Every listed action of a resource is required.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the admin plugin. Build it with New; the zero value has no operations.
func New ¶
New builds the plugin. Who is an admin is data (admin_granted_at); the options only add fixed admins and defaults for bans and impersonations.
func (*Plugin) AllSessions ¶
func (p *Plugin) AllSessions(ctx context.Context, in AllSessionsRequest) (AllSessionsResult, error)
AllSessions pages through every live session, as the signed-in admin.
func (*Plugin) Ban ¶
Ban bans a user and ends their sessions, as the signed-in admin with a fresh sign-in.
func (*Plugin) BanFor ¶
BanFor bans userID from Go only, like Grant from trusted code with no signed-in admin: an import carrying another system's bans, a job acting on a fraud signal. A zero until is permanent; otherwise the ban lifts at until (DefaultBanDuration does not apply, since the caller states the end). The user's sessions end, as with ban. An admin cannot be banned (ErrAdminTarget), an unknown user is auth.ErrUserNotFound. When an audit log is registered (EventRecorder) the ban is recorded under EndpointBan with no actor and the user as subject, in the same transaction. It runs in one transaction, or joins the one in ctx.
func (*Plugin) CheckRolePermission ¶
CheckRolePermission reports whether the authorizer allows role (one name or several, comma-joined) every action of perms, with no database: better-auth's checkRolePermission, for a UI that shows or hides a button by role. The request has no UserID; an unknown role grants nothing and an authorizer error is false.
func (*Plugin) Columns ¶
func (p *Plugin) Columns() []auth.ColumnExtension
Columns adds the admin and ban columns to auth_user.
func (*Plugin) CreateUser ¶
CreateUser creates a user with an address and optionally a password, as the signed-in admin with a fresh sign-in.
func (*Plugin) DataHooks ¶
DataHooks refuse to create a session for a banned user. The hook sits on the session write rather than on any sign-in operation, so it covers every method — password, code, link, passkey, OAuth, a pending second-factor session — and a method added later. It fires only after the method proved the credential, which is what keeps the refusal enumeration-safe: a stranger who does not hold the credential never reaches it, and sees the same wrong-credential answer as for any other account.
func (*Plugin) DisableTwoFactor ¶
DisableTwoFactor pauses a user's second factor, as the signed-in admin with a fresh sign-in and two-factor:disable.
func (*Plugin) EnableTwoFactor ¶
EnableTwoFactor switches a paused second factor back on, as the signed-in admin with a fresh sign-in and two-factor:enable.
func (*Plugin) Grant ¶
Grant makes userID an admin, from Go only. It is how the first admin is made; afterwards admins grant each other through set-admin.
func (*Plugin) HasPermission ¶
HasPermission reports whether the caller, or in.UserID, holds the permissions (better-auth's admin hasPermission).
func (*Plugin) Impersonate ¶
func (p *Plugin) Impersonate(ctx context.Context, in ImpersonateRequest) (ImpersonateResult, error)
Impersonate acts as another user, as the signed-in admin.
func (*Plugin) Init ¶
Init captures the core and finds the plugins that can set a password and an address. Their absence is not an error here: an app without passwords still gets every other admin operation, and set-password tells the caller.
func (*Plugin) Operations ¶
Operations lists every operation.
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) RateLimits ¶
RateLimits puts the search-users rule (SearchRate) on the one admin operation that reads users by fragment.
func (*Plugin) RemoveUser ¶
RemoveUser deletes a user and everything that references them, as the signed-in admin with a fresh sign-in. An admin must lose the rights first.
func (*Plugin) ResetTwoFactor ¶
ResetTwoFactor removes a user's second factor and recovery codes so they enrol again, as the signed-in admin with a fresh sign-in and two-factor:reset.
func (*Plugin) ResetTwoFactorFor ¶
ResetTwoFactorFor resets userID's second factor from Go only, like BanFor from trusted code with no signed-in admin: a recovery command run by an operator. Unlike the route it accepts an admin target, because it is the way back for an admin who lost their phone when no other admin may reset them over HTTP. When an audit log is registered (EventRecorder) the reset is recorded under EndpointResetTwoFactor with no actor and the user as subject, in the same transaction, so a reset that cannot be recorded does not happen. An unknown user is auth.ErrUserNotFound, a user with no factor the second-factor plugin's error (plugin/twofactor: ErrNotEnrolled).
func (*Plugin) RevokeSession ¶
RevokeSession ends one session of a user, as the signed-in admin with a fresh sign-in.
func (*Plugin) RevokeSessions ¶
RevokeSessions ends every session of a user without banning them, for a suspected stolen session.
func (*Plugin) SearchUsers ¶
func (p *Plugin) SearchUsers(ctx context.Context, in SearchUsersRequest) (UsersResult, error)
SearchUsers pages through users whose address or name holds a fragment, as the signed-in admin. It has its own rate rule (SearchRate), because a fragment search is the one admin read that can walk the user table.
func (*Plugin) SessionPredicates ¶
SessionPredicates refuses every session of a banned user. It is evaluated per lookup, so a ban with an expiry stops blocking the moment it passes.
func (*Plugin) SetAdmin ¶
SetAdmin grants or removes admin rights, as the signed-in admin; it refuses to remove the last admin.
func (*Plugin) SetRole ¶
func (p *Plugin) SetRole(ctx context.Context, in SetRoleRequest) error
SetRole replaces a user's roles, as a caller with user:set-role.
func (*Plugin) SetRoleFor ¶
SetRoleFor replaces userID's roles from Go, with no caller to check: a seed or an import. The last-admin rule still applies.
func (*Plugin) SetUserEmail ¶
func (p *Plugin) SetUserEmail(ctx context.Context, in SetEmailRequest) error
SetUserEmail gives a user an address, as the signed-in admin with a fresh sign-in; verified makes it the primary address at once.
func (*Plugin) SetUserPassword ¶
SetUserPassword sets a user's password, as the signed-in admin with a fresh sign-in. The user's sessions end: whoever holds them may not be the person the password is being set for.
func (*Plugin) StopImpersonating ¶
func (p *Plugin) StopImpersonating(ctx context.Context) (StopResult, error)
StopImpersonating ends the impersonation session in ctx and reports whether the admin's own session was re-issued (as an effect; see StopResult).
func (*Plugin) TwoFactor ¶
TwoFactor shows a user's second factor, as the signed-in admin with two-factor:get.
func (*Plugin) Unban ¶
Unban lifts a ban and clears its reason. Sessions ended by the ban stay ended; sessions made while banned start working again.
func (*Plugin) UpdateUser ¶
func (p *Plugin) UpdateUser(ctx context.Context, in UpdateUserRequest) error
UpdateUser changes a user's name or image, as the signed-in admin with a fresh sign-in.
func (*Plugin) Users ¶
func (p *Plugin) Users(ctx context.Context, in UsersRequest) (UsersResult, error)
Users lists users, newest first, as the signed-in admin.
type RevokeSessionRequest ¶
type RevokeSessionRequest struct {
UserID string `json:"userId"`
SessionID string `json:"sessionId"`
}
RevokeSessionRequest names one session of a user, as the input of revoke-session. The user id is required so a session id alone, which the user's own list shows, cannot be used against another user.
func (RevokeSessionRequest) TargetUserID ¶
func (r RevokeSessionRequest) TargetUserID() string
type RoleCatalog ¶
RoleCatalog is an optional upgrade of Authorizer: the role names it defines, which set-role accepts and AdminRoles and DefaultRole may name. An Authorizer without it accepts the built-in names (RoleAdmin, RoleUser) only, so set-role never stores a role nothing could decide on.
type Roles ¶
type Roles []string
Roles is a list of role names that decodes from a JSON string ("a,b") or array, as better-auth's set-role sends either.
func (*Roles) UnmarshalJSON ¶
UnmarshalJSON accepts a string or an array of strings.
type SearchField ¶
type SearchField string
SearchField is what search-users matches the value against. A closed set rather than a column name, for the reason UserSort is one: an admin search can never read a column the admin should not see.
const ( // SearchEmail matches the user's email addresses (auth_identity rows of // kind email), verified or not. It ignores case: addresses are stored // folded, and the value is folded the same way before it is compared. // The default when Field is empty. SearchEmail SearchField = "email" // SearchName matches the display name as stored, case included. Names // have no normalized form, and the library folds case in Go only, never // with lower() in SQL, so "ada" does not find "Ada". SearchName SearchField = "name" )
Search fields.
type SearchOperator ¶
type SearchOperator string
SearchOperator is where the value must appear in the field.
const ( // SearchContains matches the value anywhere; the default when Operator // is empty. SearchContains SearchOperator = "contains" SearchStartsWith SearchOperator = "starts_with" SearchEndsWith SearchOperator = "ends_with" )
Search operators.
type SearchUsersRequest ¶
type SearchUsersRequest struct {
Value string `json:"value"`
Field SearchField `json:"field,omitempty"`
Operator SearchOperator `json:"operator,omitempty"`
// Page, Limit, Offset, the sort and the filters are UsersRequest's.
Page int `json:"page,omitempty,string"`
Limit int `json:"limit,omitempty,string"`
Offset int `json:"offset,omitempty,string"`
Sort UserSort `json:"sort,omitempty"`
SortBy SortField `json:"sortBy,omitempty"`
SortDirection SortDirection `json:"sortDirection,omitempty"`
Admin Flag `json:"admin,omitempty"`
Banned Flag `json:"banned,omitempty"`
FilterField FilterField `json:"filterField,omitempty"`
FilterOperator FilterOperator `json:"filterOperator,omitempty"`
FilterValue *string `json:"filterValue,omitempty"`
}
SearchUsersRequest is the input of search-users: a fragment of an address or a name, and the same paging, sort and filters as UsersRequest. Every character of Value is text: %, _ and the escape character match themselves, never "anything".
type SessionsResult ¶
type SessionsResult struct {
Sessions []auth.SessionView `json:"sessions"`
}
SessionsResult is the output of sessions: another user's sessions as the user would see them, with Current always false.
type SetAdminRequest ¶
SetAdminRequest grants (Admin true) or removes admin rights. Either is a no-op when the user already has the requested state.
func (SetAdminRequest) TargetUserID ¶
func (r SetAdminRequest) TargetUserID() string
type SetEmailRequest ¶
type SetEmailRequest struct {
UserID string `json:"userId"`
Email string `json:"email"`
Verified bool `json:"verified,omitempty"`
}
SetEmailRequest is the input of set-email; see CreateUserRequest.Verified.
func (SetEmailRequest) TargetUserID ¶
func (r SetEmailRequest) TargetUserID() string
type SetPasswordRequest ¶
type SetPasswordRequest struct {
UserID string `json:"userId"`
Password auth.Token `json:"password"`
}
SetPasswordRequest is the input of set-password. The password plugin's rules apply, every session of the user ends, and open reset links are spent, exactly as for password.SetPassword from Go.
func (SetPasswordRequest) TargetUserID ¶
func (r SetPasswordRequest) TargetUserID() string
type SetRoleRequest ¶
SetRoleRequest is the input of set-role.
func (SetRoleRequest) TargetUserID ¶
func (r SetRoleRequest) TargetUserID() string
TargetUserID names the user for the audit log.
type SortDirection ¶
type SortDirection string
SortDirection is better-auth's sortDirection.
const ( SortAsc SortDirection = "asc" SortDesc SortDirection = "desc" )
Sort directions; empty is SortAsc, better-auth's default.
type SortField ¶
type SortField string
SortField is the user field the list is sorted by (better-auth's sortBy): a closed set, as UserSort is.
type StopResult ¶
StopResult is the output of stop-impersonating. Restored is true when the admin's own session was re-issued; false means the client was signed out, because the flow binding was gone or the admin's session had ended.
type TwoFactorManager ¶
type TwoFactorManager interface {
auth.Plugin
// FactorOf is userID's confirmed factor's method ("" for none, which
// includes an enrolment still waiting for its first code) and whether
// sign-in asks for it.
FactorOf(ctx context.Context, userID string) (method string, enabled bool, err error)
// DisableFor pauses the factor: sign-in stops asking, the factor and
// its recovery codes stay.
DisableFor(ctx context.Context, userID string) error
// EnableFor switches a paused factor back on.
EnableFor(ctx context.Context, userID string) error
// Reset removes the factor, its recovery codes and its trusted devices,
// so the user enrols again.
Reset(ctx context.Context, userID string) error
}
TwoFactorManager is what the admin plugin needs from a second-factor plugin to manage another user's factor: the methods `*twofactor.Plugin` already has. Found through auth.PluginOf at Init, so the admin plugin imports no second-factor package, and any implementation with these four methods is used. The methods check nobody; the admin plugin authorizes before it calls them.
type TwoFactorResult ¶
type TwoFactorResult struct {
// Enrolled is true when the user has a confirmed factor, on or paused.
Enrolled bool `json:"enrolled"`
// Enabled is true when sign-in asks for it; Enrolled and not Enabled is
// a paused factor.
Enabled bool `json:"enabled"`
// Method is the factor's method as the second-factor plugin names it
// (plugin/twofactor: "totp" or "delivered"); "" when not enrolled.
Method string `json:"method"`
}
TwoFactorResult is the output of two-factor: what a support screen shows before it pauses or resets anything.
type UpdateUserRequest ¶
type UpdateUserRequest struct {
UserID string `json:"userId"`
Name *string `json:"name,omitempty"`
Image *string `json:"image,omitempty"`
// Data is better-auth's data object: name and image (DataName,
// DataImage, overriding the fields above), and the app columns declared
// with EditableColumns. Any other key is ErrFieldNotEditable, an empty
// object ErrNoUserData; nil sets only Name and Image.
Data map[string]json.RawMessage `json:"data,omitempty"`
}
UpdateUserRequest is the input of update-user. A nil field is left as it is; the same limits as the user's own profile update apply.
func (UpdateUserRequest) TargetUserID ¶
func (r UpdateUserRequest) TargetUserID() string
type UserDetail ¶
type UserDetail struct {
User UserSummary `json:"user"`
BanReason string `json:"banReason"`
Identities []IdentityView `json:"identities"`
Sessions int `json:"sessions"`
}
UserDetail is one user with identities and live session count, for an admin deciding whether to ban or end sessions.
type UserRequest ¶
type UserRequest struct {
UserID string `json:"userId"`
}
UserRequest names a user, as the input of user, unban and revoke-sessions.
func (UserRequest) TargetUserID ¶
func (r UserRequest) TargetUserID() string
TargetUserID names the user the request is about, for the audit log (audit.Targeted). Every admin request that names a user implements it, so an event's subject is the user acted on rather than the admin acting.
type UserSort ¶
type UserSort string
UserSort orders the users list. A closed set rather than a column name, so the list can never be sorted by a column the admin should not see.
type UserSummary ¶
type UserSummary struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
Admin bool `json:"admin"`
Banned bool `json:"banned"`
// Email is the user's primary email address, or their earliest email
// identity when none is primary yet (an address awaiting verification),
// or "" when they have none. Loaded for the whole page in one query
// (withEmails), never per row.
Email string `json:"email"`
// Roles is every role the user holds by rolesOf's rules: the stored
// ones, the flag's admin role, or DefaultRole when they have none. Read
// from the same row as the rest of the summary, so it costs no query.
Roles []string `json:"roles"`
// LastActiveAt is the user's most recent session activity (better-auth's
// lastActiveAt), derived from the sessions rather than kept in a column
// that every request would write; nil when no session record is left.
// See lastActive for how far back it reaches.
LastActiveAt *time.Time `json:"lastActiveAt"`
}
UserSummary is one row of the list: only what an admin needs to pick a user, so a page of them leaks no identifiers.
type UsersRequest ¶
type UsersRequest struct {
Query string `json:"query,omitempty"`
// Page starts at 1. Strings in JSON, because a Query operation's input
// arrives as URL query values. Offset, when set, replaces Page: it is the
// number of rows to skip, for a client that pages by offset.
Page int `json:"page,omitempty,string"`
Limit int `json:"limit,omitempty,string"`
Offset int `json:"offset,omitempty,string"`
// Sort is one of UserSortValues; empty means UserSortNewest, unless
// SortBy is set.
Sort UserSort `json:"sort,omitempty"`
// SortBy and SortDirection are better-auth's sortBy and sortDirection:
// one of SortFieldValues, ascending unless SortDesc. SortBy and Sort
// together are ErrBadSort.
SortBy SortField `json:"sortBy,omitempty"`
SortDirection SortDirection `json:"sortDirection,omitempty"`
// Admin and Banned narrow the list to admins (or non-admins) and to users
// whose ban is in force (or not). Empty means no filter.
Admin Flag `json:"admin,omitempty"`
Banned Flag `json:"banned,omitempty"`
// FilterField, FilterOperator and FilterValue are better-auth's one
// field filter: the field defaults to FilterEmail and the operator to
// FilterEq; nil FilterValue filters nothing (a field or operator that
// does not exist is refused even then). in and not_in take values
// joined by FilterListSeparator; a time is RFC 3339; a flag is true or
// false.
FilterField FilterField `json:"filterField,omitempty"`
FilterOperator FilterOperator `json:"filterOperator,omitempty"`
FilterValue *string `json:"filterValue,omitempty"`
}
UsersRequest pages through users. Query, when set, is an identifier (an address, a number, a handle) or a user id, matched exactly. A fragment search is search-users (SearchUsersRequest), a separate operation with its own rate rule, because matching fragments of addresses turns an admin session into an enumeration tool and that deserves its own budget.
type UsersResult ¶
type UsersResult struct {
Users []UserSummary `json:"users"`
Total int64 `json:"total"`
Page int `json:"page"`
Limit int `json:"limit"`
// Offset is the number of rows skipped, as actually used.
Offset int `json:"offset"`
}
UsersResult is one page. Total counts every match, for a pager; Page and Limit are the values actually used after defaults and clamping.