Salud Capital
Salud Capital · Internal KB
April 2026
Internal Knowledge Base · Website Development

Unified Access Control & Admin Panel
Development Specification

Full specification for centralized user provisioning, tiered access control, and admin panel architecture across the SaludCap website — covering investor portals, gated research documents, employee sections, and the Salud Vault confidential appendix. Prepared for handoff to the website development team.

Auth Architecture Admin Panel Investor Portal Gated Research JWT / Sessions Next.js Role-Based Access
✍ Salud Capital Internal  ·  📅 April 2026  ·  🔒 Confidential — Dev Team Only
01   Vision & Problem Statement

One Admin Panel. All Access Decisions.

Today, access to gated Salud Capital content is managed in two disconnected ways: research document HTML files contain hardcoded SHA-256 password hashes and manually maintained email lists, and the website has no centralized user management. This creates operational drag — adding an investor means editing code — and no audit trail of who accessed what.

The target state is a single Salud Capital Admin Panel where any authorized administrator can provision, modify, and revoke access for employees, investors, and research readers across every gated surface of the SaludCap platform — website sections, investor portals, and individual research documents — from one interface.

3
Access Tiers
4
Gated Surfaces
1
Admin Panel
0
Hardcoded Passwords

"Every access decision — who sees investor research, who enters the Vault appendix, who reads the employee playbook — should be provisioned, audited, and revoked from one admin interface. No code edits required."

02   Access Tier Architecture

Three Tiers, Four Surfaces, One Source of Truth

Access Tiers

TierLabelWhoProvisioned ByAuth Method
ADMINSuper AdminSalud Capital founders, designated ops leadManual (database seed)Email + strong password + TOTP 2FA
EMPLOYEETeam MemberAny @saludcap.com or @saludwireless.com addressAdmin panel: domain auto-grant or individual inviteEmail + password (SSO-ready)
INVESTORInvestor / PartnerNamed individuals provisioned by adminAdmin panel: individual email invite with expiryEmail magic link OR email + password
PUBLICPublic ReaderAnyone with the URLNone — no account requiredNone

Gated Surfaces & Tier Requirements

Surface / Content
Public
Investor
Employee
Admin
Public research reports (non-gated)
Investor Portal (pipeline, thesis docs)
Gated research reports (investor-only)
Salud Vault Appendix (confidential)
Employee playbooks / internal docs
Admin Panel

Investors get Vault Appendix access only if the "Vault Appendix" permission toggle is individually enabled for their account in the admin panel. Default = off for investors.

Per-User Permission Flags

Beyond tier, each user account carries a set of boolean permission flags that admin can toggle independently. This allows granular control — e.g. an investor who gets all research but not the Vault Appendix, or a research partner who gets one specific report but not the investor portal.

FlagKeyDefault (Investor)Default (Employee)Description
Investor Portalcan_investor_portal✓ On✓ OnAccess to /investor and all investor portal pages
Gated Researchcan_gated_research✓ On✓ OnAccess to investor-gated research reports
Vault Appendixcan_vault_appendix✗ Off✓ OnReveals Salud Vault confidential appendix in research docs
Employee Sectionscan_employee_sections✗ Off✓ OnInternal playbooks, build tracker, partner contacts
Specific Report Overridereport_allowlist[]Array of report IDs — grants access to specific reports regardless of tier
Account Expiryexpires_atSet per invitenull (no expiry)ISO datetime; null = no expiry. Useful for time-boxed investor data room access
03   System Architecture

How the Pieces Connect

Clients / Surfaces

