Account commands
gonsole/auth
gives your program seven account commands over the accounts that the
authkit/postgres store keeps. With
them you create the first admin of a fresh database, and change a
role or disable an account from a shell. Add both modules, at
versions that work together:
go get github.com/gopherium/framework/gonsole@v0.6.0go get github.com/gopherium/framework/gonsole/auth@v0.4.0The package is named auth, like the authkit value in the
Quickstart. Import it as accounts to keep
the two apart:
import accounts "github.com/gopherium/framework/gonsole/auth"
func roles(context.Context, gonsole.Call) (accounts.Roles, error) { return accounts.Roles{ Known: []string{"admin", "editor"}, Privileged: []string{"admin"}, Capabilities: map[string][]string{ "admin": {"manage_users", "manage_reports"}, "editor": {"manage_reports"}, }, }, nil}
func program(getenv func(string) string) gonsole.Program { cfg := accounts.Config{ Roles: roles, Capability: "manage_users", RecordTimeout: 5 * time.Second, RecordsLimit: 50, } return gonsole.Program{ Name: "myapp", Version: "1.4.0", Env: gonsole.Env{Prefix: "MYAPP_", Getenv: getenv}, Database: "DATABASE_URL", Serve: serve, Migrations: []gonsole.Step{ accounts.Migration(), accounts.RecordMigration(), {Name: "reports", Run: migrateReports}, }, Commands: append( []gonsole.Command{createReport(), listReports, accounts.Records(cfg)}, accounts.Commands(cfg)..., ), Authorize: accounts.Authorize(cfg), Record: accounts.Record(cfg), }}Migration creates the auth schema, the tables the accounts live
in. RecordMigration creates the gonsole schema, which holds the
records of who changed what and its own list of applied migrations.
Both go before your own
schema steps.
RecordMigration runs on goose, a migration tool, and takes goose’s
lock first. So two migrate runs never apply it at once. Leave
Program.Lock unset, since one that takes the same lock would block
this step.
Roles is required. Without it, every command but account:list
and account:records panics. It is a function that returns two lists
and a map. Known holds every role an account may hold. Privileged
holds the roles that at least one enabled account must always keep.
Capabilities maps each role to the permissions it carries. A role
left out carries none. The commands call it on each run, so it can
include the roles your plugins add.
Commands returns six of the commands below. Records returns the
seventh, account:records.
The seven commands
Section titled “The seven commands”| Command | What it does | Writes |
|---|---|---|
account:create-admin -email <address> -name <name> -role <role> |
creates an account under a role | at once |
account:grant-role -role <role> |
gives the role to every account holding none | dry run until -yes |
account:list |
lists every account and its role, offers -json |
never |
account:role <email> <role> |
sets one account’s role | dry run until -yes |
account:disable <email> |
disables one account and deletes its sessions | dry run until -yes |
account:enable <email> |
enables one disabled account | dry run until -yes |
account:records |
lists who changed what, newest first, offers -json |
never |
A dry run
misses one error. A change that would leave no enabled account under
a privileged role passes the dry run. With -yes it fails with
<email> is the last enabled privileged account.
account:create-admin is the command for an empty database. It
runs your Migrations itself and needs neither -yes nor -as.
It prints a Password: prompt, reads the password as one line on
stdin, then prints created user <email>. The password needs at
least 12 characters. A terminal shows the password as you type it,
so pipe it in:
printf '%s\n' "$PASSWORD" | myapp account:create-admin \ -email maria.perez@example.com -name "Maria Perez" -role adminaccount:grant-role -yes also runs your Migrations before it
writes. The other commands expect them to have run, so on a new
database run myapp migrate first.
account:create-admin and account:grant-role stop at once when a
flag they need is missing:
$ myapp account:grant-role -as maria.perez@example.commyapp: account:grant-role wants -role <role>account:create-admin needs -email, -name and -role, and
account:grant-role needs -role. A line that leaves one out, or
leaves it empty or spaces only, exits 2 and prints the help page. It
stops before any schema step or account check.
For demo data, call accounts.EnsureAccounts from your
Program.Seed. It takes a store, such as
authkitpg.NewUserStore(pool) over a pool opened on
call.DatabaseURL(), the accounts, and a writer for its created
and kept lines. It creates the missing accounts and keeps the
rest. A kept account that holds no role gets the one you list.
Programs without gonsole use RunCreateAdmin and RunGrantRole
from authkit/postgres, as
User administration shows.
An acting account
Section titled “An acting account”Set Capability in accounts.Config to a permission, such as
manage_users. The four commands that change existing accounts then
want -as <email>, and your program needs
Authorize and Record.
Leave either one out and every run fails, even version. The module
ships both.
accounts.Authorize checks the acting account before the command
runs, dry runs included. A blank -as exits 2. It refuses these
with exit 1:
- an address no account holds
- an account that is disabled or was never activated
- an account without a role, or whose role lacks the permission
- a database without the records table, with an error that says to
run
migratefirst
It checks your own commands too. So list their permissions in
Capabilities as well, such as manage_reports.
The four commands also refuse three changes. A run that tries one exits 1, on dry runs too, and leaves no record:
- giving a role, or changing an account under a role, when that role carries a permission the acting account’s role lacks
- the acting account disabling itself
- the acting account changing its own role
Say you add a support role that carries only manage_users. An
account under support cannot give editor or admin, or change
an account under them, since both carry manage_reports:
$ myapp account:role maria.perez@example.com editor -as support@example.commyapp: the role editor carries manage_reports, which the account support@example.com lacksAny acting account may give a role left out of Capabilities. Give
the role that manages accounts every permission the other roles
carry, as admin does here. Otherwise no account command can give a
role that carries a permission the managing role lacks, or change an
account under it.
The four commands look the acting account up themselves, so -as
must name an account even under an Authorize of your own.
account:create-admin checks none of this, so a fresh database can
always get its first admin. The
admin HTTP handlers also
refuse disabling your own account and changing your own role.
accounts.Record stores one record for each applied change: the
acting address, its account id, the command, and the arguments and
flags as typed. account:records lists the latest records. A value
that is empty or holds a space or a quote prints in double quotes.
Two settings tune them. When one is empty, its fallback in Config
applies:
| Setting | Fallback | Sets |
|---|---|---|
MYAPP_COMMAND_RECORD_TIMEOUT |
RecordTimeout |
how long storing one record may take |
MYAPP_COMMAND_RECORDS_LIMIT |
RecordsLimit |
how many records account:records lists |
Give both fallbacks a value above zero. A run that falls back to
zero fails. Authorize reads the record timeout too, so a bad one
stops the run before anything changes. -limit on account:records
sets the limit for one run. Call cfg.Validate(call.Env) from your
Program.Validate, so
myapp check reads both settings.