Tenant UI — Users Tab (Full Spec v1.0)
Section

Tenant Users — Specification

Version 1.0 • Author: Generated for Yazan / Climacert‑X

1) Goal & Preconditions

Goal: Provide tenant admins a developer‑ready Users Tab to manage users, invite, assign facility‑scoped permissions (including view_subscriptions), and enforce server‑side RBAC. Onboarding uses invite email + first‑login phone OTP verification.

Preconditions:

  • Tenant exists; tenant admin tokens available.
  • Facilities and subscriptions modeled and linked to tenant.
  • Existing auth/OTP infra reused (OTP TTL 300s; resend 60s; max attempts 5 → lock 15m).
  • Audit logging and rate limiting platform‑wide.

2) User Flow

Keep mermaid as flowchart TD or LR for compatibility. Placeholder below.

flowchart TD
  A[Users Tab] --> B[Invite User]
  B --> C{Email or Phone?}
  C -->|Email| D[Send invite email with inviteToken]
  C -->|Phone| E[Send SMS invite + OTP]
  D --> F[Accept Invite Page]
  E --> F
  F --> G{Phone provided?}
  G -->|Yes| H[Verify OTP]
  G -->|No| I[Skip]
  H --> I
  I --> J[Set password]
  J --> K[Create user + assign facility permissions]
  K --> L[Active]

3) Functional Requirements

  • Users list — name, email, phone, role, assigned facilities (pills/summary), view_subscriptions per facility, status (Invited/Active/Locked/Removed), last login, actions.
  • Search & Filter — by name/email; filter by role, facility, status.
  • Invite/Add user — invite by email/phone; assign facilities and per‑facility view_subscriptions.
  • Invite delivery — email/SMS with single‑use inviteToken (TTL 72h) to accept page.
  • First‑login verification — if phone provided, require OTP at acceptance. Email inviteToken acts as email verification.
  • Edit user — change role, facilities, subscription visibility, lock/unlock, resend invite.
  • Soft delete — remove → soft delete; revoke tokens.
  • Server authorization — enforce user_has_access(userId, facilityId) on all facility APIs.
  • Audit logging — invites, acceptance, permission changes, resends, locks/unlocks.
  • Localization — EN/AR with RTL support.

4) Non‑Functional Requirements

  • Security — invite tokens hashed at rest; single‑use; TTL 72h. Strong password hash (bcrypt/argon2). Rate‑limit OTP & invites.
  • Performance — list load < 1s p95 for ≤500 users; pagination + server‑side search.
  • Reliability — idempotent invite & OTP; retries do not create duplicates.
  • Scalability — indexed user_facility_permissions for O(log n).
  • Observability — analytics for invite/resend/accept; audit logs.

5) Data Model (Recommended)

users {
  user_id: uuid,
  tenant_id: uuid,
  name: string,
  email: string|null,
  phone: +E164|null,
  email_verified: boolean,
  phone_verified: boolean,
  status: enum(invited|active|locked|removed),
  role: enum(tenant_admin|tenant_user),
  last_login_at: timestamp|null,
  created_at: timestamp,
  updated_at: timestamp
}

user_facility_permissions {
  id: uuid,
  user_id: uuid,
  facility_id: uuid,
  view_facility: boolean,
  view_subscriptions: boolean,
  created_at: timestamp,
  updated_at: timestamp
}

invites {
  invite_id: uuid,
  tenant_id: uuid,
  email: string|null,
  phone: +E164|null,
  invited_by: user_uuid,
  role: enum(tenant_admin|tenant_user),
  facilities: uuid[],
  message: string|null,
  invite_token_hash: string,
  expires_at: timestamp,
  status: enum(pending|accepted|expired|revoked),
  created_at: timestamp
}

otp_sessions { /* reuse existing */
  otp_id: uuid,
  identifier: email_or_phone,
  purpose: enum(invite_verification|signup),
  code_hash: string,
  expires_at: timestamp,
  attempts: number,
  locked_until: timestamp|null
}

6) API Stubs

GET /v1/tenants/{tenantId}/users?search=&role=&facilityId=&status=&page=&limit=
200 { items: [ user + permissions ], meta: { total, page, limit } }

POST /v1/tenants/{tenantId}/invites
// Body: { name, email?, phone?, role, facilities[], view_subscriptions: { [facilityId]: boolean }, message? }
// Behavior: create invite, hash token, send email/SMS link /accept-invite?token=..., audit log

POST /v1/tenants/{tenantId}/invites/{inviteId}/resend

POST /v1/auth/invite/accept
// Body: { inviteToken, password, otpCode? }
// Behavior: validate token (hash compare); if phone required -> OTP; create user; set verified flags; assign permissions; mark invite accepted

POST /v1/auth/otp/verify

PATCH /v1/tenants/{tenantId}/users/{userId}
// Allowed: name, role (admin only), facilities set, view_subscriptions per facility

DELETE /v1/tenants/{tenantId}/users/{userId}
// Soft delete; revoke tokens

