repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
The Living OS cockpit
stars
latest
clone command
git clone gitlawb://did:key:z6Mku78K...XywC/living-os-cockp...git clone gitlawb://did:key:z6Mku78K.../living-os-cockp...59751530feat: surface worker supervisor health in live work5h ago| #1 | import { cookies, headers } from 'next/headers'; |
| #2 | |
| #3 | export type UserId = string; |
| #4 | |
| #5 | export const CANONICAL_KING_ID = 'ed3c37e8-715f-4f62-9823-05ec234bf073'; |
| #6 | export const CANONICAL_MAAXX_ID = 'be4d6c49-6fca-4796-b13e-deb3af4d3ce6'; |
| #7 | |
| #8 | export class AuthRequiredError extends Error { |
| #9 | status = 401; |
| #10 | constructor(message = 'Authentication required') { |
| #11 | super(message); |
| #12 | this.name = 'AuthRequiredError'; |
| #13 | } |
| #14 | } |
| #15 | |
| #16 | export class AuthMismatchError extends Error { |
| #17 | status = 403; |
| #18 | constructor(message = 'Authenticated member does not match this cockpit subdomain') { |
| #19 | super(message); |
| #20 | this.name = 'AuthMismatchError'; |
| #21 | } |
| #22 | } |
| #23 | |
| #24 | export interface UserContext { |
| #25 | userId: UserId; |
| #26 | canonicalMemberId: string; |
| #27 | legacyUserId: 'master' | 'queen_maaxx' | string; |
| #28 | vaultUserKey: 'kingbau' | 'maaxx' | string; |
| #29 | username: string; |
| #30 | email: string; |
| #31 | role: string; |
| #32 | displayName: string; |
| #33 | token: string; |
| #34 | tier: 'free' | 'sovereign' | 'dragon' | 'sovereign_court' | 'admin'; |
| #35 | aethonUrl: string; |
| #36 | glassesPort: number; |
| #37 | glassesLogPath: string; |
| #38 | glassesHealthUrl: string; |
| #39 | vaultPath: string; |
| #40 | hostSlug: string; |
| #41 | } |
| #42 | |
| #43 | export interface PublicUserContext { |
| #44 | userId: UserId; |
| #45 | canonicalMemberId: string; |
| #46 | legacyUserId: string; |
| #47 | displayName: string; |
| #48 | username: string; |
| #49 | email: string; |
| #50 | tier: UserContext['tier']; |
| #51 | role: string; |
| #52 | hostSlug: string; |
| #53 | } |
| #54 | |
| #55 | type GatewayProfile = { |
| #56 | sub?: string; |
| #57 | id?: string; |
| #58 | member_id?: string; |
| #59 | username?: string; |
| #60 | email?: string; |
| #61 | display_name?: string; |
| #62 | tier?: UserContext['tier']; |
| #63 | member_tier?: UserContext['tier']; |
| #64 | role?: string; |
| #65 | }; |
| #66 | |
| #67 | type RefreshTokens = { |
| #68 | access_token: string; |
| #69 | refresh_token?: string; |
| #70 | }; |
| #71 | |
| #72 | type RefreshCacheEntry = RefreshTokens & { expiresAt: number }; |
| #73 | |
| #74 | const refreshState = (() => { |
| #75 | const g = globalThis as typeof globalThis & { |
| #76 | __aethonRefreshInflight?: Map<string, Promise<RefreshTokens | null>>; |
| #77 | __aethonRefreshCache?: Map<string, RefreshCacheEntry>; |
| #78 | }; |
| #79 | if (!g.__aethonRefreshInflight) g.__aethonRefreshInflight = new Map(); |
| #80 | if (!g.__aethonRefreshCache) g.__aethonRefreshCache = new Map(); |
| #81 | return { |
| #82 | inflight: g.__aethonRefreshInflight, |
| #83 | cache: g.__aethonRefreshCache, |
| #84 | }; |
| #85 | })(); |
| #86 | |
| #87 | const HOST_CANONICAL: Record<string, string> = { |
| #88 | king: CANONICAL_KING_ID, |
| #89 | maaxx: CANONICAL_MAAXX_ID, |
| #90 | }; |
| #91 | |
| #92 | const CANONICAL_META: Record<string, Omit<UserContext, 'token' | 'username' | 'email' | 'role' | 'displayName' | 'tier' | 'userId' | 'canonicalMemberId' | 'hostSlug'>> = { |
| #93 | [CANONICAL_KING_ID]: { |
| #94 | legacyUserId: 'master', |
| #95 | vaultUserKey: 'kingbau', |
| #96 | aethonUrl: 'http://localhost:7000', |
| #97 | glassesPort: 3100, |
| #98 | glassesLogPath: process.env.GLASSES_KING_LOG || '/home/kingbau/logs/aethon-glasses.log', |
| #99 | glassesHealthUrl: process.env.GLASSES_KING_HEALTH || 'http://localhost:3100/', |
| #100 | vaultPath: '/home/kingbau/vaults/kingbau', |
| #101 | }, |
| #102 | [CANONICAL_MAAXX_ID]: { |
| #103 | legacyUserId: 'queen_maaxx', |
| #104 | vaultUserKey: 'maaxx', |
| #105 | aethonUrl: 'http://localhost:7001', |
| #106 | glassesPort: 3101, |
| #107 | glassesLogPath: process.env.GLASSES_MAAXX_LOG || '/home/kingbau/logs/aethon-glasses-maaxx.log', |
| #108 | glassesHealthUrl: process.env.GLASSES_MAAXX_HEALTH || 'http://localhost:3101/health', |
| #109 | vaultPath: '/home/kingbau/vaults/maaxx', |
| #110 | }, |
| #111 | }; |
| #112 | |
| #113 | function gatewayUrl() { |
| #114 | return process.env.GATEWAY_URL || process.env.NEXT_PUBLIC_API_URL || 'http://127.0.0.1:8890'; |
| #115 | } |
| #116 | |
| #117 | function hostSlugFromHost(host: string) { |
| #118 | const clean = host.split(':')[0].toLowerCase(); |
| #119 | const first = clean.split('.')[0] || ''; |
| #120 | return first === 'localhost' || first === '127' ? '' : first; |
| #121 | } |
| #122 | |
| #123 | function tokenFromCookieHeader(cookieHeader: string | null) { |
| #124 | return namedTokenFromCookieHeader(cookieHeader, 'theliving_token'); |
| #125 | } |
| #126 | |
| #127 | function refreshTokenFromCookieHeader(cookieHeader: string | null) { |
| #128 | return namedTokenFromCookieHeader(cookieHeader, 'theliving_refresh'); |
| #129 | } |
| #130 | |
| #131 | function namedTokenFromCookieHeader(cookieHeader: string | null, name: string) { |
| #132 | if (!cookieHeader) return ''; |
| #133 | const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| #134 | const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${escaped}=([^;]+)`)); |
| #135 | return match ? decodeURIComponent(match[1]) : ''; |
| #136 | } |
| #137 | |
| #138 | function cookieOptions(host: string, maxAge: number) { |
| #139 | const clean = host.split(':')[0].toLowerCase(); |
| #140 | return { |
| #141 | path: '/', |
| #142 | maxAge, |
| #143 | sameSite: 'lax' as const, |
| #144 | secure: clean === 'theliving.ai' || clean.endsWith('.theliving.ai'), |
| #145 | ...(clean === 'theliving.ai' || clean.endsWith('.theliving.ai') ? { domain: '.theliving.ai' } : {}), |
| #146 | }; |
| #147 | } |
| #148 | |
| #149 | async function refreshAccessToken(refreshToken: string, host: string) { |
| #150 | if (!refreshToken) return ''; |
| #151 | const now = Date.now(); |
| #152 | const cached = refreshState.cache.get(refreshToken); |
| #153 | if (cached && cached.expiresAt > now) { |
| #154 | await writeAuthCookies(cached, host); |
| #155 | return cached.access_token; |
| #156 | } |
| #157 | |
| #158 | let refreshPromise = refreshState.inflight.get(refreshToken); |
| #159 | if (!refreshPromise) { |
| #160 | refreshPromise = fetch(`${gatewayUrl()}/api/v1/auth/refresh`, { |
| #161 | method: 'POST', |
| #162 | headers: { 'Content-Type': 'application/json' }, |
| #163 | body: JSON.stringify({ refresh_token: refreshToken }), |
| #164 | cache: 'no-store', |
| #165 | }).then(async (res) => { |
| #166 | if (!res.ok) return null; |
| #167 | const tokens = await res.json() as { access_token?: string; refresh_token?: string }; |
| #168 | if (!tokens.access_token) return null; |
| #169 | const next = { access_token: tokens.access_token, refresh_token: tokens.refresh_token }; |
| #170 | refreshState.cache.set(refreshToken, { ...next, expiresAt: Date.now() + 15_000 }); |
| #171 | return next; |
| #172 | }).finally(() => { |
| #173 | setTimeout(() => refreshState.inflight.delete(refreshToken), 15_000); |
| #174 | }); |
| #175 | refreshState.inflight.set(refreshToken, refreshPromise); |
| #176 | } |
| #177 | |
| #178 | const tokens = await refreshPromise; |
| #179 | if (!tokens?.access_token) return ''; |
| #180 | await writeAuthCookies(tokens, host); |
| #181 | return tokens.access_token; |
| #182 | } |
| #183 | |
| #184 | async function writeAuthCookies(tokens: RefreshTokens, host: string) { |
| #185 | const c = await cookies(); |
| #186 | const mutableCookies = c as any; |
| #187 | mutableCookies.set?.('theliving_token', tokens.access_token, cookieOptions(host, 60 * 60)); |
| #188 | if (tokens.refresh_token) { |
| #189 | mutableCookies.set?.('theliving_refresh', tokens.refresh_token, cookieOptions(host, 30 * 24 * 60 * 60)); |
| #190 | } |
| #191 | } |
| #192 | |
| #193 | async function readBearerToken() { |
| #194 | const h = await headers(); |
| #195 | const auth = h.get('authorization') || ''; |
| #196 | if (auth.toLowerCase().startsWith('bearer ')) return auth.slice(7).trim(); |
| #197 | |
| #198 | const c = await cookies(); |
| #199 | const cookieToken = c.get('theliving_token')?.value; |
| #200 | if (cookieToken) return cookieToken; |
| #201 | |
| #202 | return tokenFromCookieHeader(h.get('cookie')); |
| #203 | } |
| #204 | |
| #205 | async function readRefreshToken() { |
| #206 | const h = await headers(); |
| #207 | const c = await cookies(); |
| #208 | return c.get('theliving_refresh')?.value || refreshTokenFromCookieHeader(h.get('cookie')); |
| #209 | } |
| #210 | |
| #211 | async function fetchGatewayProfile(token: string): Promise<GatewayProfile> { |
| #212 | const res = await fetch(`${gatewayUrl()}/api/v1/auth/me`, { |
| #213 | headers: { Authorization: `Bearer ${token}` }, |
| #214 | cache: 'no-store', |
| #215 | }); |
| #216 | if (!res.ok) { |
| #217 | throw new AuthRequiredError(`Gateway auth failed with ${res.status}`); |
| #218 | } |
| #219 | return await res.json() as GatewayProfile; |
| #220 | } |
| #221 | |
| #222 | function canonicalIdFromProfile(profile: GatewayProfile) { |
| #223 | return String(profile.sub || profile.id || '').trim(); |
| #224 | } |
| #225 | |
| #226 | function canAccessHost(profile: GatewayProfile, canonicalMemberId: string, hostSlug: string) { |
| #227 | const expected = HOST_CANONICAL[hostSlug]; |
| #228 | if (!expected) return true; |
| #229 | if (canonicalMemberId === expected) return true; |
| #230 | return profile.role === 'admin'; |
| #231 | } |
| #232 | |
| #233 | export async function getUserContext(): Promise<UserContext> { |
| #234 | const h = await headers(); |
| #235 | const host = h.get('host') ?? ''; |
| #236 | const hostSlug = hostSlugFromHost(host); |
| #237 | let token = await readBearerToken(); |
| #238 | if (!token) { |
| #239 | token = await refreshAccessToken(await readRefreshToken(), host); |
| #240 | } |
| #241 | if (!token) throw new AuthRequiredError(); |
| #242 | |
| #243 | let profile: GatewayProfile; |
| #244 | try { |
| #245 | profile = await fetchGatewayProfile(token); |
| #246 | } catch (error) { |
| #247 | const refreshed = await refreshAccessToken(await readRefreshToken(), host); |
| #248 | if (!refreshed || refreshed === token) throw error; |
| #249 | token = refreshed; |
| #250 | profile = await fetchGatewayProfile(token); |
| #251 | } |
| #252 | const canonicalMemberId = canonicalIdFromProfile(profile); |
| #253 | if (!canonicalMemberId) throw new AuthRequiredError('Gateway profile did not include a canonical user id'); |
| #254 | if (!canAccessHost(profile, canonicalMemberId, hostSlug)) throw new AuthMismatchError(); |
| #255 | |
| #256 | const meta = CANONICAL_META[canonicalMemberId] || { |
| #257 | legacyUserId: canonicalMemberId, |
| #258 | vaultUserKey: canonicalMemberId, |
| #259 | aethonUrl: canonicalMemberId === CANONICAL_MAAXX_ID ? 'http://localhost:7001' : 'http://localhost:7000', |
| #260 | glassesPort: canonicalMemberId === CANONICAL_MAAXX_ID ? 3101 : 3100, |
| #261 | glassesLogPath: canonicalMemberId === CANONICAL_MAAXX_ID ? (process.env.GLASSES_MAAXX_LOG || '/home/kingbau/logs/aethon-glasses-maaxx.log') : (process.env.GLASSES_KING_LOG || '/home/kingbau/logs/aethon-glasses.log'), |
| #262 | glassesHealthUrl: canonicalMemberId === CANONICAL_MAAXX_ID ? (process.env.GLASSES_MAAXX_HEALTH || 'http://localhost:3101/health') : (process.env.GLASSES_KING_HEALTH || 'http://localhost:3100/'), |
| #263 | vaultPath: `/home/kingbau/vaults/${canonicalMemberId}`, |
| #264 | }; |
| #265 | |
| #266 | const tier = profile.member_tier || profile.tier || (profile.role === 'admin' ? 'admin' : 'free'); |
| #267 | |
| #268 | return { |
| #269 | ...meta, |
| #270 | userId: canonicalMemberId, |
| #271 | canonicalMemberId, |
| #272 | username: String(profile.username || ''), |
| #273 | email: String(profile.email || ''), |
| #274 | role: String(profile.role || 'user'), |
| #275 | displayName: String(profile.display_name || profile.username || 'Member'), |
| #276 | tier, |
| #277 | token, |
| #278 | hostSlug, |
| #279 | }; |
| #280 | } |
| #281 | |
| #282 | export function publicContext(ctx: UserContext): PublicUserContext { |
| #283 | return { |
| #284 | userId: ctx.userId, |
| #285 | canonicalMemberId: ctx.canonicalMemberId, |
| #286 | legacyUserId: ctx.legacyUserId, |
| #287 | displayName: ctx.displayName, |
| #288 | username: ctx.username, |
| #289 | email: ctx.email, |
| #290 | tier: ctx.tier, |
| #291 | role: ctx.role, |
| #292 | hostSlug: ctx.hostSlug, |
| #293 | }; |
| #294 | } |
| #295 | |
| #296 | export function authErrorResponse(error: unknown) { |
| #297 | if (error instanceof AuthRequiredError || error instanceof AuthMismatchError) { |
| #298 | return Response.json({ error: error.message }, { status: error.status }); |
| #299 | } |
| #300 | throw error; |
| #301 | } |
| #302 |