SaludCap Website
saludcap.github.io/salud
Research Reports
Standalone HTML files
Investor Portal
/investor/* pages
Admin Panel
/admin/* pages

SaludCap Auth Service

Central auth API · JWT issuance · Session management
Hosted: Vercel Edge Functions or Cloudflare Workers

User & Permissions DB

Postgres (Supabase or PlanetScale)
Users · Roles · Flags · Invite tokens · Audit log

Email Service

Magic links · Invite emails · Password reset
Recommended: Resend or SendGrid

Admin Controls

Provision Users
Set Permission Flags
Manage Domains
Set Expiry Dates
Revoke Access
View Audit Log
Generate Invite Links
Export Access Report

Authentication Flow

1

User visits gated page or research document

Browser checks for valid JWT in httpOnly cookie (website) or localStorage key sc_auth_token (standalone HTML research docs). If no token or token expired → redirect to login.

2

Login via SaludCap Auth Service

User submits email + password at auth.saludcap.com/login. Service validates credentials against database. On success, issues a signed JWT (RS256) containing: sub (user ID), email, tier, permissions object, exp (24h default, 7d for "remember me").

3

JWT stored and passed to gated surfaces

Website pages: JWT in httpOnly cookie, validated server-side via Vercel middleware. Standalone research HTML files: JWT in localStorage, read client-side via fetch() call to /api/verify-token. Token payload contains all permission flags — no additional DB call needed per page.

4

Permission check renders correct content

Website: Next.js middleware checks JWT claims before rendering protected routes. Research HTML: client-side JS calls /api/verify-token, receives permission object, shows or hides content sections based on flags. can_vault_appendix: true → Vault appendix revealed. can_gated_research: false → redirect to request access page.

5

Access event logged to audit trail

Every gated page view, research document open, and Vault appendix unlock writes an event to the audit log table: (user_id, event_type, resource_id, ip_address, user_agent, timestamp). Visible in admin panel. Exportable to CSV.

Migration: Hardcoded SHA-256 → Centralized Auth

Current State (Research HTML Files): SHA-256 password hash and email list hardcoded in each document's JavaScript. No server communication. No audit trail. Adding an investor requires editing file code.

Target State: Research documents call GET /api/verify-token with the user's JWT. The API returns the full permission object. The document renders based on flags. Zero credentials stored in the HTML file.
Current (to be deprecated)
// ❌ Old approach — hardcoded in each HTML file
const PASS_HASH = '98364381a28f...';
const AUTHORIZED = ['@saludcap.com', '@saludwireless.com'];
async function vaultAuth() { /* SHA-256 check */ }
Target (post-migration)
// ✅ New approach — research HTML calls auth service
async function initResearchDoc() {
  const token = localStorage.getItem('sc_auth_token');
  if (!token) { showLoginPrompt(); return; }

  const res = await fetch('https://auth.saludcap.com/api/verify-token', {
    headers: { 'Authorization': `Bearer ${token}` }
  });
  if (!res.ok) { showLoginPrompt(); return; }

  const { permissions, email } = await res.json();
  // permissions = { can_gated_research, can_vault_appendix, ... }

  if (permissions.can_vault_appendix) {
    document.getElementById('vault-appendix').classList.add('unlocked');
  }
  logAccess(token, 'digital_payments_research_v1'); // audit event
}
04   Database Schema

Data Model — Postgres (Supabase Recommended)

-- Users table
CREATE TABLE users (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email         TEXT UNIQUE NOT NULL,
  password_hash TEXT,                    -- bcrypt; null if magic-link-only
  tier          TEXT NOT NULL DEFAULT 'investor',  -- admin | employee | investor | public
  display_name  TEXT,
  company       TEXT,
  created_at    TIMESTAMPTZ DEFAULT now(),
  last_login_at TIMESTAMPTZ,
  expires_at    TIMESTAMPTZ,             -- null = no expiry; set for time-boxed investors
  is_active     BOOLEAN DEFAULT true,
  invited_by    UUID REFERENCES users(id)
);

-- Permission flags (one row per user, all flags)
CREATE TABLE user_permissions (
  user_id               UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
  can_investor_portal   BOOLEAN DEFAULT false,
  can_gated_research    BOOLEAN DEFAULT false,
  can_vault_appendix    BOOLEAN DEFAULT false,   -- Salud Vault confidential appendix
  can_employee_sections BOOLEAN DEFAULT false,
  report_allowlist      TEXT[] DEFAULT '{}',      -- array of report slugs
  updated_at            TIMESTAMPTZ DEFAULT now(),
  updated_by            UUID REFERENCES users(id)
);

