Table of Contents
- 1) Goal & Preconditions
- 2) User Flow
- 3) Functional Requirements
- 4) Non‑Functional Requirements
- 5) Data Model (Recommended)
- 6) API Stubs
- 7) Data & Validation Cheatsheet
- 8) UX Copy & Errors (EN / AR)
- 9) Edge Cases & Business Rules
- 10) Acceptance Criteria
- 11) Analytics & Events
- 12) Security & Implementation Notes
- 13) Gaps Found & Decisions Made
- 14) Developer Handoff Checklist
- 15) Next steps
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_subscriptionsper 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_permissionsfor 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=trueand assigned facilities. - Permission enforcement: Given no
view_facilityfor 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.