Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/setup-heady/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ runs:
uses: pnpm/action-setup@v4
with:
version: 9.15.0
standalone: true
- name: Setup Node 22 LTS
uses: actions/setup-node@v4
with:
Expand Down
33 changes: 14 additions & 19 deletions .github/workflows/hcfullpipeline-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,29 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
node-version: [22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- name: Setup Heady
uses: ./.github/actions/setup-heady
- run: pnpm install --no-frozen-lockfile
- name: Lint
run: npm run lint 2>/dev/null || echo "Lint configured via eslint"
run: pnpm run lint 2>/dev/null || echo "Lint configured via eslint"
- name: Test
run: npm test
run: pnpm test
- name: Build
run: npm run build 2>/dev/null || echo "No build step"
run: pnpm run build 2>/dev/null || echo "No build step"

security-scan:
name: Security Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: NPM Audit
run: npm audit --audit-level=high 2>/dev/null || true
- name: Setup Heady
uses: ./.github/actions/setup-heady
- run: pnpm install --no-frozen-lockfile
- name: Pnpm Audit
run: pnpm audit --audit-level=high 2>/dev/null || true
- name: Secrets Scan
run: |
# Scan for hardcoded secrets
Expand All @@ -62,9 +58,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Heady
uses: ./.github/actions/setup-heady
- name: Validate YAML configs
run: |
npx yaml-lint configs/hcfullpipeline.yaml 2>/dev/null || echo "YAML valid"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/package-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ jobs:
- name: Setup Heady
uses: ./.github/actions/setup-heady
- run: pnpm install --no-frozen-lockfile
- run: pnpm run build --filter="./packages/**"
- run: pnpm -r --filter "./packages/**" run build
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,3 @@ configs/secrets/
secrets_registry.json

# ── Loose credential files ─────────────────────────────
!package-lock.json
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ enable-pre-post-scripts=true
prefer-frozen-lockfile=true
side-effects-cache=true
engine-strict=true
use-node-version=20.18.0
use-node-version=22.22.1
6 changes: 4 additions & 2 deletions agents/headybee-swarm.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ const FIBONACCI = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144];
*/
class Config {
constructor() {
this.redisUrl = process.env.REDIS_URL || 'rediss://localhost:6379';
this.postgresUrl = process.env.DATABASE_URL || 'postgresql://localhost/heady';
this.redisUrl = process.env.REDIS_URL || process.env.UPSTASH_REDIS_URL;
this.postgresUrl = process.env.DATABASE_URL || process.env.NEON_DATABASE_URL;
if (!this.redisUrl) throw new Error('Missing REDIS_URL or UPSTASH_REDIS_URL environment variable');
if (!this.postgresUrl) throw new Error('Missing DATABASE_URL or NEON_DATABASE_URL environment variable');
this.pineconeApiKey = process.env.PINECONE_API_KEY || '';
this.pineconeIndex = process.env.PINECONE_INDEX || 'task-router';
this.nodeEnv = process.env.NODE_ENV || 'production';
Expand Down
243 changes: 240 additions & 3 deletions cloudflare/worker-heady-router/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,12 @@ const MODULE_BRANDS = {
ctaHref: 'https://headysystems.com',
},
'auth-portal': {
brand: 'Heady Auth',
brand: 'HeadyKey',
title: 'Centralized sign-in',
description: 'Unified authentication entrypoint for Heady domains and service access.',
accent: '#7c5eff',
ctaLabel: 'Open sign-in hub',
ctaHref: 'https://headyme.com',
ctaLabel: 'Sign in',
ctaHref: 'https://auth.headysystems.com/login',
},
'edge-mcp': {
brand: 'Heady Edge',
Expand Down Expand Up @@ -354,6 +354,224 @@ async function tryCompiler(url, hostname, moduleName, env) {
return response.text();
}

// ─── Auth Portal Pages ─────────────────────────────────────────────
// Served directly at the edge for auth.headysystems.com so the login
// and relay pages never fall through to the marketing-site origin.

function renderAuthLoginPage() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HeadyKey — Sign In</title>
<meta name="description" content="Secure centralized authentication for the Heady ecosystem.">
<meta property="og:title" content="HeadyKey — Sign In">
<meta property="og:description" content="Central authentication relay for all Heady properties.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://auth.headysystems.com/login">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#060814;--surface:rgba(13,13,26,0.88);--text:#e5eefb;--text-secondary:#9fb0c9;--text-tertiary:#6b7e99;--text-muted:#4a5568;--border:rgba(255,255,255,0.08);--accent:#7c5eff;--accent-glow:rgba(124,94,255,0.25);--text-sm:0.9rem;--text-xs:0.75rem}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Inter,ui-sans-serif,system-ui,sans-serif;color:var(--text);background:radial-gradient(circle at top left,rgba(124,94,255,0.15),transparent 40%),radial-gradient(circle at bottom right,rgba(64,224,208,0.1),transparent 35%),linear-gradient(180deg,#050816,#0b1220);display:flex;align-items:center;justify-content:center;min-height:100vh;padding:2rem}
.auth-container{width:100%;max-width:460px;position:relative;z-index:1}
.auth-card{background:var(--surface);backdrop-filter:blur(40px);border:1px solid var(--border);border-radius:21px;padding:34px}
.auth-logo{font-family:'JetBrains Mono',monospace;font-size:2rem;font-weight:700;text-align:center;letter-spacing:6px;background:linear-gradient(135deg,#7c5eff,#40e0d0,#f0c040);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin-bottom:4px}
.auth-product{text-align:center;font-size:var(--text-xs);color:var(--accent);letter-spacing:0.12em;text-transform:uppercase;margin-bottom:8px}
.auth-subtitle{text-align:center;color:var(--text-secondary);font-size:var(--text-sm);margin-bottom:21px}
.auth-tabs{display:flex;gap:2px;background:rgba(255,255,255,0.03);border-radius:8px;padding:2px;margin-bottom:21px}
.auth-tab{flex:1;padding:10px;background:none;border:none;color:var(--text-tertiary);font-size:var(--text-sm);font-weight:500;cursor:pointer;border-radius:6px;transition:all .2s;font-family:inherit}
.auth-tab.active{background:rgba(255,255,255,0.06);color:var(--text)}
.auth-field{margin-bottom:16px}
.auth-field label{display:block;font-size:var(--text-xs);color:var(--text-secondary);margin-bottom:6px;text-transform:uppercase;letter-spacing:0.08em}
.auth-field input{width:100%;padding:12px 16px;background:rgba(255,255,255,0.03);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:var(--text-sm);transition:border-color .2s;font-family:inherit}
.auth-field input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(124,94,255,0.15)}
.auth-submit,.auth-provider,.auth-guest{width:100%;padding:14px;border:none;border-radius:8px;font-size:var(--text-sm);font-weight:600;cursor:pointer;transition:all .2s;font-family:inherit}
.auth-submit{background:var(--accent);color:#fff;margin-top:8px}
.auth-submit:hover{box-shadow:0 0 21px var(--accent-glow);transform:translateY(-1px)}
.auth-provider{background:rgba(255,255,255,0.03);border:1px solid var(--border);color:var(--text-secondary);margin-top:10px}
.auth-provider:hover{background:rgba(255,255,255,0.06);color:var(--text)}
.auth-guest{background:transparent;color:var(--text-tertiary);margin-top:10px;border:1px dashed var(--border)}
.auth-footer{text-align:center;margin-top:21px;font-size:.75rem;color:var(--text-muted)}
.auth-footer a{color:var(--text-muted)}
.auth-meta{margin:16px 0 0;padding:12px;background:rgba(255,255,255,0.02);border-radius:8px;font-size:var(--text-xs);color:var(--text-tertiary);line-height:1.5}
.auth-status{margin-top:12px;min-height:20px;font-size:var(--text-xs);color:var(--text-secondary);text-align:center}
.allowed-domains{display:flex;flex-wrap:wrap;gap:4px;justify-content:center;margin-top:13px}
.allowed-domains .tag{font-size:9px;padding:2px 6px;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:4px;color:var(--text-tertiary)}
</style>
</head>
<body>
<div class="auth-container">
<div class="auth-card">
<div class="auth-logo">HEADY</div>
<div class="auth-product">HeadyKey</div>
<p class="auth-subtitle">Authenticate once. Return safely to your selected Heady property.</p>
<div class="auth-tabs">
<button class="auth-tab active" data-tab="login" type="button">Sign In</button>
<button class="auth-tab" data-tab="signup" type="button">Create Account</button>
</div>
<form id="auth-form">
<div class="auth-field">
<label for="auth-email">Email</label>
<input type="email" id="auth-email" placeholder="you@example.com" required autocomplete="email">
</div>
<div class="auth-field">
<label for="auth-password">Password</label>
<input type="password" id="auth-password" placeholder="Your secure password" required autocomplete="current-password">
</div>
<button type="submit" class="auth-submit" id="auth-submit-btn">Sign In</button>
</form>
<button class="auth-provider" type="button" id="google-btn">Continue with Google</button>
<button class="auth-provider" type="button" id="github-btn">Continue with GitHub</button>
<button class="auth-guest" type="button" id="guest-btn">Continue as Guest</button>
<div class="auth-meta" id="auth-meta"></div>
<div class="auth-status" id="auth-status"></div>
<div class="allowed-domains">
<span class="tag">headyme.com</span>
<span class="tag">headysystems.com</span>
<span class="tag">heady-ai.com</span>
<span class="tag">headyos.com</span>
<span class="tag">headyconnection.org</span>
<span class="tag">headybuddy.org</span>
<span class="tag">headymcp.com</span>
<span class="tag">headykey.com</span>
<span class="tag">admin.headysystems.com</span>
</div>
<p class="auth-footer">
Central auth domain: <strong>auth.headysystems.com</strong><br>
Cookie model: httpOnly &middot; Secure &middot; SameSite=Strict<br>
Powered by <a href="https://headykey.com">HeadyKey</a>
</p>
</div>
</div>
<script>
const params = new URLSearchParams(window.location.search);
const redirectUrl = params.get('redirect') || 'https://headysystems.com';
const state = params.get('state') || crypto.randomUUID();
const nonce = params.get('nonce') || crypto.randomUUID();
let mode = params.get('mode') === 'signup' ? 'signup' : 'login';

const meta = document.getElementById('auth-meta');
const statusEl = document.getElementById('auth-status');
const submitBtn = document.getElementById('auth-submit-btn');

function setStatus(msg, isError) {
statusEl.textContent = msg;
statusEl.style.color = isError ? '#f38ba8' : 'var(--text-secondary)';
}

function syncModeUI() {
document.querySelectorAll('.auth-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === mode));
submitBtn.textContent = mode === 'signup' ? 'Create Account' : 'Sign In';
document.getElementById('auth-password').autocomplete = mode === 'signup' ? 'new-password' : 'current-password';
}

document.querySelectorAll('.auth-tab').forEach(t => {
t.addEventListener('click', () => { mode = t.dataset.tab; syncModeUI(); });
});

meta.innerHTML = 'Return target: <strong>' + redirectUrl + '</strong>';
syncModeUI();

document.getElementById('auth-form').addEventListener('submit', async e => {
e.preventDefault();
setStatus('Contacting auth service...');
try {
const r = await fetch('/api/auth/email', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({
email: document.getElementById('auth-email').value,
password: document.getElementById('auth-password').value,
redirect: redirectUrl, state, nonce, mode
})
});
const d = await r.json();
if (!r.ok || !d.ok) throw new Error(d.error || 'Authentication failed');
setStatus('Authenticated. Redirecting...');
window.location.href = d.redirectTo;
} catch (err) { setStatus(err.message, true); }
});

document.getElementById('guest-btn').addEventListener('click', async () => {
setStatus('Creating guest session...');
try {
const r = await fetch('/api/auth/anonymous', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({ redirect: redirectUrl, state, nonce })
});
const d = await r.json();
if (!r.ok || !d.ok) throw new Error(d.error || 'Guest session failed');
window.location.href = d.redirectTo;
} catch (err) { setStatus(err.message, true); }
});

document.getElementById('google-btn').addEventListener('click', () => {
const u = new URL('/oauth/google', location.origin);
u.searchParams.set('redirect', redirectUrl);
u.searchParams.set('state', state);
u.searchParams.set('nonce', nonce);
location.href = u;
});

document.getElementById('github-btn').addEventListener('click', () => {
const u = new URL('/oauth/github', location.origin);
u.searchParams.set('redirect', redirectUrl);
u.searchParams.set('state', state);
u.searchParams.set('nonce', nonce);
location.href = u;
});
</script>
</body>
</html>`;
}

function renderAuthRelayPage() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,nofollow">
<title>HeadyKey Auth Relay</title>
<script>
const ALLOWED_ORIGINS = [
'https://headyme.com',
'https://headysystems.com',
'https://heady-ai.com',
'https://headyos.com',
'https://headyconnection.org',
'https://headyconnection.com',
'https://headyex.com',
'https://headybuddy.org',
'https://headymcp.com',
'https://headykey.com',
'https://admin.headysystems.com',
'https://auth.headysystems.com'
];
window.addEventListener('message', async (event) => {
if (!ALLOWED_ORIGINS.includes(event.origin)) return;
if (event.data?.type === 'heady:context:request') {
const nonce = typeof event.data.nonce === 'string' ? event.data.nonce : null;
event.source.postMessage({
type: 'heady:auth:sync',
user: null,
session: { source: 'relay', verifiedOrigin: event.origin, requestedSite: event.data.site || null, nonce }
}, event.origin);
}
});
</script>
</head>
<body></body>
</html>`;
}

async function tryOrigin(request, url, hostname, moduleName, env) {
const originBase = env.HEADY_ORIGIN_BASE || DEFAULT_ORIGIN;
const originUrl = `${originBase}${url.pathname}${url.search}`;
Expand Down Expand Up @@ -403,6 +621,25 @@ export default {
});
}

// ── Auth portal: serve login & relay directly at the edge ──────
// API routes (/api/*, /oauth/*) still proxy to origin for backend handling.
if (moduleName === 'auth-portal') {
const path = url.pathname.replace(/\/+$/, '') || '/';
if (path === '/' || path === '/login') {
return new Response(renderAuthLoginPage(), {
status: 200,
headers: commonHeaders(moduleName, 'edge-auth-portal'),
});
}
if (path === '/relay') {
return new Response(renderAuthRelayPage(), {
status: 200,
headers: commonHeaders(moduleName, 'edge-auth-relay'),
});
}
// All other auth paths (/api/auth/*, /oauth/*, /verify, /token/*) → origin proxy
}

const cacheKey = `hologram:${moduleName}:${url.pathname || '/'}:${url.search || ''}`;
const contentType = contentTypeForPath(url.pathname);

Expand Down
Loading
Loading