Lolly
Abrir o app

Invite people and handle requests

Nota: Lolly Work

This guide covers the optional organisation service. Configuration, console and API paths refer to your Work instance. The packaged console keeps its release-matched copy of this guide.

This page is for admins and project managers who bring people into Lolly Work, and for anyone who answers a request to join or a request for access. Work sends no email: it gives you a personal link and a ready-to-send message, and you send them yourself by chat or email.

  • Invite an address. From the console's People view for the whole workspace, or from a project's People panel in Lolly.
  • Send the link. Copy the invite link, or the ready-to-send message, and send it yourself.
  • They open the link. The page says who invited them and to what. It also shows the address to sign in with.
  • They sign in with that address. Work accepts the invitation and opens the project, or the workspace.

Two ways to invite

Workspace invitationProject invite
WhereInvite people on the console's People view, lw invite add, or POST /api/v1/invitations (API)People with access in Lolly's Share dialog or Team projects view, or POST /api/v1/projects/:id/invite
Whoholders of user.invite (admins and owners by default)managers of the project, within the invite policy
Givesadmission, plus optional local groupsadmission, plus a role on the project
An address that already has an accountjoins the ticked groups at once (status: "applied")becomes a project member at once (added)

Both write one invitation per address. An address has at most one active invitation, and every project that invites it is added to that same invitation.

Which account holds an address. Work treats an address as an account's only when the account has shown it holds the address: a sign-in whose provider verified it, a source the operator vouches for (the reverse proxy, or a provider set to emailVerification: "trusted"), or an accepted invitation for that address. A project invite also counts an account provisioned with no sign-in yet. An account whose sign-in only claimed the address is treated as if the address had no account: it gets an invitation, which a verified sign-in has to accept.

Invite people to the workspace

An invitation identifies one address, stored lowercased, plus optional local groups and an optional end date of at most 366 days. Inviting an address that already has an active invitation returns that invitation unchanged, so to change its groups or end date, revoke it and invite again.

At each sign-in, while idp.admission.invitations is not false, the server looks up the active invitation for the email:

Attaching groups to an invitation assigns groups, so it carries the same controls as editing a person's local groups:

Erasing an account deletes the invitation it accepted, and any other invitation for that address when no other account carries it, so nothing keeps admitting the erased address.

Invite people to a project

In Lolly, People with access lists the people on a project and Invite by email adds more. The request is POST /api/v1/projects/:id/invite with {"emails": [...], "role": "viewer" | "commenter" | "editor" | "manager", "passwordSetup": false}, from a manager of the project. Each address gets one result:

ResultMeaning
addedThe address belongs to an account, which becomes a member now and finds "name shared project with you" in their inbox.
invitedThe address has no account yet. An invitation carrying the project is created, or the address's open invitation gains the project. The result carries invitationId, the address's own invite link for this project (link) and the end date (expiresAt).
alreadyNothing to do: the person already has this role or a higher one, or owns the project.
refusedWith a reason, listed below.

For invited, at their first sign-in the person joins every project the invitation carries, each only while the person who added it is still an enabled account that manages that project and may still invite people, and the role is still one the policy gives. An entry that fails is skipped and audited (invite.project.skip), so removing or offboarding a manager also stops the invitations they had pending.

A role is raised by inviting again with a higher one, and only lowered on purpose through the members route (Team projects).

Refusal reasons
  • invalid-email.
  • account-disabled, shown only to a caller who holds user.invite.
  • invites-not-allowed: the invite policy does not let you invite new people.
  • domain-not-allowed.
  • invitations-off: idp.admission.invitations is false, so sign-in would never read an invitation.
  • invitation-accepted: the address's invitation was already used. Shown only to a caller who holds user.invite; everyone else reads unavailable.
  • invitation-changed: someone else changed the invitation at the same moment; try again.

Which addresses have an account is directory knowledge, which members do not otherwise get. A caller without user.invite is never told that an account is disabled: that address is handled as if it had no account. Such a caller may send at most 100 addresses an hour (429 RATE_LIMITED), counted per server process. Sharing still shows them who already has an account, because that person appears on the project; the hourly limit stops this being run over a list.

