Skip to content

Admin Web App — Overview

The admin portal (semalink-admin) is the internal tool used by Sema Link staff to operate the platform. It is a completely separate application from the customer web app — different repo, different auth system, different deployment pipeline.

Staging: staging-admin.semalink.africaProduction: admin.semalink.africa (not yet live)


Tech Stack

LayerTechnology
FrameworkVite + Vue 3 + TypeScript
StylingTailwind CSS
APIsemalink-api/api/v1/admin/* routes, separate JWT namespace
HostingCloudflare Pages
CI/CDGitHub Actions

Authentication — Two Layers

Staff authentication uses two independent gates, giving effectively 2FA without building TOTP:

Layer 1 — Cloudflare Zero Trust (production only)

  • Staff must authenticate via Google SSO before the login page is even reachable
  • Access policy restricts to @semalink.africa email domain
  • A stolen password alone is useless without the Google account

Layer 2 — Username/password against the API

  • Staff enter their own admin credentials after Zero Trust clears them
  • Handled by POST /api/v1/admin/auth/login
  • No self-signup. Accounts are created by an owner only via POST /api/v1/admin/staff
  • Brute-force protection: rate limiting on the login endpoint + account lockout after N failed attempts

The admin JWT uses a completely separate Fastify namespace (ADMIN_JWT_SECRET) — admin tokens cannot be used on customer routes and vice versa.


Permission Model

Three permission levels per module:

owner  >  staff  |  (no access)
LevelCan do
ownerAll actions including destructive/irreversible ones (delete org, manage staff accounts, change pricing)
staffDay-to-day operational actions (approve sender IDs, adjust credits, view all data)
No accessModule not visible in launcher

Permissions are stored per staff member on their admin_staff record and carried in the JWT.


Modular Launcher

On login, staff land on a home page showing all modules as cards. Modules with no access are greyed out ("coming soon"). Modules with access are clickable and navigate into that mini-app.


Modules

ModuleAPI PrefixStatus
Staff auth/admin/auth/*✅ Login, logout, refresh, change password
Staff management/admin/staff/*✅ List, create, update, permissions, activate/deactivate
CRM/admin/crm⬜ Org list, user list, suspend/reactivate, impersonate
Sender IDs/admin/sender-ids⬜ Approval queue, approve/reject/bulk
Billing/admin/billing⬜ Credit balances, ledger, manual top-up/adjustment
Pricing/admin/pricing⬜ Per-country SMS rates, agent wholesale rates
Agent Manager/admin/agents⬜ Agent list, sub-customer visibility
Campaigns/admin/campaigns⬜ Cross-org campaign view
Engineering/admin/engineering⬜ Queue depths, DLR backlog, API health
Audit Log/admin/audit⬜ Append-only audit log viewer

First Owner Account

The first staff account (evanson@semalink.africa, role: owner) is seeded directly on the database using a seed script — there is no self-signup flow:

sh
ADMIN_EMAIL=evanson@semalink.africa ADMIN_NAME="Evanson Biwott" \
  npx tsx scripts/seed-admin-staff.ts

This has been run on staging. Production seeding happens as part of the production launch checklist.

Internal use only — Sema Link Engineering