AstroCraft — home
AstroCraft
Buy a licence
All documentation

Team, roles and invites

The roster, what each role may do, how an invite is minted and spent, and the three checks that stop a guard from being mistaken for a permission.

Last updated

/admin/team/ is admin-only. The roster is the accounts store unioned with its outstanding invites — an invited person has no account until they redeem, so their row is an invite rather than a member with a pending flag.

The three roles

Role Can
Admin Publish and approve changes · manage the team and invites · change settings and field overrides · connect and disconnect the project · everything a client editor can do
Client editor Write and edit entries · upload media and write alt text · submit changes for review · preview the site. Cannot publish, approve or manage the team
No access Nothing. Sign-in refuses at the door

The split is one line: editors may save, only admins may publish — because publishing pushes.

“No access” is a real, selectable state rather than the absence of one. Taking access away is a role change, not a deletion, so the person’s account, their history and their authorship on past commits stay intact.

A guard is not a permission

Worth understanding, because it is why some things are checked twice.

The session guard answers “is somebody signed in?” It never answers “may this person do this?” So every admin-only action re-checks the caller’s role in its own handler, behind the guard. Invite, role change, settings save and branch change all do.

The guard also re-reads your account from the store on every guarded request rather than trusting the session payload. A demotion therefore lands on the demoted person’s next request, not at their next sign-in, and one refusal covers no session, deleted account, demoted to none, and a stale session at once.

Self-demotion is refused in the handler. The screen does not offer it, but the rule lives where it cannot be skipped.

Inviting somebody

Invite mints a token carrying the address and the role, stores it server-side, mails the link, and hands it back to the modal so you can copy it. The default role in the picker is Client editor, not Admin.

The invited person opens the link, sets a password, and lands with the role you chose.

Two details that come from real failures:

  • The token is spent on POST, never on the GET that loads the page. A mail scanner following the link used to burn the invite before its recipient ever saw it.
  • The redemption trusts the token’s address, never the posted one. A read-only field is a property of one browser, not of the request.

Invites need mail configured. Without RESEND_API_KEY and MAIL_FROM, the action refuses with a sentence on the page and a line in the server log — everything else keeps working. You can still copy the link out of the modal and send it yourself.

Self-serve sign-up

/admin/signup/ is public and anyone who reaches it can create an account. That account gets No access, which sign-in refuses until an admin grants it.

This is not a limitation to work around. A self-serve sign-up that granted itself access would hand a fresh deployment to whoever loaded the page first.

Sign-up also needs mail, because confirming a mailed link is how the address is proved. Without it the screen says accounts cannot be created right now — permanently, not transiently. The CLI is the documented path for the first account either way; see Your first account.

Promoting somebody

The role picker on a member’s row. It changes the role and nothing else — deliberately not the same operation as creating an account, which would reset their password.

Promoting an address nobody has is refused: a promotion is not an invite.

Last active is a fact rather than an estimate, and it is genuinely absent until that person’s first sign-in rather than being backfilled with their creation date.

Mail

One mailbox for the whole product: MAIL_FROM, a verified sender on a domain you own in Resend. Replies to an invite go to whoever sent it.

Resend is reached with a plain fetch — no SDK, a ten-second timeout — and it never throws. Provider errors go to the server log only, because they can name the account, the sender domain and the mailbox.

Troubleshooting

An invite link says it is already used. It was spent. Mint another; tokens are single-use by design.

Somebody signed up and cannot sign in. Correct — sign-up grants No access. Promote them from this screen.

The invite modal shows a link but no mail arrived. Mail is not configured, or Resend rejected the send. Check the server log; the failure is there rather than on screen, because the message can name a mailbox. Copying the link out of the modal works regardless.

A demoted user still has access. They should lose it on their next request. If they genuinely do not, that is a bug worth reporting — sessions are not revoked, they are re-authorised per request.

You have no admin at all. Run the accounts CLI for your own address with no third argument; it defaults to admin and replaces the role along with the password.