Browse documentation

Frontend Specs

Section 14 — Security (`/admin/security`)

Role access: ADMIN only. VIEWER/EDITOR/CLIENT redirected to /dashboard.

docs/specs/frontend/admin-spec-04d-security.md
On this page

Role access: ADMIN only. VIEWER/EDITOR/CLIENT redirected to /dashboard. Layout: 3-tab interface: Dashboard, Sessions, Settings. 1200px max-width. Sidebar: carbon (#12161E) background. Notes: Security is the highest-priority admin function. All security actions are logged in the audit trail with SECURITY category. Failed login attempts, suspicious activity, and policy changes are highlighted in red.


14.1 Security Dashboard#

1. Purpose#

At-a-glance security posture of the platform. Admins see failed login attempts, suspicious IP activity, password strength distribution, 2FA adoption, and recent security events. The "security operations center" view.

2. Visual Layout#

Section A: Security Score Card (top, full width, prominent)

  • Large card with security score: 0-100 scale.
  • Score display: Circular gauge (SVG) with score in center. Color: green (80-100), amber (50-79), red (0-49).
  • Score breakdown: 4 sub-scores below:
    • Authentication: {N}/25 (password strength, 2FA adoption, session policies)
    • Access Control: {N}/25 (RBAC enforcement, role distribution, impersonation logs)
    • Data Protection: {N}/25 (encryption, retention, backup, deletion compliance)
    • Threat Detection: {N}/25 (failed logins, suspicious IPs, anomaly detection)
  • Each sub-score: progress bar + color. Click → scrolls to relevant section.
  • Overall status: "Secure" (green), "Needs Attention" (amber), "At Risk" (red). Based on total score.
  • Last updated: "Score computed {relative time}". "Recompute" button (secondary) to force refresh.

Section B: Threat Overview Cards (below score, full width)

  • 5 cards in a row:
    • Failed Logins (24h): {N} with trend. Red if > 10, amber if > 5, green if <= 5.
    • Blocked IPs: {N} IPs blocked by rate limiting or WAF. Red if > 0.
    • Suspicious Sessions: {N} sessions flagged by anomaly detection (unusual location, device, time). Red if > 0.
    • 2FA Adoption: {N}% of users with 2FA enabled. Green if > 80%, amber if > 50%, red if < 50%.
    • Unpatched Vulnerabilities: {N} (if vulnerability scanner integrated). Red if > 0, green if 0.
  • Card style: same as other KPI cards (white, shadow, 8px radius, text-2xl value).

Section C: Recent Security Events (below, full width)

  • Feed of last 20 security events (not a table, feed style like Alert History):
  • Each event card:
    • Left border: 4px colored by severity. CRITICAL = red, HIGH = amber, MEDIUM = blue, LOW = gray.
    • Event type badge: FAILED_LOGIN, SUSPICIOUS_IP, PASSWORD_WEAK, SESSION_HIJACK, IMPERSONATION, POLICY_CHANGE, CONFIG_CHANGE.
    • Description: "Failed login for admin@rankflow.ai from IP 203.0.113.45 (Mumbai, IN) — 5th attempt in 10 min."
    • Timestamp: relative. "2 min ago".
    • Action: "Block IP" (for suspicious IPs) or "Investigate" (for sessions) or "View Details" (for all).
  • "View All" link → navigates to /admin/audit with security filter.
  • Auto-refreshes every 10 seconds. New events appear at top with fade-in + red pulse for CRITICAL.

Section D: Security Charts (bottom, full width)

  • 2 charts side by side:
    • Failed Login Attempts: Line chart. Failed logins per hour over last 24 hours. Red line. Threshold reference line at 5/hour.
    • Login Success vs Failure: Bar chart. Successful logins (green) vs failed (red) per hour. Last 24 hours.
  • Chart time range: "24h" (default), "7d", "30d".

3. Data Source (tRPC endpoint)#

admin.getSecurityScore.useQuery(undefined, { refetchInterval: 300000 }); // 5min static
admin.getSecurityThreats.useQuery(undefined, { refetchInterval: 10000 }); // 10s real-time
admin.getSecurityEvents.useQuery({
  limit: z.number().min(1).max(100).default(20),
}, { refetchInterval: 10000 }); // 10s real-time
admin.blockIp.useMutation({ ipAddress: z.string().ip(), reason: z.string().optional(), durationHours: z.number().min(1).default(24) });
admin.forceRecomputeSecurityScore.useMutation();

4. Zod Schema#

const SecurityScoreSchema = z.object({
  total: z.number().min(0).max(100),
  authentication: z.number().min(0).max(25),
  accessControl: z.number().min(0).max(25),
  dataProtection: z.number().min(0).max(25),
  threatDetection: z.number().min(0).max(25),
  status: z.enum(["SECURE", "NEEDS_ATTENTION", "AT_RISK"]),
  lastComputedAt: z.date(),
  recommendations: z.array(z.object({
    category: z.string(),
    message: z.string(),
    severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
    action: z.string().optional(), // e.g., "Enable 2FA for all admins"
  })).optional(),
});

const SecurityThreatSchema = z.object({
  failedLogins24h: z.number(),
  failedLoginsTrend: z.enum(["UP", "DOWN", "STABLE"]).optional(),
  blockedIps: z.number(),
  suspiciousSessions: z.number(),
  twoFactorAdoptionPercent: z.number(),
  unpatchedVulnerabilities: z.number().optional(),
});

const SecurityEventTypeSchema = z.enum([
  "FAILED_LOGIN", "SUSPICIOUS_IP", "PASSWORD_WEAK", "SESSION_HIJACK",
  "IMPERSONATION", "POLICY_CHANGE", "CONFIG_CHANGE", "RATE_LIMIT_HIT",
  "UNUSUAL_LOCATION", "UNUSUAL_TIME", "BRUTE_FORCE_ATTEMPT",
]);

const SecurityEventSchema = z.object({
  id: z.string().uuid(),
  type: SecurityEventTypeSchema,
  severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
  description: z.string(),
  userId: z.string().uuid().optional(),
  userName: z.string().optional(),
  ipAddress: z.string().ip().optional(),
  countryCode: z.string().length(2).optional(),
  userAgent: z.string().optional(),
  timestamp: z.date(),
  actionTaken: z.string().optional(), // e.g., "IP blocked for 24h"
  metadata: z.record(z.any()).optional(),
});

5. Fetch Frequency#

  • Real-time (10000ms / 10s): Security events and threats need immediate awareness. 10s polling.
  • Static (300000ms / 5min): Security score changes slowly (policy changes, 2FA adoption). 5min polling.
  • On-demand: Recompute score, block IP, investigate event.
  • SSE: When a security event occurs (failed login, suspicious IP, etc.), push immediately to feed. When score changes, push update.

6. Data Manipulations#

  • Security score gauge: Circular SVG gauge. 0-100. Color segments: red (0-49), amber (50-79), green (80-100). Animated on load (number counts up from 0 to score).
  • Sub-score bars: 4 horizontal bars, each 0-25. Same color rules. Clickable — scrolls to relevant section.
  • Recommendations: If score < 80, show recommendation cards below the score: "Enable 2FA for all admin accounts (+10 points)", "Enforce password rotation every 90 days (+5 points)", "Block IP after 5 failed attempts (+3 points)". Each with a "Fix" button that navigates to the relevant settings.
  • Threat card colors:
    • Failed logins: green if <= 5, amber if 6-10, red if > 10.
    • Blocked IPs: always red if > 0 (someone is being blocked).
    • Suspicious sessions: red if > 0, green if 0.
    • 2FA adoption: green > 80%, amber > 50%, red < 50%.
    • Vulnerabilities: red if > 0, green if 0.
  • Event feed: Card style with left border color. CRITICAL events get pulsing red dot for 30 seconds. New events fade in at top.
  • Event description: Parsed from raw data. "Failed login for {email} from {IP} ({location}) — {N}th attempt in {time}."
  • Geolocation: IP → country code → flag emoji. City if available.
  • Failed login chart: Hourly buckets. Reference line at 5/hour (brute force threshold). Red line. Tooltip: exact count per hour.
  • Success vs failure chart: Side-by-side bars per hour. Green for success, red for failure. Immediate visual of login health.

7. Rationale#

  • Security score: A single number that answers "how secure are we?" 80+ = good, < 50 = panic. Executives and non-technical stakeholders understand a score. Sub-scores tell you where to focus.
  • 10s polling for threats: A brute force attack can fire 100 login attempts in 1 minute. 10s polling catches it fast enough to respond. Failed logins and suspicious sessions are time-sensitive.
  • Event feed: Security events are temporal. A feed is better than a table for real-time monitoring. Each event is a card with context and action.
  • Block IP action: If an IP is hammering the login endpoint, one-click block. 24h default duration. Admin can customize. Immediate response to active threats.
  • 2FA adoption: The most impactful security control. If adoption is < 50%, the platform is vulnerable to credential stuffing. The score and recommendation push admin to enforce 2FA.
  • Recommendations: Not just a score — actionable advice. "Enable 2FA for all admins" with a "Fix" button. Turns the score into a to-do list.
  • Login charts: Visualize attack patterns. If failed logins spike at 3 AM, that's an automated bot. If they spike during business hours, it might be a legitimate user with a wrong password. Pattern recognition.
  • Impersonation events: If an admin starts an impersonation session, it appears in the security feed. Other admins can see that someone is impersonating. Transparency prevents abuse.
  • Vulnerability count: If a vulnerability scanner is integrated (e.g., Snyk, Dependabot), show unpatched count. Red if > 0 means "update your dependencies now."
  • Score recompute: Security score is expensive to compute (queries all users, all sessions, all policies). Cached for 5min. "Recompute" button forces refresh after a policy change.
  • Country flags: Geolocation provides context. A failed login from a country with no users is suspicious. A failed login from the user's home country is probably a typo.

8. Interaction Flows#

  • Block IP: Click "Block IP" on suspicious event → modal: "Block {IP} for how long?" + duration select (1h, 6h, 24h, 7d, permanent) + reason textarea → "Block" → toast "IP {IP} blocked for {duration}." → event updates with "Action taken: IP blocked for 24h" → audit logged.
  • Investigate: Click "Investigate" on session event → navigates to Session Security tab with that session pre-filtered.
  • View details: Click "View Details" on any event → modal with full metadata (request ID, full UA, headers, all failed attempts from that IP).
  • Recompute score: Click "Recompute" → spinner for 2-5 seconds → score updates → toast "Security score recomputed."
  • Fix recommendation: Click "Fix" on recommendation card → navigates to relevant settings page (e.g., /admin/security/settings for 2FA enforcement).
  • Time range: Click "7d" on charts → reloads with 7-day data. Click "30d" → 30-day data.
  • Event filter: Click event type badge in header → feed filters to only that type. Click "All" → reset.
  • Impersonation: Read-only. Security events viewable. Block IP button disabled. Investigate disabled. Cannot recompute score. Score and threats viewable. Charts viewable. No actions.

9. Error States#

  • Loading: Skeleton score gauge + skeleton cards + skeleton feed + skeleton charts.
  • Score unavailable: "Security score unavailable. Check security data collector." → link to /admin/system.
  • No events: "No security events in last 24 hours. Platform is quiet." (good news, shown in green).
  • Block IP fail: Toast "Failed to block IP. IP may already be blocked or firewall is down."
  • Recompute fail: Toast "Score recomputation failed. Try again in 1 minute."
  • All critical: If > 5 CRITICAL events in last hour, red banner at top: "⚠️ {N} critical security events in last hour. Investigate immediately." with link to event feed.
  • Brute force detected: If failed logins from single IP > 20 in 1 hour, auto-alert: "Brute force detected from {IP}. IP automatically blocked for 24h." appears in feed.
  • SSE disconnect: "Live security feed paused. Reconnecting..." → auto-reconnect.
  • No permission: Non-ADMIN redirected.

10. Role-Based Variations#

  • ADMIN: Full access — view score, threats, events, block IPs, investigate, recompute, fix recommendations, time ranges.
  • Other roles: No access.
  • Impersonation: Read-only. Score, threats, events, charts all viewable. No actions (block, investigate, recompute, fix). Event feed shows impersonation events but no action buttons.

14.2 Session Security#

1. Purpose#

Manage all active and recent sessions across the platform. Admins view session details, force logout, and enforce session policies (max duration, concurrent sessions, IP binding). Used for incident response and policy enforcement.

2. Visual Layout#

Section A: Session Policy Settings (top, full width)

  • Card with settings:
    • "Max session duration": Number input (hours). Default: 24. Range: 1-168 (1 hour to 7 days).
    • "Max concurrent sessions per user": Number input. Default: 5. Range: 1-20.
    • "Enforce IP binding": Toggle. If ON, sessions are tied to IP. Changing IP invalidates session (with grace period for mobile users). Default: OFF.
    • "Enforce device fingerprinting": Toggle. If ON, new devices require email verification. Default: OFF.
    • "Idle timeout": Number input (minutes). Default: 30. Range: 5-120.
    • "Admin session max duration": Number input (hours). Default: 8. Range: 1-24. Admins have shorter sessions for security.
  • Each setting has a label + input + helper text + "Save" button (per setting or global save bar).
  • "Reset to defaults" link (secondary) at bottom.

Section B: Active Sessions Table (middle, full width)

  • Same as Performance Monitor's Active Sessions table, but with more security context:
  • Table: User | Role | Started | Last Activity | IP | Location | Device | Browser | Session Duration | Trust Score | Actions
  • Trust Score: 0-100 computed by anomaly detection. Green if > 80, amber if > 50, red if < 50. Factors: known device, known location, time of day, login frequency, 2FA used.
  • Location: City + country flag. "Mumbai, IN 🇮🇳" or "New York, US 🇺🇸".
  • Device: Parsed device type. "Desktop — macOS" or "Mobile — iOS" or "Tablet — Android".
  • Browser: Parsed browser. "Chrome 125" or "Safari 17" or "Firefox 126".
  • Session Duration: "{N} hr {N} min" or "{N} min" since start.
  • Actions: "Terminate" (danger) + "Trust" (mark as trusted, overrides red score) + "Flag" (mark as suspicious, triggers alert).
  • Anomaly highlighting: Sessions with trust score < 50 or flagged get red left border. New sessions from unknown locations get amber left border.
  • Impersonation column: If impersonating, shows "Impersonating: {clientName}" with purple badge.
  • Pagination: 25 per page. Real-time updates (10s polling).
  • Search: Filter by user name, email, IP, or device.

Section C: Recent Session History (bottom, full width)

  • Table of ended sessions (last 24 hours): User | Role | Started | Ended | Duration | End Reason | IP | Location
  • End Reason: LOGOUT (green), EXPIRED (amber), TERMINATED_BY_ADMIN (red), IP_CHANGED (red), IDLE_TIMEOUT (gray), CONCURRENT_LIMIT (gray).
  • Duration: Total session duration. "2 hr 15 min".
  • Pagination: 50 per page.
  • Export: "Export CSV" button.

3. Data Source (tRPC endpoint)#

admin.getSessionPolicies.useQuery(undefined, { refetchInterval: 300000 }); // 5min static
admin.updateSessionPolicy.useMutation({
  maxSessionDurationHours: z.number().min(1).max(168),
  maxConcurrentSessions: z.number().min(1).max(20),
  enforceIpBinding: z.boolean(),
  enforceDeviceFingerprinting: z.boolean(),
  idleTimeoutMinutes: z.number().min(5).max(120),
  adminSessionMaxDurationHours: z.number().min(1).max(24),
});
admin.getActiveSessions.useQuery(undefined, { refetchInterval: 10000 }); // 10s real-time (same as performance)
admin.getSessionHistory.useQuery({
  dateRange: z.object({ from: z.date(), to: z.date() }).optional(),
  page: z.number().min(1).default(1),
  pageSize: z.number().min(1).max(100).default(50),
}, { refetchInterval: 30000 }); // 30s frequent
admin.terminateSession.useMutation({ sessionId: z.string() });
admin.trustSession.useMutation({ sessionId: z.string() });
admin.flagSession.useMutation({ sessionId: z.string(), reason: z.string().optional() });

4. Zod Schema#

const SessionPolicySchema = z.object({
  maxSessionDurationHours: z.number().min(1).max(168).default(24),
  maxConcurrentSessions: z.number().min(1).max(20).default(5),
  enforceIpBinding: z.boolean().default(false),
  enforceDeviceFingerprinting: z.boolean().default(false),
  idleTimeoutMinutes: z.number().min(5).max(120).default(30),
  adminSessionMaxDurationHours: z.number().min(1).max(24).default(8),
});

const SessionTrustScoreSchema = z.object({
  score: z.number().min(0).max(100),
  factors: z.array(z.object({
    name: z.string(), // e.g., "Known device", "Known location"
    weight: z.number(),
    passed: z.boolean(),
  })),
});

const ActiveSessionSchema = z.object({
  sessionId: z.string(),
  userId: z.string().uuid(),
  userName: z.string(),
  userEmail: z.string().email(),
  userRole: z.enum(["ADMIN", "CLIENT", "EDITOR", "VIEWER"]),
  userAvatar: z.string().url().optional(),
  startedAt: z.date(),
  lastActivityAt: z.date(),
  ipAddress: z.string().ip(),
  countryCode: z.string().length(2).optional(),
  city: z.string().optional(),
  userAgent: z.string(),
  parsedDevice: z.string().optional(), // "Desktop — macOS"
  parsedBrowser: z.string().optional(), // "Chrome 125"
  sessionDurationMinutes: z.number(),
  trustScore: SessionTrustScoreSchema,
  isImpersonating: z.boolean().default(false),
  impersonatingClientId: z.string().uuid().optional(),
  impersonatingClientName: z.string().optional(),
  isFlagged: z.boolean().default(false),
  flagReason: z.string().optional(),
});

const SessionHistoryItemSchema = z.object({
  sessionId: z.string(),
  userId: z.string().uuid(),
  userName: z.string(),
  userRole: z.enum(["ADMIN", "CLIENT", "EDITOR", "VIEWER"]),
  startedAt: z.date(),
  endedAt: z.date(),
  durationMinutes: z.number(),
  endReason: z.enum(["LOGOUT", "EXPIRED", "TERMINATED_BY_ADMIN", "IP_CHANGED", "IDLE_TIMEOUT", "CONCURRENT_LIMIT"]),
  ipAddress: z.string().ip(),
  countryCode: z.string().length(2).optional(),
  city: z.string().optional(),
  terminatedBy: z.string().optional(), // admin name if terminated by admin
});

5. Fetch Frequency#

  • Real-time (10000ms / 10s): Active sessions change as users log in/out. 10s polling.
  • Frequent (30000ms / 30s): Session history updates as sessions end. 30s polling.
  • Static (300000ms / 5min): Session policies change rarely. 5min polling.
  • On-demand: Policy update, terminate, trust, flag, filter, pagination.
  • SSE: New session → push to active table. Session ended → push to history table. Session terminated by admin → push update to both.

6. Data Manipulations#

  • Trust score: 0-100. Color: green > 80, amber > 50, red < 50. Hover shows factor breakdown: "Known device: ✓ (+20), Known location: ✓ (+20), Business hours: ✓ (+10), 2FA used: ✓ (+30), Normal login frequency: ✓ (+20) = 100". If unknown location: "Known location: ✗ (-30)" → score drops.
  • Location formatting: "City, Country {flag}". If city unknown, just "Country {flag}". If geolocation unavailable, "Unknown 🌍".
  • Device parsing: User agent → "Desktop — macOS", "Mobile — iOS", "Tablet — Android", "Unknown". Uses a simple UA parser library.
  • Browser parsing: "Chrome 125", "Safari 17", "Firefox 126", "Edge 125", "Unknown".
  • Session duration: < 60min → "{N} min"; < 24hr → "{N} hr {N} min"; >= 24hr → "{N} days {N} hr".
  • End reason badges: LOGOUT = green; EXPIRED = amber; TERMINATED_BY_ADMIN = red + admin name; IP_CHANGED = red; IDLE_TIMEOUT = gray; CONCURRENT_LIMIT = gray.
  • Anomaly highlighting: Trust score < 50 = red left border. Flagged = red left border + red FLAGGED badge. Unknown location = amber left border.
  • Policy save: Per-setting save or global save bar. If per-setting, immediate mutation on change. If global, all changes batched until "Save" clicked.
  • Impersonation column: Purple badge "IMPERSONATING" with client name. Hover shows full impersonation details (admin name, client name, session start).
  • Terminate animation: Row fades out over 500ms when session terminated. Toast confirms action.

7. Rationale#

  • Session security: Sessions are the attack surface. If an attacker steals a session token, they have access. Monitoring sessions is critical for security operations.
  • Trust score: Automated anomaly detection. Admins can't manually review 100+ sessions. Trust score surfaces the suspicious ones. Red = investigate. Green = ignore.
  • 10s polling: Active sessions change fast. Users log in and out constantly. 10s keeps the table accurate.
  • Session policies: Enforceable security controls. Short session duration reduces risk. IP binding prevents session hijacking (though may inconvenience mobile users). Device fingerprinting blocks unknown devices. Idle timeout prevents abandoned sessions.
  • Admin session shorter: Admin accounts are higher value. 8-hour max vs 24-hour for clients. Admin sessions should be shorter but not so short they become annoying.
  • Terminate action: Incident response. If a session is compromised, terminate it immediately. One-click. The user is logged out instantly. Their next request gets 401.
  • Flag action: If a session looks suspicious but admin isn't sure, flag it. Flagged sessions trigger alerts in the Security Dashboard. Other admins see the flag and investigate.
  • Trust action: If a session is flagged by anomaly detection but admin knows it's legitimate (e.g., user is traveling), mark it as trusted. Overrides the red score. Prevents false positive alerts.
  • Session history: Post-incident review. "When did the attacker log in? How long was their session active? Where did they connect from?" History answers all of this.
  • Concurrent limit: Prevents session abuse. If a user shares credentials, they'll hit the concurrent limit. Forces credential sharing to stop or use proper team accounts.
  • IP binding: Strong security control. If a session token is stolen, it can't be used from a different IP. Trade-off: mobile users on cellular networks change IPs frequently. Grace period (e.g., 1 hour) allows legitimate IP changes.
  • Device fingerprinting: New device = email verification. Prevents "I found a laptop with a saved password, let me log in" attacks. The real user gets an email alert.
  • Geolocation: Context for trust score. A login from a new country is suspicious. A login from the user's home city is normal. Flags help admin understand why a score is low.

8. Interaction Flows#

  • Update policy: Change "Max session duration" to 12 → immediate mutation (if per-setting) or amber dot + save bar (if global) → toast "Session policy updated. Affects new sessions immediately. Existing sessions expire at their original time."
  • Terminate session: Click "Terminate" on suspicious row → confirmation: "End session for {userName}? They will be logged out immediately." → "Terminate" → row fades out → toast "Session terminated." → history table updates with TERMINATED_BY_ADMIN reason.
  • Flag session: Click "Flag" → optional reason textarea → "Flag" → row gets red border + FLAGGED badge → toast "Session flagged. Other admins notified." → alert appears in Security Dashboard.
  • Trust session: Click "Trust" on flagged row → confirmation: "Trust this session? It will no longer trigger alerts." → "Trust" → score turns green → flag removed → toast "Session trusted."
  • Search: Type user name or IP → table filters live (debounced 300ms).
  • View history: Scroll to bottom section → pagination through ended sessions. Click "Export CSV" → download.
  • Impersonation: Read-only. Session policies viewable but cannot change. Active sessions viewable. Terminate, flag, trust all disabled. History viewable. No actions.

9. Error States#

  • Loading: Skeleton policy form + skeleton table (10 rows) + skeleton history (5 rows).
  • Policy update fail: Toast "Failed to update session policy. Value must be between {min} and {max}."
  • Terminate fail: Toast "Failed to terminate session. Session may have already ended."
  • No active sessions: "No active sessions. Platform is idle or session tracking is unavailable."
  • History empty: "No session history for selected period."
  • Flag fail: Toast "Failed to flag session. It may have already ended."
  • Trust fail: Toast "Failed to trust session. It may have already ended."
  • Geolocation unavailable: Location column shows "Unknown 🌍" for all sessions. Banner: "IP geolocation service unavailable. Location data is approximate."
  • No permission: Non-ADMIN redirected.

10. Role-Based Variations#

  • ADMIN: Full access — view policies, update policies, terminate, flag, trust, search, view history, export.
  • Other roles: No access.
  • Impersonation: Read-only. Policies viewable. Active sessions viewable. History viewable. No modifications. No terminations. No flagging.

14.3 Security Settings#

1. Purpose#

Configure platform-wide security policies — password rules, 2FA enforcement, API key policies, CORS, CSP, and encryption settings. The "security configuration" screen.

2. Visual Layout#

Section A: Password Policy (top, 50% width)

  • Minimum length: Number input. Default: 12. Range: 8-32.
  • Require uppercase: Toggle. Default: ON.
  • Require lowercase: Toggle. Default: ON.
  • Require numbers: Toggle. Default: ON.
  • Require special characters: Toggle. Default: ON.
  • Password history: Number input. "Prevent reuse of last {N} passwords." Default: 5. Range: 1-20.
  • Max age: Number input (days). "Require password change every {N} days." Default: 90. Range: 30-365. 0 = never.
  • Breach detection: Toggle. "Check passwords against Have I Been Pwned database." Default: ON.
  • Each setting: label + input/toggle + helper text. Edited fields show amber dot.
  • "Save Password Policy" button (ember, sticky at bottom of section).

Section B: Two-Factor Authentication (top, 50% width)

  • 2FA requirement: Radio group — "Optional" (default), "Recommended" (prompts users), "Required for admins", "Required for all users".
  • Allowed methods: Multi-select — "TOTP (Authenticator app)" (default ON), "SMS" (default OFF), "Email OTP" (default OFF), "WebAuthn/Security Key" (default OFF).
  • Grace period: Number input (days). "Users have {N} days to set up 2FA before enforcement." Default: 7. Only visible if requirement is not "Optional".
  • Backup codes: Number input. "Generate {N} backup codes." Default: 10. Range: 5-20.
  • 2FA stats: "Current adoption: {N}% ({N} of {N} users)." Green if > 80%, amber if > 50%, red if < 50%. Bar chart below: admins vs clients vs editors vs viewers.
  • "Save 2FA Policy" button (ember).

Section C: API Security (bottom, full width)

  • API rate limit (per client): Number input. "{N} requests per minute." Default: 60. Range: 10-1000.
  • API rate limit (global): Number input. "{N} requests per minute across all clients." Default: 1000.
  • API key rotation: Number input (days). "Auto-rotate keys every {N} days." Default: 90. Range: 30-365. 0 = never.
  • IP whitelist for API: Textarea. One IP per line. "Leave empty to allow all IPs." CIDR notation supported.
  • CORS origins: Textarea. One origin per line. "Allowed origins for API requests." Default: platform domain.
  • Webhook signature verification: Toggle. "Require Stripe/webhook signature verification." Default: ON.
  • "Save API Security" button (ember).

Section D: Encryption & Data Protection (bottom, full width)

  • Data at rest encryption: Status badge. "AES-256 (managed by database)" — always ON, not configurable. Gray badge.
  • Data in transit: Status badge. "TLS 1.3 (always ON)" — always ON. Gray badge.
  • Field-level encryption: Multi-select. "Encrypt these fields at application level:" — PII fields (client phone, lead emails, etc.). Toggle per field.
  • Backup encryption: Toggle. "Encrypt backup snapshots." Default: ON.
  • "Save Encryption Settings" button (ember).

3. Data Source (tRPC endpoint)#

admin.getSecuritySettings.useQuery(undefined, { refetchInterval: 300000 }); // 5min static
admin.updatePasswordPolicy.useMutation({
  minLength: z.number().min(8).max(32),
  requireUppercase: z.boolean(),
  requireLowercase: z.boolean(),
  requireNumbers: z.boolean(),
  requireSpecialChars: z.boolean(),
  passwordHistory: z.number().min(1).max(20),
  maxAgeDays: z.number().min(0).max(365),
  breachDetection: z.boolean(),
});
admin.updateTwoFactorPolicy.useMutation({
  requirement: z.enum(["OPTIONAL", "RECOMMENDED", "REQUIRED_ADMINS", "REQUIRED_ALL"]),
  allowedMethods: z.array(z.enum(["TOTP", "SMS", "EMAIL", "WEBAUTHN"])),
  gracePeriodDays: z.number().min(0).max(30),
  backupCodesCount: z.number().min(5).max(20),
});
admin.updateApiSecurity.useMutation({
  clientRateLimitPerMin: z.number().min(10).max(1000),
  globalRateLimitPerMin: z.number().min(100).max(10000),
  apiKeyRotationDays: z.number().min(0).max(365),
  ipWhitelist: z.array(z.string()),
  corsOrigins: z.array(z.string()),
  webhookSignatureVerification: z.boolean(),
});
admin.updateEncryptionSettings.useMutation({
  fieldLevelEncryption: z.array(z.string()), // field names
  backupEncryption: z.boolean(),
});

4. Zod Schema#

const PasswordPolicySchema = z.object({
  minLength: z.number().min(8).max(32).default(12),
  requireUppercase: z.boolean().default(true),
  requireLowercase: z.boolean().default(true),
  requireNumbers: z.boolean().default(true),
  requireSpecialChars: z.boolean().default(true),
  passwordHistory: z.number().min(1).max(20).default(5),
  maxAgeDays: z.number().min(0).max(365).default(90),
  breachDetection: z.boolean().default(true),
});

const TwoFactorPolicySchema = z.object({
  requirement: z.enum(["OPTIONAL", "RECOMMENDED", "REQUIRED_ADMINS", "REQUIRED_ALL"]).default("OPTIONAL"),
  allowedMethods: z.array(z.enum(["TOTP", "SMS", "EMAIL", "WEBAUTHN"])).default(["TOTP"]),
  gracePeriodDays: z.number().min(0).max(30).default(7),
  backupCodesCount: z.number().min(5).max(20).default(10),
  adoptionPercent: z.number().optional(), // computed
  adoptionByRole: z.record(z.number()).optional(), // { ADMIN: 85, CLIENT: 45, ... }
});

const ApiSecuritySchema = z.object({
  clientRateLimitPerMin: z.number().min(10).max(1000).default(60),
  globalRateLimitPerMin: z.number().min(100).max(10000).default(1000),
  apiKeyRotationDays: z.number().min(0).max(365).default(90),
  ipWhitelist: z.array(z.string()).default([]),
  corsOrigins: z.array(z.string()).default([]),
  webhookSignatureVerification: z.boolean().default(true),
});

const EncryptionSettingsSchema = z.object({
  dataAtRest: z.literal("AES-256").default("AES-256"), // always on, not configurable
  dataInTransit: z.literal("TLS-1.3").default("TLS-1.3"), // always on, not configurable
  fieldLevelEncryption: z.array(z.string()).default([]), // field names to encrypt
  backupEncryption: z.boolean().default(true),
});

5. Fetch Frequency#

  • Static (300000ms / 5min): Security settings change rarely (policy changes are deliberate). 5min polling.
  • On-demand: After any policy update mutation, invalidate and refetch.

6. Data Manipulations#

  • Policy save: Per-section save buttons (Password, 2FA, API, Encryption). Each section saves independently. No global save bar. This prevents accidentally changing multiple policies at once.
  • Amber dots: Edited fields show amber dot. Save button enabled only when changes detected in that section.
  • 2FA adoption stats: Bar chart showing adoption % by role. Admins usually higher (forced), clients lower (optional). Color: green > 80%, amber > 50%, red < 50%.
  • Grace period: Only visible if requirement is not "Optional". Hidden otherwise to reduce UI clutter.
  • IP whitelist: Textarea validates each line. Invalid IP/CIDR shows red inline error. "203.0.113.0/24" is valid. "not-an-ip" is invalid.
  • CORS origins: Validates URL format. "https://rankflow.ai" valid. "rankflow.ai" invalid (missing protocol).
  • Field-level encryption: Checkboxes for sensitive fields. Client phone, lead email, lead phone, admin API keys, etc. Each with a "why encrypt?" tooltip explaining compliance benefit.
  • Always-on settings: Data at rest and in transit are always ON. Shown as gray badges with lock icon. Not editable. Communicates that encryption is baseline, not optional.
  • Breach detection: If ON, password changes check HIBP API. If password is in a breach, user is warned: "This password has been seen in {N} data breaches. Choose a different one."

7. Rationale#

  • Per-section saves: Password policy, 2FA policy, API security, and encryption are independent domains. Changing password requirements shouldn't accidentally change CORS origins. Separate saves prevent cross-contamination.
  • Password policy: NIST guidelines recommend minimum 12 chars, no complexity requirements (but we offer toggles for flexibility). Breach detection is modern best practice — prevents users from using "password123" if it's in a known breach.
  • 2FA enforcement tiers: "Optional" = users can ignore it. "Recommended" = nudges users but doesn't force. "Required for admins" = admin accounts must have 2FA (high-value targets). "Required for all" = everyone must have 2FA (maximum security, but may cause support tickets).
  • 2FA adoption stats: If requirement is "Required for all" but adoption is 45%, there are 55% of users who haven't set it up yet. They'll be locked out when grace period ends. Admin needs to see this and send reminders.
  • API rate limits: Per-client limit prevents one client from DDoS-ing the API. Global limit prevents total platform overload. Both are needed for multi-tenant platforms.
  • IP whitelist: If API is only used from known servers, whitelist those IPs. Blocks all other IPs even with valid API keys. Strong security control for server-to-server integrations.
  • CORS origins: Prevents API calls from unauthorized domains. If attacker hosts a phishing site, it can't call the API due to CORS.
  • Webhook signature verification: Stripe and other webhooks send signed payloads. Verifying signatures prevents attackers from forging webhook events (e.g., fake payment confirmations).
  • Field-level encryption: Database-level encryption (AES-256) protects against disk theft. Application-level encryption protects against DB credential compromise. If attacker gets DB access, they still can't read encrypted PII without the application key.
  • Backup encryption: Backups are a goldmine for attackers. Encrypting them means even if backup storage is compromised, data is safe.
  • 5min polling: Security settings are almost static. Changes are policy decisions, not events. No need for real-time.
  • Always-on encryption: Baseline security. Users should know it's there, even if they can't change it. Builds trust.

8. Interaction Flows#

  • Edit password policy: Change min length to 14 → amber dot appears → "Save Password Policy" enabled → click → mutation → toast "Password policy updated. Affects new passwords immediately. Existing passwords valid until changed."
  • Change 2FA requirement: Select "Required for all" → grace period input appears (default 7 days) → "Save 2FA Policy" → confirmation: "All users will be required to enable 2FA within {N} days. Users without 2FA will be locked out after grace period." → "Save" → toast "2FA policy updated. Users notified." → adoption stats update.
  • Add IP to whitelist: Type "203.0.113.0/24" in textarea → inline validation → green checkmark → "Save API Security" → toast "API security updated."
  • Add CORS origin: Type "https://app.rankflow.ai" → validation → save → toast "CORS origins updated."
  • Toggle field encryption: Check "Lead email" → "Save Encryption Settings" → confirmation: "Encrypting lead emails will affect search and filtering performance. Continue?" → "Save" → toast "Field-level encryption updated. Lead emails now encrypted at application level."
  • Invalid input: If IP is invalid, red inline error: "Invalid IP address or CIDR." If CORS origin is invalid, "Must be a valid URL with protocol."
  • Impersonation: Read-only. All settings viewable. All inputs disabled. All save buttons hidden. Cannot modify any security policy. Cannot view 2FA setup QR codes or backup codes (sensitive).

9. Error States#

  • Loading: Skeleton form with 4 sections (shimmer rows).
  • Policy save fail: Toast "Failed to save {section} policy. {error}." Form keeps changes (amber dots remain).
  • Invalid IP: Inline red text below textarea. "Line 3: 'not-an-ip' is not a valid IP address."
  • Invalid CORS: Inline red text. "Line 2: 'rankflow.ai' must include protocol (https://)."
  • 2FA conflict: If requirement is "Required for all" but allowed methods is empty, validation error: "At least one 2FA method must be enabled."
  • Grace period warning: If grace period is 0 and requirement is not Optional, amber banner: "Users without 2FA will be locked out immediately. Consider a grace period for onboarding."
  • Concurrent edit: If another admin changes policy while you're editing, banner: "Security settings were updated by {name} at {time}. Reload to see latest values."
  • No permission: Non-ADMIN redirected.

10. Role-Based Variations#

  • ADMIN: Full access — view, edit, save all security policies. Can view 2FA adoption stats (not individual 2FA secrets).
  • Other roles: No access.
  • Impersonation: Read-only. All policies viewable. No modifications. Cannot view sensitive 2FA data (QR codes, backup codes, secrets). Cannot change any policy. Cannot rotate API keys.