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=membershipsportal 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 }insite.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:
| content | who | what they get |
|---|---|---|
responsible: { name, contact } on a page | the person answering for that question | an account, and a mail saying they are listed as responsible for asking it |
responsible: { name, contact, group } | a group answering for it | nothing: 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.yaml | members of that group | the pages and desks under it |
invite: { group: treasurer } on a desk page | the desk’s own group | the 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:
| variable | value |
|---|---|
KANIDM_URL | https://id.example.no |
OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET | the client above (show-basic-secret) |
KANIDM_API_TOKEN | the 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.
| variable | value |
|---|---|
SEED_ADMIN_EMAIL | the first person’s address |
SEED_ADMIN_NAME | their name, as it should appear (optional) |
SEED_GROUP | a group to put them in, if not the most privileged one (optional) |
RESPONSIBLE_GROUP | a group every responsible person joins, e.g. editors (optional) |
4. Start portal
At start, portal:
- Invites the first person into
SEED_GROUP, or the most privileged group, and mails them the one-time link. Once: it is remembered in theportal_seedbucket, so restarts and upgrades never send a second link. - 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_GROUPthey 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: groupinaggregates.yamlputs 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:
| state | means | how it gets there |
|---|---|---|
open | the link is waiting | the invite was sent, or sent again |
done | the person is in | they signed in to the site; or a desk says so |
expired | the link ran out unused | a 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 see | why |
|---|---|
an invite fails with 403 setting mail | a 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 group | the group is not managed by portal-onboarding: group set-entry-manager <group> portal-onboarding |
Item not found setting the login page’s name or logo | those are system settings only admin may change, not idm_admin - or the CLI and server versions differ |
| accounts are made but nobody is mailed | site.yaml has no mail.from, or gdo is not running on the host |
| someone responsible never got a mail | they 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 passkey | Kanidm 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 desk | the 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_GROUP | they were in the group before portal added them, so it is not portal’s to undo |
| the first person was invited twice | it cannot be, per address and group; changing SEED_ADMIN_EMAIL or SEED_GROUP invites the new pair |