The inbox message goes out once, when the person is first added. Raising their role sends nothing, and removing and re-adding them never brings back a message they dismissed. Each person may send at most 200 of these messages a day; past that the person is still added, and the audit row records the message as held.

The message to send. The response carries link, the project's address in Lolly (<baseUrl>/#/team/project/<projectId>, or <appUrl>/... when instance.appUrl is set), and message: the workspace name, the inviter's name, the sign-ins on offer and instance.inviteNote. Each invited address gets Copy invite message in the People panel: the inviter, the project and the role, the invite link on its own line, which sign-ins to use ("Sign in as … with Google or GitHub."), "Open the link to set your password." when the link may set one, the end date and the invite note. Someone who is already a member can use the plain project link.

Pending invitations. Managers see the project's pending invitations in the People panel, each with its end date, whether it was opened and who invited the person, and the actions Copy link, Copy message, New link and Revoke. An invitation that expired in the last 30 days stays listed as Expired with Invite again.

Withdrawing. DELETE /api/v1/projects/:id/invitations/:invitationId takes the project off the invitation. When a project invite created the invitation (createdVia: "project") and no project is left on it, the invitation is revoked too, so the address can no longer sign in through that invitation. An invitation made in the console only loses the project: whether it still admits the person is an admin's decision in People. An invitation accepted in the meantime is never revoked by this route.

No one is listed twice. When a person becomes a member or the owner of a project, by an invite, by accepting an invitation for another of their addresses or by a transfer, the project comes off any pending invitation for an address they hold. A project-made invitation left with no projects and no groups is revoked. Both are audited (invite.project.remove, invite.revoke, with via: "membership"). Two invitations stay open with the project gone: one made in the console, and one for an account that has not signed in yet, which may be how that person gets in the first time.

A person can also come to hold an invited address without accepting anything, for example by adding a sign-in for that address from their profile. The members list leaves such an invitation out only while accepting it would change nothing. When it carries a higher role than the person has on the project, it stays listed, because their next sign-in with that address raises them to that role. A manager who does not want that withdraws the invitation.

Every pending invitation has a personal link, <baseUrl>/l/invite/<token>, and each project on the invitation has a link of its own. Admins copy the workspace link from the console's Invitations table (Copy link, or Copy message). Managers copy a project's link from that project's People panel in Lolly.

The link identifies the invitation and never admits anyone by itself: the person still signs in, with the invited address. The token is signed, not stored (the API), so every inviter who added a project to an invitation can copy a working link at any time.

The invite page. Opening a link shows a page with no script. It says who invited the person, to which project and with which role, which address to use and when the invitation ends:

The page never contains the full invited address, the inviter's email, the project name in the <title> (always "Invitation to" and the instance name) or og: and twitter: preview tags, so a link pasted into a chat previews as nothing in particular. Reading the page changes nothing: a link preview or a mail scanner that fetches it does not mark the invitation opened. It is marked opened when someone presses a sign-in button.

StateStatusThe page
Pending, signed out200the invite page above
Pending, signed in with the invited address200"You are signed in as …" and Join Brand refresh (Accept invitation for a workspace link), which accepts and opens the project
Pending, signed in as another account200"You are signed in as another account", sign-in buttons that ask for an account, and Use this account instead?
Accepted by the reader200"You are already in" and Open Brand refresh
Accepted by someone else, or signed out200"This invitation was already used", with Sign in
Ended (pending past its end date)410"This invitation has ended. … Ask Ana for a new link."
Unknown, malformed, revoked or replaced410"This link no longer works", the same page for every cause

Signing in from the page. A sign-in button posts to POST /api/auth/invite. The server records the first opening (invite.open), then starts that sign-in with the invitation carried in the signed state the provider hands back; the token itself never goes to the provider. An OpenID Connect provider also gets login_hint with the invited address, so its account picker offers the right account first. GitHub takes no hint, and the email and password form says which address to use without filling it in. When the sign-in comes back:

If the invitation was revoked, replaced or accepted by someone else while the person was at the provider, the sign-in finishes as a plain one and opens /.

