Access & Onboarding

Staff Onboarding & Invitations Guide

How a super admin invites a staff member to a tenant, and what the invited member experiences. Covers the auth-engine admin console path (the only path — Hiana Loans reads invitations, it doesn't create them).

What an invitation is

An invitation is a user_invitations row in the auth engine, scoped to one tenant + one client app + one role. The raw accept token exists only in the outgoing email link; the database stores only its hash.

Inviting (super admin)

In https://auth.twoengines.tech/admin/invite:

  1. Tenant — the organization the member joins (e.g. Nakhalat Finance).
  2. Email — the invitee. If they already have an account on any tenant, this adds a membership row rather than a second user.
  3. Client app — the application the membership is for (Hiana Loans).
  4. Role — the membership's role (admin, officer, …).

The invitation is PENDING with a ~72h expiry (tenant-configurable via invitation_expiry_hours).

What the invitee sees

The email contains a single-use accept-invitation?token=... link to the auth engine's accept page. On submit:

  • Token hash is matched against the stored hash; expired/consumed invitations are rejected (TokenExpired / "no longer pending").
  • Password is validated against the tenant's password policy.
  • New account: a users row + a user_app_roles membership (principal_type=organization_user, tenant+app+role from the invitation).
  • Existing account on another tenant: only a second user_app_roles row is added — same user identity, a new tenant-scoped membership. The existing password is unchanged (the password field on the accept page applies only to new accounts).
  • Email is marked verified — the token itself proved mailbox access.
  • The invitation flips to ACCEPTED and a session is issued immediately — the member lands logged in, scoped to the invited tenant.

Membership model — why this works across tenants

  • users is global; user_app_roles is per (user, app, tenant). One email can hold admin in tenant A and officer in tenant B simultaneously.
  • Each access token is bound to one tenant — the JWT's tenant_id decides which schema/permissions apply on every request. A Nakhalat-scoped session cannot read another tenant's data; there is no ambient cross-tenant access.
  • Permissions come from the role's authorization snapshot carried inside the JWT — Hiana trusts the claims, not a local role table.

Managing invitations (admin)

  • List: GET /api/v1/auth/invitations — pending/accepted/expired.
  • Resend: rotates the token, refreshes the expiry, keeps the invitation PENDING. The new raw token goes back through the notification layer; old links die.
  • Revoke: flips PENDING/EXPIRED → REVOKED. Accepted invitations are not revocable — remove the membership via user management instead.

Deactivating a member

Deactivation/removal of the membership happens in the auth engine's Users or Authorization surfaces — a revoked membership stops resolving on the next token refresh; Hiana also rejects requests for profiles flagged inactive.

Operational checks

  • Invitation "sent" in the admin UI only proves the row was created and the notification was queued — delivery is email-provider dependent. If the member reports no email, check spam, then resend (never share a token out-of-band).
  • On first login the member lands on the Hiana dashboard for the invited tenant. If they see an empty workspace they may be on the wrong tenant session — sign out and accept the invite again, or switch tenant through the business switcher if present.