Edit Page

Team Invitations

RESTHeart

Team invitations allow users with the owner or admin role to add people to their tenant. The invited user receives an email with a one-time activation link; on click they set a password and gain immediate access.

Invite a user

POST /auth/invite — requires role owner or admin.

Request

POST /auth/invite
Authorization: Bearer <token>
Content-Type: application/json

{
  "email": "bob@example.com",
  "role":  "member"
}

role must be one of: member, admin. Assigning owner via invite is not permitted.

Server-side steps

  1. Authorize the caller (must be owner or admin of their tenant).

  2. Validate email format and role value.

  3. If the user already belongs to the caller’s tenant → 409 Conflict.

  4. If the user exists in a different tenant → create a new membership document for the caller’s tenant; send a shorter "you’ve been added" email (no password setup needed).

  5. If the user does not exist → create a new user document:

    {
      "email":             "bob@example.com",
      "status":            "invited",
      "password":          "<random 256-bit — not usable>",
      "inviteToken":       "<random 256-bit>",
      "inviteCreatedAt":   "<ISO timestamp>",
      "tenant":            "<caller's tenantId>"
    }
    plus a `membership` document with the requested `role`.
  6. Send an activation email:

    {baseAppUrl}/auth/activate?email=bob@example.com&token=<inviteToken>
  7. Return 201 Created.


Activate an invitation

PATCH /auth/activate — accessible to $unauthenticated (the user has only the token link).

Request

PATCH /auth/activate
Content-Type: application/json

{
  "email":       "bob@example.com",
  "inviteToken": "<token from email>",
  "password":    "correct-horse-battery",
  "consents": {
    "terms":   "1.0",
    "privacy": "1.0"
  }
}

Server-side steps

  1. Validate all fields.

  2. Find user by email.

  3. Compare inviteToken using constant-time comparison.

  4. Check inviteCreatedAt + TTL (inviteTokenTtlDays, default 7 days).

  5. Enforce password strength (zxcvbn score ≥ minimumPasswordStrength).

  6. Validate consent versions against the configured legalPolicies — 409 if versions do not match (policy update required).

  7. Clear any existing server session (prevents session fixation).

  8. Update the user:

    {
      "$set":   { "status": "active", "password": "<bcrypt hash>",
                  "consents": { "terms": "1.0", "privacy": "1.0",
                                "ip": "...", "at": "..." } },
      "$unset": { "inviteToken": "", "inviteCreatedAt": "" }
    }
  9. Issue JWT + refresh cookie → auto-login.

  10. Return 200 OK.

Error responses

Status Reason

400

Missing fields; password too weak; expired token

409

Consent versions outdated (user must accept current policies)


Re-send an invitation

POST /auth/resend-invite/{email} — requires role owner or admin.

Generates a new inviteToken (invalidating the previous one) and re-sends the activation email.

POST /auth/resend-invite/bob@example.com
Authorization: Bearer <token>

Returns 200 OK on success. Returns 404 if the user is not found in the caller’s tenant or is not in invited status.