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
-
Session End Tracking: The
endEmailAccessSession()function exists but isn't wired up to page unload yet. Could usebeforeunloadevent or Beacon API. -
Approval Workflow: Non-allowed domains are logged but require manual NocoDB review. Could add admin notification via webhook.
-
Rate Limiting: No rate limiting on email submissions yet. Consider adding to prevent abuse.
-
Email Validation: Currently just checks for
@symbol. Could add more robust email validation. -
Previous Session Logic: Currently any previous session = approved. Could add time-based expiration for old sessions.
-
Access at:
/portfolio-gate?method=email