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:
- Tenant — the organization the member joins (e.g. Nakhalat Finance).
- Email — the invitee. If they already have an account on any tenant, this adds a membership row rather than a second user.
- Client app — the application the membership is for (Hiana Loans).
- 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
usersrow + auser_app_rolesmembership (principal_type=organization_user, tenant+app+role from the invitation). - Existing account on another tenant: only a second
user_app_rolesrow 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
ACCEPTEDand a session is issued immediately — the member lands logged in, scoped to the invited tenant.
Membership model — why this works across tenants
usersis global;user_app_rolesis 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_iddecides 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.