Invites
ARGUS has two distinct invite flows. Both use signed, single-use tokens and both land on the
same /invite route, but they differ in scope and lifetime.
The two kinds of invite
Org invites — full accounts
An org invite adds a user with a real OrgRole (admin, manager, operator,
observer). Accepted invites create a permanent membership at
orgs/{orgId}/members/{uid}.
- Who sends: anyone with
admin.users(rolessuperadmin,admin,manager). - Lifetime: 7 days default, configurable per-invite.
- From: Admin → Users (
/admin/users).
Mission-guest invites
A short-lived link giving someone access to one operation only — no org membership, no other missions, no admin surfaces. Useful for sharing an op with an outside agency for a few hours.
- Who sends: mission owner or org admin.
- Lifetime: 1 h / 12 h / 24 h / 7 d.
- From: Mission detail → Share (
/operations/:id). - Access: read-only by default; optional “can annotate” / “can post on PTT” toggles.
Guests sign in or register for that one op; when the link expires or is revoked they lose access immediately.
Sending an org invite
- Go to
/admin/users, click Invite user. - Fill in Email, Role (built-in or custom if your plan supports it), optional expires in (default 7 days), optional message.
- Click Send. The server creates an
OrgInvitation+InviteTokenDocwith the opaque token, then sends the email.
The pending invite appears in the users list with a grey “Pending” badge — from there you can Copy link, Resend, or Revoke.
Sending a mission-guest invite
- Open the mission at
/operations/:id. - Click Share.
- Pick As guest (vs. adding an existing user as a participant).
- Enter email, set expiry, pick a permission preset, click Send.
The mission document gets a new entry in its guestInvites subcollection and the email
goes out with the same /invite?token=... URL shape — the token carries whether it’s an
org invite or a mission-guest invite.
Redeeming an invite
The invite email contains a link like https://app.argus.tactical/invite?token=<opaque>.
Opening it routes to InviteComponent at /invite, which:
- Verifies the token. States:
loading→ready/invalid/expired/revoked/accepted. - Displays the offer — inviter, org name, role, and expiry date.
- Branches on auth:
- Already signed in — shows Accept invitation.
InvitationService.acceptInvite()runs the server transaction, creates the membership, and lands you on the dashboard. - Not signed in — mini auth form pre-filled with the invite email. Toggle between
Sign in and Create account; submitting authenticates and accepts in one step
(
loginAndAccept()/registerAndAccept()).
- Already signed in — shows Accept invitation.
- The “Welcome to {org}” state renders with an Enter Dashboard button.
What can go wrong
- Invalid — token doesn’t exist / is malformed.
- Expired — past
expiresOn. - Revoked — sender revoked it before you accepted.
- Already accepted — you’ve used it before.
- Error — server-side failure during
acceptInvite; the component offers Try again.
The email on the invite is authoritative — accepting while signed in as a different user is rejected.
Revoking an invite
From /admin/users → Pending invites → row actions → Revoke. Revoking writes
status: 'revoked' on the OrgInvitation. The token immediately stops working even if the
email is still in the recipient’s inbox. Mission-guest invites revoke the same way from the
mission detail page.
Security notes
- Invite tokens are opaque, cryptographically random, and single-use.
- The
InviteTokenDoclives in a separate Firestore collection from theOrgInvitationitself — clients can look up a token without being able to enumerate existing invitations for an org. - Accepting an invite is a transactional write — the membership is created and the
invitation is marked
acceptedatomically.