POST /v1/tenants/{tenantId}/users/{userId}/lock
POST /v1/tenants/{tenantId}/users/{userId}/unlock

7) Data & Validation Cheatsheet

  • Name: 2–80 chars
  • Email: RFC compliant; lowercase for uniqueness
  • Phone: E.164; validate with libphonenumber
  • Password: min 8; at least 1 upper, 1 number, 1 symbol
  • Invite TTL: 72h default
  • OTP: TTL 300s; resendAfter 60s; maxAttempts 5 → lock 15m
  • Role constraints: only active Tenant Admins can invite tenant_admin role
  • Facility assignment: facility IDs must belong to tenant

8) UX Copy & Errors (EN / AR)

Invite email (EN)

Subject: You’ve been invited to [TenantName] on Climacert‑X
Body:
Hi {name},
You were invited to join {TenantName} on Climacert‑X as {role}. Click to accept: {Accept Invite} — link expires in 72 hours.
After accepting you may be asked to confirm your phone via OTP and set a password. Once complete, you’ll only see the facilities assigned to you.
— The Climacert‑X Team

Invite email (AR)

Subject: تمت دعوتك إلى {TenantName} على Climacert‑X
Body:
مرحبًا {name},
تمّت دعوتك للانضمام إلى {TenantName} على Climacert‑X كـ {role}. اضغط للقبول: {قبول الدعوة} — تنتهي صلاحية الرابط بعد 72 ساعة.
بعد القبول قد يُطلب منك تأكيد رقم الهاتف عبر رمز OTP وتعيين كلمة مرور. بعد إتمامها، ستظهر لك المرافق المخصصة فقط.
— فريق Climacert‑X

Common errors

  • Invite expired — “This invite has expired. Ask the tenant admin to resend the invite.” / «انتهت صلاحية هذه الدعوة. اطلب من مسؤول المستأجر إعادة إرسال الدعوة.»
  • OTP invalid — “Invalid code. Check the code and try again.” / «رمز غير صالح. تفقد الرمز وحاول مرة أخرى.»
  • Unauthorized facility — “You do not have permission to view this facility.” / «ليس لديك إذن لعرض هذه المنشأة.»

9) Edge Cases & Business Rules

  • Invite for existing email/phone: avoid duplicates — allow "Add existing user to tenant" path.
  • Facility deleted before acceptance: drop missing facilities; show acceptance summary; Admin to reassign.
  • Multiple invites to same identifier: use latest pending; invalidate previous tokens on resend.
  • Token replay: single‑use; mark used on acceptance.
  • User with no facilities: allowed; show warning; disable facility‑specific actions.
  • Time zones: display in user/tenant local TZ; store UTC.
  • Permissions cascading: view_subscriptions ≠ payment permission; billing actions require separate authorization.

10) Acceptance Criteria

  • List users: Given Tenant Admin authenticated → When opening Users tab → Then see paginated users with roles + facilities.
  • Invite & accept: Given phone invite → When invitee enters correct OTP → Then account created with phone_verified=true and assigned facilities.
  • Permission enforcement: Given no view_facility for F → When requesting /v1/facilities/{F} → Then 403 Forbidden.
  • Invite expiry & resend: Given invite older than 72h → When clicking link → Then see “Invite expired”, and Admin can resend (new token, old invalidated).

11) Analytics & Events

  • user_invite_created {inviteId, tenantId, invitedById}
  • user_invite_resent {inviteId, tenantId, invitedById}
  • user_invite_accepted {userId, inviteId, method}
  • user_otp_sent {otpId, identifier, purpose}
  • user_otp_failed {otpId, attempts}
  • user_role_changed {userId, beforeRole, afterRole, actorId}
  • user_facility_permission_changed {userId, facilityId, permsBefore, permsAfter, actorId}
  • user_locked {userId, actorId, reason}
  • user_removed {userId, actorId}

12) Security & Implementation Notes

  • Hash tokens; never store raw inviteToken. Compare via HMAC/bcrypt/argon2.
  • Idempotent invite endpoint for same identifier+tenant+role+facilities.
  • Strict throttle for OTP; rate limit invite creation per admin.
  • Server‑side enforcement on all facility/device/subscription APIs.
  • Immutable audit logs for invites, acceptance, permission changes.

13) Gaps Found & Decisions Made

  • Invite TTL set to 72h (adjustable).
  • Permission shape includes view_facility & view_subscriptions.
  • Invites table with hashed token + status introduced.
  • Phone verification required when phone provided.
  • Deleted facilities before acceptance are dropped; prompt Admin to reassign.

14) Developer Handoff Checklist

  • Confirm Invite TTL (72h) and Roles set.
  • Add indexes on user_facility_permissions.
  • Wire OTP to auth service (reuse endpoints).
  • Implement EN/AR email templates.
  • Add analytics hooks.
  • Create e2e tests: invite→accept, expired invite, permission checks, lock/unlock.

15) Next steps

  • Export to Word/Markdown.
  • Generate React/Tailwind components for Users table, Invite modal, Edit drawer.
  • Produce Postman collection for API stubs.