-- Authorized email domains (auto-grants EMPLOYEE tier)
CREATE TABLE authorized_domains (
  id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  domain     TEXT UNIQUE NOT NULL,   -- e.g. 'saludwireless.com'
  auto_tier  TEXT DEFAULT 'employee',
  added_by   UUID REFERENCES users(id),
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Invite tokens (for investor onboarding)
CREATE TABLE invite_tokens (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  token       TEXT UNIQUE NOT NULL DEFAULT gen_random_uuid()::text,
  email       TEXT NOT NULL,
  tier        TEXT NOT NULL DEFAULT 'investor',
  permissions JSONB,                  -- pre-set permission flags for this invite
  expires_at  TIMESTAMPTZ NOT NULL,   -- invite link expiry (typically 7 days)
  used_at     TIMESTAMPTZ,
  created_by  UUID REFERENCES users(id)
);

-- Audit log
CREATE TABLE access_log (
  id          BIGSERIAL PRIMARY KEY,
  user_id     UUID REFERENCES users(id),
  email       TEXT,                   -- denormalized for easy log reading
  event_type  TEXT NOT NULL,          -- login | view_research | unlock_vault | invite_sent | access_revoked
  resource_id TEXT,                   -- report slug, page path, etc.
  ip_address  INET,
  user_agent  TEXT,
  metadata    JSONB,
  created_at  TIMESTAMPTZ DEFAULT now()
);

-- Default permissions by tier (for new user initialization)
-- Employee: all internal flags on. Investor: portal + research on; vault off.
CREATE VIEW default_permissions AS
SELECT 'employee' AS tier, true AS can_investor_portal, true AS can_gated_research,
       true AS can_vault_appendix, true AS can_employee_sections
UNION ALL
SELECT 'investor', true, true, false, false;

Recommended Stack

ComponentRecommendedAlternativeNotes
DatabaseSupabase (Postgres)PlanetScale, NeonSupabase has built-in auth, row-level security, and a REST API — reduces custom code significantly
Auth LayerSupabase AuthNextAuth.js, ClerkSupports email+password, magic links, and OAuth; custom JWT claims via hooks
FrameworkNext.js 14+ (App Router)Remix, AstroAlready in SaludCap tech stack; Vercel-native; middleware for route protection
HostingVercelCloudflare PagesAlready connected via GitHub → Vercel pipeline
EmailResendSendGrid, PostmarkDeveloper-friendly; React Email templates; generous free tier
Admin UIshadcn/ui + TailwindTremor, Ant DesignAlready used in SaludCap artifacts; no additional CSS framework needed
05   Admin Panel Functional Specification

Admin Panel — Screen-by-Screen

The admin panel lives at /admin and is accessible only to accounts with tier = 'admin'. It is the single interface for all access management across the SaludCap platform.

Panel Wireframe — Users Screen

⬡ SALUDCAP ADMIN admin@saludcap.com · Sign out
Users
All Users
Invite New
Access
Domains
Permissions
Research Docs
Logs
Audit Log
Export

Users  · 3 employees · 2 investors · 1 admin

Email / Name
Tier
Research
Vault
Active
chris@saludcap.com
Chris — Founder
Admin
investor@example.com
J. Smith — Acme Capital
Investor
partner@saludwireless.com
Auto-provisioned · domain
Employee

Admin Panel — All Screens

ScreenRouteKey Functions
Dashboard/admin Active user count by tier; recent access events; expiring accounts (next 7 days); quick-invite button
All Users/admin/users Table of all accounts. Columns: email, name, company, tier, expiry, permission toggles (Research, Vault, Employee, Investor Portal), last login, Active toggle. Inline edit all flags. Click row → detail page.
User Detail/admin/users/[id] Full permission set; expiry date picker; report allowlist editor; access history for this user; revoke all sessions button; delete account button.
Invite New/admin/users/invite Email field; tier dropdown; pre-set permission toggles; expiry date (default 90 days); optional personal note in email. Generates and sends invite link. Shows copyable invite URL.
Domains/admin/domains Add/remove authorized email domains. Each domain: domain string, auto-tier (employee/investor), date added, added by. Domain users auto-provisioned on first login with domain-default permissions.
Research Docs/admin/research Registry of all gated research reports. Each doc: slug, title, required tier, Vault appendix yes/no, access count, last accessed. Toggle individual reports between public/gated. Add new report to registry.
Audit Log/admin/audit Filterable log of all access events: login, view_research, unlock_vault, invite_sent, access_revoked. Filter by user, event type, date range. Export to CSV.

Invite Flow — Investor Onboarding

1

Admin opens Invite New screen

Enters investor email, selects tier "Investor," sets permission toggles (Research: on, Vault: off by default), sets expiry (e.g. 90 days), adds optional note.

2

System generates signed invite token

Stores invite record in invite_tokens table. Sends branded email to investor with invite link: auth.saludcap.com/accept-invite?token=xxxx. Admin sees copyable link on screen as backup.

3

Investor clicks link, sets password

Token validated (not expired, not used). Investor sets password. Account created in users table with pre-configured permissions from invite record. Welcome email sent.

4

Investor can now access all provisioned surfaces

JWT issued on login contains their permission flags. Research documents, investor portal, and Vault appendix (if flag enabled) all respond to the same JWT without additional provisioning.

06   API Endpoints

Auth Service API — Required Endpoints

All endpoints hosted under https://auth.saludcap.com/api/ (Vercel Edge Function or Next.js API route). All authenticated endpoints require Authorization: Bearer <jwt>.

MethodEndpointAuth RequiredDescription
POST/api/auth/loginEmail + password → returns signed JWT + user object
POST/api/auth/magic-linkEmail → sends magic link email; for password-less investor login
POST/api/auth/accept-inviteInvite token + new password → creates account, returns JWT
POST/api/auth/logoutUserInvalidates session; clears cookie
GET/api/verify-tokenUserValidates JWT; returns {email, tier, permissions}; used by standalone research HTML files
POST/api/log-accessUserWrites event to audit log; called by research docs on open/unlock
GET/api/admin/usersAdminList all users with permissions
POST/api/admin/usersAdminCreate user directly (no invite)
PATCH/api/admin/users/[id]AdminUpdate tier, permissions, expiry, active status
DELETE/api/admin/users/[id]AdminDeactivate account; does not hard-delete (preserve audit trail)
POST/api/admin/inviteAdminGenerate invite token; send invite email; returns invite URL
GET/api/admin/domainsAdminList authorized domains
POST/api/admin/domainsAdminAdd authorized domain + auto-tier
DELETE/api/admin/domains/[id]AdminRemove authorized domain
GET/api/admin/auditAdminPaginated audit log with filters
GET/api/admin/audit.csvAdminCSV export of audit log
verify-token response shape (used by research HTML files)
// GET /api/verify-token
// Response 200:
{
  "valid": true,
  "user_id": "uuid-...",
  "email": "investor@example.com",
  "tier": "investor",
  "display_name": "J. Smith",
  "permissions": {
    "can_investor_portal": true,
    "can_gated_research": true,
    "can_vault_appendix": false,   // ← controls Vault appendix visibility
    "can_employee_sections": false,
    "report_allowlist": ["genius_act_research", "crypto_policy_trump_biden"]
  },
  "expires_at": "2026-07-01T00:00:00Z"
}

// Response 401 (expired or invalid token):
{ "valid": false, "error": "token_expired" }
Research HTML integration (replaces SHA-256 gate)
// Drop-in script for every gated research HTML file
// Replaces the current hardcoded SHA-256 auth block entirely

(async function saludAuth() {
  const token = localStorage.getItem('sc_auth_token');
  const loginUrl = 'https://saludcap.github.io/salud/login?return='
                 + encodeURIComponent(location.href);

  // No token → redirect to login
  if (!token) { location.href = loginUrl; return; }

  try {
    const res = await fetch('https://auth.saludcap.com/api/verify-token', {
      headers: { Authorization: 'Bearer ' + token }
    });
    if (!res.ok) { location.href = loginUrl; return; }

    const { permissions, email, tier } = await res.json();

    // Gate: gated research requires can_gated_research
    if (!permissions.can_gated_research) {
      document.getElementById('gated-gate').style.display = 'block';
      return;
    }

    // Vault Appendix: controlled by can_vault_appendix flag
    if (permissions.can_vault_appendix) {
      const vault = document.getElementById('vault-appendix');
      if (vault) vault.classList.add('unlocked');
    }

    // Show user identity in nav
    document.getElementById('user-email').textContent = email;

    // Log access event
    fetch('https://auth.saludcap.com/api/log-access', {
      method: 'POST',
      headers: { Authorization: 'Bearer ' + token,
                 'Content-Type': 'application/json' },
      body: JSON.stringify({
        event_type: 'view_research',
        resource_id: document.documentElement.dataset.reportSlug
      })
    });

  } catch (e) {
    console.error('Auth check failed', e);
    // Fail open on network error for offline access — or fail closed:
    // location.href = loginUrl;
  }
})();
07   Research Document Registry

Mapping Research Files to Access Rules

Every gated research document should be registered in the admin panel's Research Docs screen. This allows access rules to be changed without touching the HTML files, and gives the audit log meaningful resource identifiers.

Report SlugTitleCurrent GateVault AppendixMin Tier
digital_payments_v1Digital Payments, Tokens, Smart Contracts & Secure IdentityInvestor✓ YesInvestor
genius_act_researchThe GENIUS Act: America's First Crypto LawPublic— NoPublic
crypto_policy_trump_bidenTrump vs. Biden: Digital Asset Policy ComparisonPublic— NoPublic
defi_ux_senior_unbankedDeFi UX: Simplicity for Seniors & UnderbankedInvestor— NoInvestor
Implementation Note — HTML file data attribute: Add data-report-slug="digital_payments_v1" to the <html> tag of each research document. The auth script reads this to log the correct resource ID. The admin panel can then show per-report access stats.
<!-- Add to <html> tag of each research document -->
<html lang="en"
  data-report-slug="digital_payments_v1"
  data-report-title="Digital Payments, Tokens, Smart Contracts & Secure Identity"
  data-vault-appendix="true">
08   Security Requirements

Security Architecture & Compliance Notes

RequirementImplementationPriority
Password hashingbcrypt with work factor ≥ 12. Never store plaintext or MD5/SHA-256 of passwords.Critical
JWT signingRS256 (asymmetric). Private key in Vercel/Supabase secrets. Public key for verification only.Critical
HTTPS onlyAll auth API endpoints enforce HTTPS. HSTS header required.Critical
Rate limitingLogin endpoint: max 5 attempts per 15 min per IP. Magic link: 3 per hour per email.Critical
TOTP 2FA for adminsAdmin accounts require TOTP (Google Authenticator / Authy). TOTP secret stored encrypted in DB.Critical
Invite token expiryInvite links expire after 7 days. Single-use only. Mark used_at on first use.High
Session revocationMaintain a JWT revocation list (Redis or DB) for forced logout. Check on every verify-token call.High
CORS policyAuth API allows: saludcap.github.io, *.saludcap.com, localhost:* (dev only).High
Audit log immutabilityAudit log rows are insert-only. No update or delete permissions on access_log table.High
Account expiry enforcementJWT issuance checks expires_at. Expired accounts return 401 on verify-token. Nightly job deactivates expired accounts.Medium
Soft delete onlyNever hard-delete users. Set is_active = false. Preserves audit trail.Medium
Research HTML offline riskStandalone HTML files can be saved locally. Auth gate is client-side. For truly sensitive content: serve from authenticated Next.js routes instead of standalone HTML.Note
Important Limitation — Standalone HTML Files: Client-side auth in standalone HTML research documents (like the current gated research files) can be bypassed by a determined user who disables JavaScript or edits the DOM. For the current stage this is acceptable — the gate deters casual access and provides an audit trail. For the highest-sensitivity content (SaludID Plus specs, legal memos), serve from server-rendered Next.js pages behind Vercel middleware auth rather than standalone HTML files.
09   Development Checklist

Build Sequence for Website Dev Team

Phase 1 — Foundation (Week 1–2)

Phase 2 — Admin Panel (Week 3–4)

Phase 3 — Research Doc Integration (Week 5)

Phase 4 — Polish & Hardening (Week 6)

10   Current State — Bridge Instructions

What to Tell the Dev Team About Existing Files

Existing Research Files: The following research HTML files currently have hardcoded SHA-256 auth gates that will be replaced by the centralized auth system above. Until Phase 3 migration is complete, the SHA-256 gates remain functional.

Current password: SaludVault2026!
Current authorized domains: @saludcap.com, @saludwireless.com
Investor access: add individual email addresses to the const AUTHORIZED array in the HTML file's <script> block.

Do not commit the password or investor email list to any public GitHub repository. Keep gated research files out of the public saludcap.github.io/salud/ pages tree until the auth service is live.
FileLocationGate TypeMigration Priority
salud_digital_payments_research.htmlOutputs / Vercel deploySHA-256 inline JS (Vault appendix only)Phase 3 — High
salud_digital_payments_research_PUBLIC.htmlOutputs / Vercel deployNo gate — locked notice onlyReplace with gated version post-auth
genius_act_research.htmlProject / GitHubNone — publicNo action required
crypto_policy_trump_biden.htmlProject / GitHubNone — publicNo action required
11   References & Resources

Recommended Reading for Dev Team

TopicResource
Auth frameworkSupabase Auth Docs — email+password, magic links, JWT customization
Next.js route protectionNext.js Authentication Guide — App Router, middleware, session management
JWT best practicesAuth0 — JWT Best Current Practices
Row Level SecuritySupabase RLS Guide — protect DB rows by user role
Email (magic links)Resend Docs + React Email for branded invite templates
Rate limitingUpstash Rate Limit — Redis-backed, Vercel Edge compatible
TOTP 2FAotpauth (npm) — TOTP generation and verification in Node.js
Admin UI componentsshadcn/ui Blocks — pre-built dashboard, table, and form layouts
Current SaludCap stackGitHub: saludcap.github.io/salud/ → Vercel deployment pipeline. Local dev path: C:\Users\Admin\Documents\ai coding\salud