Documentation
¶
Overview ¶
Package authcli is the command line an app gives its operators for the auth schema: apply migrations, print them as SQL, or show what is pending.
It is a package, not a binary, because only the app knows its plugin list, and the plugin list decides the migrations. The app wires it into its own main or its own CLI framework:
if len(os.Args) > 1 && os.Args[1] == "auth" {
os.Exit(authcli.Main(ctx, a, os.Args[2:], os.Stdout, os.Stderr))
}
Index ¶
- Constants
- Variables
- func Main(ctx context.Context, a *auth.Auth, args []string, stdout, stderr io.Writer) int
- func MainWithStdin(ctx context.Context, a *auth.Auth, args []string, stdin io.Reader, ...) int
- type AddressSetter
- type AdminGranter
- type AdminResult
- type Duration
- type Info
- type MigrationRef
- type OperationInfo
- type PasswordSetter
- type SessionInfo
Constants ¶
const ( //ds:def id=cmdadmin-5rk2wq8n owner=@khanakia stability=stable CmdAdmin = "admin" //ds:def id=adminsubgrant-7mt4xk2p owner=@khanakia stability=stable AdminGrant = "grant" //ds:def id=adminsubcreate-9vq3nw6r owner=@khanakia stability=stable AdminCreate = "create" )
The admin command and its subcommands: `auth admin grant` makes an existing user an admin, `auth admin create` creates one. They exist because the first admin cannot be made over HTTP (no admin exists to grant), and every app otherwise writes the same one-off job.
const ( //ds:def id=exitok-gnrts3ej owner=@khanakia stability=stable ExitOK = 0 //ds:def id=exitfailed-ssu2cn9e owner=@khanakia stability=stable ExitFailed = 1 //ds:def id=exitusage-d43sp75x owner=@khanakia stability=stable ExitUsage = 2 )
Exit codes: the library-wide convention for command lines. ExitFailed means the command ran and failed (the reason is on stderr); ExitUsage means it never ran because the arguments were wrong, so a script can tell a typo from a broken database.
const ( //ds:def id=cmdmigrate-mr367qab owner=@khanakia stability=stable CmdMigrate = "migrate" //ds:def id=cmdsql-y9ntekef owner=@khanakia stability=stable CmdSQL = "sql" //ds:def id=cmdstatus-cebt94q5 owner=@khanakia stability=stable CmdStatus = "status" //ds:def id=cmdorphans-ue59r4y3 owner=@khanakia stability=stable CmdOrphans = "orphans" // CmdCheck compares the live schema with the code: pending and changed // migrations, missing tables and columns. ExitFailed when anything is // behind, so a deploy script can gate on it. //ds:def id=cmdcheck-7hq3xw2n owner=@khanakia stability=stable CmdCheck = "check" //ds:def id=cmdsweep-nsszxzc2 owner=@khanakia stability=stable CmdSweep = "sweep" // CmdMaintain runs the plugins' scheduled jobs (auth.Maintain), such as // plugin/stale's mails to dormant accounts. Unlike sweep it can send mail and // delete accounts, so it is a command of its own and `--dry-run` shows what // it would do first. //ds:def id=cmdmaintain-4wn8qk3v owner=@khanakia stability=stable CmdMaintain = "maintain" //ds:def id=cmdinfo-7wkd3xqn owner=@khanakia stability=stable CmdInfo = "info" // CmdSecret prints a fresh random value for auth.EnvSecret, better-auth's // `secret` command. It touches no database. //ds:def id=cmdsecret-3wq8nk5v owner=@khanakia stability=stable CmdSecret = "secret" // CmdGenerate is `migrate sql` under better-auth's name (`generate`): // every migration as SQL, for a migration tool the app already runs. //ds:def id=cmdgenerate-8nd2xq4w owner=@khanakia stability=stable CmdGenerate = "generate" )
Commands: the words Main routes on, after the "auth" word the app routed on. CmdSQL, CmdStatus and CmdOrphans are subcommands of CmdMigrate.
const ( CmdClient = "client" CmdClientTS = "ts" )
CmdClient writes a client library for the app's HTTP API; CmdClientTS is its one language so far, TypeScript (authhttp.TypeScriptClient). It reads the app's own operation list, so the app's custom plugins and their error codes are typed exactly, and touches no database.
const (
FlagPasswordStdin = "password-stdin"
)
Flags of the admin subcommands. The password is never a flag: argv is visible to every process on the machine and lands in shell history, so it arrives on stdin only (FlagPasswordStdin), as `docker login --password-stdin` does.
const ModulePath = "github.com/ubgo/auth"
ModulePath is the module whose version Info reports, read from the binary's build information. An app that vendors or replaces the module sees "(devel)" or the replacement's version, which is the truth about what it runs.
const SecretBytes = auth.MinSecretSize
SecretBytes is how many random bytes `auth secret` draws: auth.MinSecretSize, printed base64-encoded, so the value is longer than the minimum and holds the full 256 bits.
Variables ¶
var ( // ErrNoAdminPlugin: the Auth has no plugin with a Grant method. ErrNoAdminPlugin = errors.New("authcli: the admin plugin is not registered (auth.WithPlugins(admin.New()))") // ErrNoAddressPlugin: no plugin can find or set an address. ErrNoAddressPlugin = errors.New("authcli: no identifier plugin can set an address (auth.WithPlugins(email.New(...)))") // ErrNoPasswordPlugin: --password-stdin with no plugin that sets passwords. ErrNoPasswordPlugin = errors.New("authcli: no plugin can set a password (auth.WithPlugins(password.New()))") // ErrNoPassword: --password-stdin given and stdin was empty. ErrNoPassword = errors.New("authcli: --password-stdin: nothing was read from stdin") )
Errors of the admin command. Each is a configuration or input problem the operator can act on; database errors pass through as they are.
Functions ¶
func Main ¶
Main runs one command and returns its exit code. args excludes the program name and the "auth" word the app routed on. Output meant for a pipe (the SQL, the status table) goes to stdout; usage and errors go to stderr. Every command but "migrate" without a subcommand, "sweep", "maintain" and "admin" is read-only. A password for `admin create --password-stdin` is read from the process's standard input; MainWithStdin takes another reader.
func MainWithStdin ¶
func MainWithStdin(ctx context.Context, a *auth.Auth, args []string, stdin io.Reader, stdout, stderr io.Writer) int
MainWithStdin is Main with an explicit standard input, for an app that embeds the command line behind its own and for tests. Only `admin create --password-stdin` reads it.
Types ¶
type AddressSetter ¶
type AddressSetter interface {
auth.Plugin
Identifiers() []auth.IdentifierKind
SetFor(ctx context.Context, userID, address string, verified bool) error
}
AddressSetter is what `auth admin` needs from the identifier plugin that owns addresses: its kind, to find a user by address, and SetFor, to give a created user a verified one. `*email.Plugin` has both. The kind is read from the plugin rather than named here, so this package never spells the email plugin's constant.
type AdminGranter ¶
AdminGranter is what `auth admin` needs from the admin plugin: the method `*admin.Plugin` has. Found through auth.PluginOf, so this package imports no plugin and an app registers nothing extra.
type AdminResult ¶
type AdminResult struct {
UserID string `json:"userId"`
Email string `json:"email"`
// Created is true when `create` made the user; `grant` leaves it false.
Created bool `json:"created"`
// Granted is true when the user is now an admin by this command.
Granted bool `json:"granted"`
// PasswordSet is true when a password was read from stdin and set.
PasswordSet bool `json:"passwordSet"`
DryRun bool `json:"dryRun"`
}
AdminResult is what `auth admin --json` prints: the user acted on and what was done. Under --dry-run nothing was done and DryRun says so, so a script never mistakes a rehearsal for the real thing.
type Duration ¶
Duration is a time.Duration that encodes as its String form ("720h0m0s") in JSON and decodes from it. Info uses it because a bare time.Duration encodes as nanoseconds, which no person reading a support request can interpret, and because the round trip must hold: what --json prints decodes back into Info.
func (Duration) MarshalText ¶
MarshalText encodes d as time.Duration's String form.
func (*Duration) UnmarshalText ¶
UnmarshalText decodes the String form; anything time.ParseDuration refuses is an error, never a zero.
type Info ¶
type Info struct {
Version string `json:"version"`
Engine string `json:"engine"`
BaseURL string `json:"baseUrl"`
TrustedOrigins []string `json:"trustedOrigins"`
FreshWindow Duration `json:"freshWindow"`
Session SessionInfo `json:"session"`
Plugins []string `json:"plugins"`
Identifiers []string `json:"identifiers"`
Operations []OperationInfo `json:"operations"`
Disabled []auth.EndpointID `json:"disabled"`
Migrations []MigrationRef `json:"migrations"`
}
Info is what "auth info" prints: the wiring of this instance, for a support request or a deployment check. It holds no secret and no user data; the trusted origins and the base URL are the deployment's public addresses.
type MigrationRef ¶
MigrationRef names one migration in Info, without its statements.
type OperationInfo ¶
type OperationInfo struct {
ID auth.EndpointID `json:"id"`
Kind auth.OpKind `json:"kind"`
Require auth.Requirement `json:"require"`
Route string `json:"route"`
}
OperationInfo is one route in Info.
type PasswordSetter ¶
type PasswordSetter interface {
auth.Plugin
SetPassword(ctx context.Context, userID string, password auth.Token) error
}
PasswordSetter is what `auth admin create --password-stdin` needs from a password plugin: the method `*password.Plugin` has, found through auth.PluginOf, so the core module never imports the password module.
type SessionInfo ¶
type SessionInfo struct {
IdleTimeout Duration `json:"idleTimeout"`
MaxLifetime Duration `json:"maxLifetime"`
RefreshAfter Duration `json:"refreshAfter"`
TransientIdleTimeout Duration `json:"transientIdleTimeout"`
}
SessionInfo is auth.SessionConfig as Info reports it: the same four lifetimes, with JSON names and readable durations. auth.SessionConfig has no JSON tags because it is configuration, not a wire format; this is the wire format, so a rename in the core cannot silently change the output.