Tier 1.5 Email Authentication with NocoDB Session Tracking

Added email-based authentication as an alternative to passcode, with domain allowlisting and NocoDB session tracking.

Claude
Claude Claude

Changelog - 2025-12-25 (#2)

Dark Matter Site: Tier 1.5 Email Authentication with NocoDB

Overview

Implemented a hybrid authentication system that offers both passcode and email-based access to confidential content. Users with approved email domains (e.g., @darkmatter.vc) get instant access, while all sessions are logged to NocoDB for analytics and audit purposes.

Key deliverables:

  • Dual Authentication Methods: Tab switcher for passcode vs email
  • Domain Allowlisting: Instant access for approved email domains
  • NocoDB Session Tracking: All access sessions logged with timestamps
  • Mode-Responsive Tabs: Tab UI adapts to light/dark/vibrant themes
  • Blueprint Documentation: Updated with Tier 1.5 specification

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                        Email Authentication Flow                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│   User visits /portfolio/confidential                                        │
│         │                                                                    │
│         ▼                                                                    │
│   Has auth cookie? ────Yes───▶ Show confidential content                    │
│         │                                                                    │
│         No                                                                   │
│         │                                                                    │
│         ▼                                                                    │
│   Redirect to /portfolio-gate                                                │
│         │                                                                    │
│         ▼                                                                    │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                    Tab Switcher                                      │   │
│   │   ┌─────────────────────┐   ┌─────────────────────┐                 │   │
│   │   │     Passcode        │   │       Email         │                 │   │
│   │   │   (existing)        │   │   (new option)      │                 │   │
│   │   └─────────────────────┘   └─────────────────────┘                 │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│         │                               │                                    │
│         ▼                               ▼                                    │
│   /api/verify-portfolio-passcode   /api/verify-email                        │
│         │                               │                                    │
│         │                               ▼                                    │
│         │                    ┌────────────────────────┐                     │
│         │                    │ checkEmailAccess()     │                     │
│         │                    │                        │                     │
│         │                    │ 1. Domain allowlist?   │                     │
│         │                    │    └─ Yes → allowed    │                     │
│         │                    │                        │                     │
│         │                    │ 2. Previous session?   │                     │
│         │                    │    └─ Yes → allowed    │                     │
│         │                    │                        │                     │
│         │                    │ 3. New email → pending │                     │
│         │                    └────────────────────────┘                     │
│         │                               │                                    │
│         │                               ▼                                    │
│         │                    createEmailAccessSession()                     │
│         │                               │                                    │
│         │                               ▼                                    │
│         │                    ┌────────────────────────┐                     │
│         │                    │      NocoDB            │                     │
│         │                    │   emailAccess table    │                     │
│         │                    │                        │                     │
│         │                    │ • emailOfAccessor      │                     │
│         │                    │ • sessionStartTime     │                     │
│         │                    │ • sessionEndTime       │                     │
│         │                    └────────────────────────┘                     │
│         │                               │                                    │
│         ▼                               ▼                                    │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                    Set auth cookie                                   │   │
│   │                    Redirect to content                               │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Changes by Area

1. NocoDB Email Access Table

Added new table configuration for session tracking:

Table ID: ms0dzr6telg2cxu

Column Type Purpose
emailOfAccessor Text User's email address
sessionStartTime DateTime When access session began
sessionEndTime DateTime When session ended (optional)

2. NocoDB Library Extension (src/lib/nocodb.ts)

Added email access control functions:

// Table registration
export const NOCODB_TABLES = {
  organizations: 'myxl4ug85sr1d4p',
  materials: 'mruw5fu5cthdwkl',
  emailAccess: 'ms0dzr6telg2cxu',  // NEW
} as const;

// Types
export interface EmailAccessFields {
  emailOfAccessor: string;
  sessionStartTime: string;
  sessionEndTime?: string | null;
}

export type EmailAccessStatus = 'domain_allowed' | 'approved' | 'pending' | 'new';

// Functions
export function isAllowedDomain(email: string): boolean
export async function checkEmailAccess(email: string): Promise<EmailAccessResult>
export async function createEmailAccessSession(email: string): Promise<{...}>
export async function fetchEmailAccessSessions(options?: {...}): Promise<[...]>
export async function endEmailAccessSession(recordId: number): Promise<{...}>

Domain Allowlist Configuration:

// Default allowed domains (or set via ALLOWED_EMAIL_DOMAINS env var)
function getAllowedDomains(): string[] {
  const envDomains = import.meta.env.ALLOWED_EMAIL_DOMAINS;
  if (envDomains) {
    return envDomains.split(',').map((d: string) => d.trim().toLowerCase());
  }
  return [
    'darkmatter.vc',
    'darkmatterlongevity.com',
    'lossless.group',
  ];
}

3. Email Verification API (src/pages/api/verify-email.ts)

New API endpoint for email-based authentication:

export const POST: APIRoute = async ({ request, cookies, redirect }) => {
  const email = formData.get('email');

  // Check email access (domain allowlist + previous sessions)
  const accessResult = await checkEmailAccess(email);

  if (accessResult.allowed) {
    // Create session record in NocoDB
    await createEmailAccessSession(email);

    // Set auth cookie
    cookies.set('universal_portfolio_access', sessionToken, {
      httpOnly: true,
      secure: import.meta.env.PROD,
      maxAge: 60 * 60 * 24, // 24 hours
    });

    // Also store email for tracking
    cookies.set('accessor_email', email, {...});

    return redirect(redirectTo);
  }

  // Not allowed - log request anyway, show pending message
  await createEmailAccessSession(email);
  return redirect('/portfolio-gate?error=pending&email=' + email);
};

4. Gate Page Tab Switcher (src/pages/portfolio-gate.astro)

Added dual-method authentication UI:

Tab Structure:

<div class="auth-tabs" role="tablist">
  <button data-tab="passcode" class:list={['auth-tab', method === 'passcode' && 'active']}>
    Passcode
  </button>
  <button data-tab="email" class:list={['auth-tab', method === 'email' && 'active']}>
    Email
  </button>
</div>

<!-- Passcode Form (existing) -->
<form id="passcode-form" action="/api/verify-portfolio-passcode">...</form>

<!-- Email Form (new) -->
<form id="email-form" action="/api/verify-email">
  <input type="email" name="email" placeholder="you@company.com" />
  <button type="submit">Access with Email</button>
  <p>Team members and approved partners get instant access.</p>
</form>

Error States:

{error === 'invalid' && (
  <div class="error">Invalid passcode.</div>
)}

{error === 'invalid-email' && (
  <div class="error">Please enter a valid email address.</div>
)}

{error === 'pending' && (
  <div class="warning">
    Your access request has been logged. Please contact us for approval.
  </div>
)}

5. Mode-Responsive Tab Styling

Added CSS that adapts to light/dark/vibrant themes:

.auth-tabs {
  display: flex;
  padding: 0.25rem;
  border-radius: 0.75rem;
  background: var(--color-muted);
  border: 1px solid var(--color-border);
}

.auth-tab {
  flex: 1;
  padding: 0.625rem 1rem;
  color: var(--color-muted-foreground);
  background: transparent;
  transition: all 0.2s ease;
}

.auth-tab.active {
  background: var(--color-primary);
  color: var(--color-primary-foreground);
}

/* Dark mode adjustments */
:global([data-mode="dark"]) .auth-tabs {
  background: rgba(31, 41, 55, 0.5);
}

:global([data-mode="dark"]) .auth-tab.active {
  box-shadow: 0 0 12px rgba(156, 133, 223, 0.2);
}

/* Vibrant mode - signature purple glow */
:global([data-mode="vibrant"]) .auth-tabs {
  background: rgba(15, 9, 35, 0.7);
  border-color: rgba(102, 67, 226, 0.4);
  box-shadow: 0 0 20px rgba(102, 67, 226, 0.1);
}

:global([data-mode="vibrant"]) .auth-tab.active {
  background: var(--color-marjorelle-purple);
  box-shadow:
    0 0 20px rgba(102, 67, 226, 0.4),
    0 0 40px rgba(102, 67, 226, 0.2);
}

6. Environment Configuration

Updated .env.example with new option:

# Email Access Control
# --------------------
# Comma-separated list of domains that get instant access
ALLOWED_EMAIL_DOMAINS=darkmatter.vc,darkmatterlongevity.com,lossless.group

7. Blueprint Documentation

Updated Confidential-Content-Access-Control-Blueprint.md:

  • Added Tier 1.5 section with full implementation details
  • Updated comparison matrix to include Tier 1.5
  • Updated Dark-matter implementation status
  • Added authentication flow diagram

Files Changed Summary

New

File Purpose
src/pages/api/verify-email.ts Email verification API endpoint

Modified

File Changes
src/lib/nocodb.ts Added emailAccess table, access control functions
src/pages/portfolio-gate.astro Tab switcher UI, email form, mode-responsive styles
.env.example Added ALLOWED_EMAIL_DOMAINS config
context-v/Confidential-Content-Access-Control-Blueprint.md Tier 1.5 documentation

Authentication Flow Summary

┌────────────────────────────────────────────────────────────────────────┐
│                        Access Decision Matrix                           │
├────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│   Input                    Check                    Result              │
│   ─────                    ─────                    ──────              │
│                                                                         │
│   user@darkmatter.vc       Domain allowlist         ✅ Instant access   │
│   user@lossless.group      Domain allowlist         ✅ Instant access   │
│   user@partner.com         Previous session?        ✅ Access (if yes)  │
│   user@unknown.com         New email                ⏳ Pending + logged │
│                                                                         │
└────────────────────────────────────────────────────────────────────────┘

NocoDB Session Data

Each email access creates a record:

{
  "emailOfAccessor": "investor@partner.com",
  "sessionStartTime": "2025-12-25T14:30:00.000Z",
  "sessionEndTime": null
}

This enables:

  • Access audit trail: Who accessed when
  • Session analytics: Time on site (when sessionEndTime is set)
  • Approval workflow: Review pending requests in NocoDB UI
  • Domain pattern analysis: Identify new partner domains to add to allowlist

Notes / Follow-Ups

  1. Session End Tracking: The endEmailAccessSession() function exists but isn't wired up to page unload yet. Could use beforeunload event or Beacon API.

  2. Approval Workflow: Non-allowed domains are logged but require manual NocoDB review. Could add admin notification via webhook.

  3. Rate Limiting: No rate limiting on email submissions yet. Consider adding to prevent abuse.

  4. Email Validation: Currently just checks for @ symbol. Could add more robust email validation.

  5. Previous Session Logic: Currently any previous session = approved. Could add time-based expiration for old sessions.

  6. Access at: /portfolio-gate?method=email