For people who will sign in with email and password, an admin or owner can let the invite link set the password, so they send one link instead of an invitation and a password link. The tick is Can set a password on Invite people in the console and on Lolly's invite form. It is offered only to an admin or owner while a password entry is configured, and it starts ticked when every address on the invite is at a domain in policy.invites.passwordDomains.

The invite page then shows Set a password first. Pressing it works only when all of these hold, checked at that moment:

The server then issues a one-time password link valid for an hour (audited as auth.password.link.issue with via: "invitation") and shows the set-password form, including the optional name. Setting the password signs the person in and opens the project. If any check fails, the invite page shows "This invitation can no longer set a password. Sign in another way, or ask Ana for a sign-in link."

Aviso: Send a password-setting link privately

Until a password is set, whoever holds the link can set it for the address. Send it to the person directly, not in a shared channel. Once a password exists, the link offers only the ordinary sign-ins.

New link (in the console, or on a pending row in a project's People panel) raises the invitation's link version. Every link copied before, from any project and from the console, then shows "This link no longer works", and the invitation's "Opened" mark is cleared. Use it when a link went to the wrong place. Each invitation gets at most 10 new links a day. The project route is POST /api/v1/projects/:id/invitations/:invitationId/link.

Invite again is for an expired or revoked invitation. It writes a new invitation for the same address, with the same projects (and in the console the same groups) and a new link. The console needs user.invite and the usual group controls; a project needs a manager who may invite new people (POST /api/v1/projects/:id/invitations/:invitationId/reinvite).

GitHub addresses

GitHub accounts often carry several verified addresses, such as a personal primary address and a work address. For invitations, and only for invitations, a GitHub sign-in counts every verified address on the account (up to 10, never the users.noreply.github.com commit address). A person invited at their work address can therefore sign in with a personal GitHub account that has the work address verified on it, and the console shows "Accepted as" the primary address.

Everything else still uses the primary address alone: the emails and domains lists, the address stored on the account, and joining an existing person by email. A secondary address at a listed domain does not admit anyone. An account admitted through a secondary address stays admitted, device sign-ins included, while the invitation it accepted is not revoked.

Invite policy

policy.invites decides who may invite new people and on what terms. The keys and their defaults are in instance configuration:

The tier and the domain list apply to every invitation that lets a new person in: from a project, from the console and from lw invite add. Sharing a project with someone who already has an account, or giving their account groups in the console, needs manager on the project (or the console's own checks) and an allowed role.

Lolly reads can['user.invite'] and invites (domains, maxTtlHours, projectRoles, passwordSetup and passwordDomains) from GET /api/v1/org-config, so it offers only what the server accepts. Older servers send neither, and Lolly then hides inviting. The policy is part of the instance configuration, so changing it means editing the configuration and redeploying.

Requests

People can ask for a way in. Every request reaches the right people as an inbox notice, and the person asking learns nothing about the workspace or project until someone approves.

RequestWho can file oneWho answersSwitch
Ask to joinsomeone who signed in but is not invitedeveryone who may invite new peoplepolicy.requests.join (off by default)
Use another accountsomeone who opened an invite link with a different accounteveryone who may invite new people, and the link project's managers when the person already has an accountfollows the invitation, so always on
Ask for accessa member who opened a project or session they cannot see, or a viewer who wants to editthe project's owner and managerspolicy.requests.project (on by default)

Requests expire after policy.requests.ttlDays (14 days by default), and so do their notices.

Asking to join

A person who signed in but is not invited sees the refusal page ("Example Studio is invite only"). With policy.requests.join on, the page also offers Ask to join: "Ask the admins of Example Studio to let sam.k@example.net in.", with an optional note of up to 280 characters. The request carries the address the sign-in just verified, never a typed one. With it off, the page says to ask the person who invited them to invite this address. Other refusals (a disabled account, an unverified email, a hostedDomain or tenantId mismatch) never offer the request.

While people email is off, the "Request sent" page says "Example Studio does not send email yet. Sign in again later: once an admin approves, you are in."

Using another account

An invitation is for the invited address only. When someone opens an invite link and signs in with another account, the wrong-account page lets them ask: "Ana decides whether sam.k@example.net can use this invitation." The signed-in page asks "Use this account instead?", shown when the reader is not the inviter and has less than the invitation gives.

This files a request of kind switch. It goes to the admins who may invite new people and, when the person already has an account, also to the managers of the link's project. Their notice carries a warning: "Someone with the invitation for an•••@… signed in as … Approve only if you know the address belongs to them."

Asking for access

Someone who opens a project or session link they cannot open sees "You do not have access to this team project." and can Ask for access: they choose Can view or Can edit and may add a note of up to 280 characters. A viewer can Ask to edit the same way, from Share, Team and from the People panel. Both file a request of kind project, with POST /api/v1/projects/:id/access-requests or, from a session link, POST /api/v1/sessions/:id/access-requests (the API).

Filing never tells the requester anything about the project. The answer is the same 202 whether the project exists, is archived, the person already has that access, a request is already open or requests are switched off. The requester never learns who manages a project until someone approves.

Who answers. The project's owner and its managers, when at least one of them is an enabled account. Otherwise, everyone who manages the project through project.manage (admins and owners). They get an inbox notice, "Sam asks to edit Brand refresh", and see the request under Asking for access in the project's People panel. The approver is checked again on every list and answer, against the project stored on the request.

Notices

Every step above reaches people through their inbox. The server writes these notices in one place (server/src/notify/people.ts), in English, naming people by name and never by email to anyone who could not already see the address.

data.kindGoes toTitle
access-requestthe people who may answer it"Sam asks to edit Brand refresh", "… asks to view …", "Sam asks to join Example Studio", or "Sam asks to use their own account for an invitation", with the address, the sign-in and the note in the body, and Review
access-answerthe requester"Andy gave you edit access to Brand refresh" with Open, or "Your request for Brand refresh was not approved"
invite-acceptedeach person who invited them (at most 5)"Sam accepted your invitation", with Open (the project) or Open People (the console)
invite-skippedthe invitee"Your invitation to Brand refresh no longer works", when the person who added the project can no longer add people to it, or the project was archived
welcomethe invitee, on accepting"Welcome to Example Studio", with the inviter and, for a project, its name, the role and Open
comment-mentiona person mentioned in a comment"Ana mentioned you in Spring poster", with the start of the newest message and Open thread
comment-replythe people in a comment thread when someone replies"Ana replied in Spring poster", or "New replies in Spring poster: 3", with Open thread

Comment notices (kind: "comment", id cn_…) work differently. There is one per person per thread, updated as replies arrive. It stores no text and no names: the inbox writes the title, the excerpt and participant labels each time it is read, so an edited or deleted message never lingers there. It is shown only while the person can still open the document and read its comments. Dismissing it removes it until the next reply, and reading the thread in Lolly removes it too. The API reference has the details.

How Lolly shows them. GET /api/v1/inbox answers with an ETag and an unread count, and 304 when nothing changed. Lolly checks it when the tab comes back into view (at most once a minute) and every 60 seconds while it is visible. The banner shows one message at a time, and the next after a dismiss. View all opens the inbox, where project requests have Approve (with a role) and Decline. Requests to join and to use another account are answered in the console.

Notices are not emailed yet. notify.people.email is the switch for that, off by default, and this release sends no people email even with it on (emailing people notices). Until then, copy invite messages and links and send them yourself.

Audit actions for invitations and requests
  • invite.create: payload email, groups, expiresAt, via (console, project, request or reinvite) and passwordSetup, plus from for an invitation made again.
  • invite.link: a new link; payload email and version.
  • invite.open: actor anonymous, the first sign-in started from a link; payload provider and idp.
  • invite.wrong-account: payload the signed-in email, provider, idp and admitted.
  • invite.revoke: payload email and was, the status before revoking.
  • invite.accept: actor the new member; payload provider, idp, email, groups and via (sign-in, join or link). It adds createdGroups when the sign-in created local groups, or groupsNotApplied when the account came before the invitation or is the inviter's own.
  • invite.project.skip, invite.project.remove.
  • access.request, access.request.held, access.approve, access.decline, access.withdraw and access.supersede. A request's note is recorded only as its length.

Each invite. row has subject invitation:<id>. The full list of access. rows is in the API.