Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting started: bringing your people in

A site is run by a handful of people: whoever answers for each question it asks, and whoever works each desk. This is how they get in - from an empty Kanidm to everyone signed in at their own desk - without anyone handing out passwords or running an admin tool per person.

The idea in one line: the content says who is involved, and portal makes their accounts. A page names who is responsible for it; a directory is gated on a group; a desk may invite into groups. Portal reads that and does the rest at start.

What you need

  • A content repo, and a portal instance running it (Operations has adding a site).
  • A Kanidm of the site’s own. Group names below are exactly the ones the content uses, with no prefix; that is only safe when the Kanidm serves this site alone. (Or no Kanidm at all: with PEOPLE_BACKEND=memberships portal keeps the groups itself and any OIDC provider that can say who someone is will do; see Memberships. The rest of this page is the Kanidm way.)
  • Mail: mail: { from } in site.yaml, and gdo on the host. Without it accounts are still made, but nobody hears about them.

1. Say who is involved, in the content

Three things in the content decide who needs an account:

contentwhowhat they get
responsible: { name, contact } on a pagethe person answering for that questionan account, and a mail saying they are listed as responsible for asking it
responsible: { name, contact, group }a group answering for itnothing: the inbox is not a person; the group’s members have the desk, and each of them may suggest changes to the page
qualifies: board on a page or _section.yamlmembers of that groupthe pages and desks under it
invite: { group: treasurer } on a desk pagethe desk’s own groupthe right to bring people into treasurer

The desk whose group may invite into the most groups is the site’s most privileged one; for an association that is the board. Portal works that out from the content (see step 4).

2. Set up Kanidm for portal

Portal needs four things from Kanidm. The tomtervel/infrastructure repo’s kanidm-setup.sh does all of them and is a good starting point; by hand, as idm_admin:

K="kanidm -D idm_admin -H https://id.example.no"

# An onboarding account: may create people and issue a first password
# link, nothing more. pii_read lets an invite find someone who already
# has an account by their email instead of making a second one.
$K service-account create portal-onboarding "Portal onboarding" idm_admin
$K group add-members idm_people_on_boarding portal-onboarding
$K group add-members idm_people_pii_read portal-onboarding

# One group per `qualifies:` in the content, managed by that account:
# adding a member to a group takes the right to manage it.
for g in board treasurer traffic; do
  $K group create "$g" portal-onboarding
done

# The sign-in client. Everyone in this Kanidm may sign in; what they
# see comes from their groups, in the `groups` claim.
$K system oauth2 create portal "My site" https://site.example.no
$K system oauth2 add-redirect-url portal https://site.example.no/auth/callback
$K system oauth2 update-scope-map portal idm_all_persons openid profile email
for g in board treasurer traffic; do
  $K system oauth2 update-claim-map portal groups "$g" "$g"
done
$K system oauth2 update-claim-map-join portal groups array

# The onboarding token, read-write, shown once: into portal's env.
$K service-account api-token generate portal-onboarding portal --readwrite

Then portal’s environment:

variablevalue
KANIDM_URLhttps://id.example.no
OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRETthe client above (show-basic-secret)
KANIDM_API_TOKENthe onboarding token

3. Name the first person

Someone has to be first: every later person is invited from a desk, and on an empty Kanidm there is no desk anyone can open yet.

variablevalue
SEED_ADMIN_EMAILthe first person’s address
SEED_ADMIN_NAMEtheir name, as it should appear (optional)
SEED_GROUPa group to put them in, if not the most privileged one (optional)
RESPONSIBLE_GROUPa group every responsible person joins, e.g. editors (optional)

4. Start portal

At start, portal:

  1. Invites the first person into SEED_GROUP, or the most privileged group, and mails them the one-time link. Once: it is remembered in the portal_seed bucket, so restarts and upgrades never send a second link.
  2. Makes an account for everyone named responsible who has none, and mails them that they are listed as responsible for asking those questions, with their link. With RESPONSIBLE_GROUP they all join it; someone who already had an account is added without a mail.

The two run side by side. Kanidm may still be starting beside portal, so each is tried again a minute apart, up to twenty times. The log says what happened, per person.

The second happens again whenever the content changes, so a new page naming a new person sets them up without a restart. And it works both ways: someone the content has stopped naming leaves RESPONSIBLE_GROUP in the same pass, and with it whatever else that group opens. Only if portal is who put them there - a person who was in the group before is left in it - and their account stays, since it is theirs.

5. The rest come in from the desks

From here nobody touches Kanidm:

  • At a desk, whoever may invite fills in a name and an address. Portal makes the account, adds them to the group, and mails the link.
  • A decision can be the invitation. A state with grants: group in aggregates.yaml puts the person a case is about into that group when the case arrives there - an approved committee application that is the membership.
  • A new page with a new responsible person gets them an account when the page goes live.

An invite that nobody used

A link lives a day. Every invite - a desk’s, and the ones portal made at start - is a case in portal_invites, and keeps itself current:

statemeanshow it gets there
openthe link is waitingthe invite was sent, or sent again
donethe person is inthey signed in to the site; or a desk says so
expiredthe link ran out unuseda sweep, every ten minutes

A desk that lists the bucket offers the way back with a transition from expired to open. Pressing it makes a fresh link and mails it:

resource:
  source: { kind: kv, bucket: portal_invites }
  requires_group: board
  transitions:
    - { from: open, to: done, label: Is in }
    - { from: expired, to: open, label: Send again }
    - { from: expired, to: done, label: Is in }

6. What the people who ask the questions can do

Signed in, a person a page names as responsible sees a card at the foot of their own pages to suggest a change. The server checks it is really them, and the suggestion is mailed to the site’s inbox with them as reply-to, so answering it answers them.

It is also a case, in the built-in portal_edit_suggestions bucket, so it does not depend on somebody reading that inbox. Give a desk the list, and whoever sits there takes it up and settles it:

resource:
  source: { kind: kv, bucket: portal_edit_suggestions }
  requires_group: board
  transitions:
    - { from: open, to: discussed, label: Looking at it }
    - { from: open, to: done, label: Changed }
    - { from: open, to: declined, label: Will not change }
    - { from: discussed, to: done, label: Changed }
    - { from: discussed, to: declined, label: Will not change }

The person who suggested it is mailed at each move, in the site’s language, and the card on their page lists what they have sent and where each one stands. A content repo that declares the bucket itself in aggregates.yaml, with a mail: on a state, sends that instead.

Their group can also be the way into other tools. The vel’s editors group signs in to the project Gitea with the same account: a second OAuth2 client scoped to that group alone.

When it does not work

what you seewhy
an invite fails with 403 setting maila portal before 0.5.4 set the address in a second call; the onboarding account may create but not modify
an invite fails with 404 adding to a groupthe group is not managed by portal-onboarding: group set-entry-manager <group> portal-onboarding
Item not found setting the login page’s name or logothose are system settings only admin may change, not idm_admin - or the CLI and server versions differ
accounts are made but nobody is mailedsite.yaml has no mail.from, or gdo is not running on the host
someone responsible never got a mailthey already had an account (they are only added to RESPONSIBLE_GROUP), or their contact is not an email address
an invite stays open though the person has set a passkeyKanidm does not show a passkey to the onboarding account; it becomes done when they sign in to the site
“send again” is not on the deskthe list has no { from: expired, to: open } transition, or the content declares portal_invites itself without an expired state
someone left a page and is still in RESPONSIBLE_GROUPthey were in the group before portal added them, so it is not portal’s to undo
the first person was invited twiceit cannot be, per address and group; changing SEED_ADMIN_EMAIL or SEED_GROUP invites the new pair