From 668a40a51a1bf883fd3cd02f37a4218cceda7453 Mon Sep 17 00:00:00 2001 From: Matthew Jackson Date: Fri, 6 Mar 2026 15:55:26 -0800 Subject: [PATCH] Look up auth connections by domain instead of stored ID Connection IDs change when re-authing. Instead of storing IDs in settings.json (which go stale), look up connections by domain at runtime via kernel.auth.connections.list({ domain }). This keeps the applier in sync regardless of connection recreation. Co-Authored-By: Claude Opus 4.6 --- job_applier.mjs | 3 +-- lib/session.mjs | 62 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/job_applier.mjs b/job_applier.mjs index 752c0bf..900c9df 100644 --- a/job_applier.mjs +++ b/job_applier.mjs @@ -137,9 +137,8 @@ async function main() { browser = await createBrowser(settings, null); // no profile needed } else { // Check auth status before creating browser - const connectionIds = settings.kernel?.connection_ids || {}; const kernelApiKey = process.env.KERNEL_API_KEY || settings.kernel_api_key; - const authResult = await ensureAuth(platform, kernelApiKey, connectionIds); + const authResult = await ensureAuth(platform, kernelApiKey); if (!authResult.ok) { console.error(` ❌ ${platform} auth failed: ${authResult.reason}`); await sendTelegram(settings, `⚠️ *${platform}* auth failed — ${authResult.reason}`).catch(() => {}); diff --git a/lib/session.mjs b/lib/session.mjs index 3f4bdc9..efe524c 100644 --- a/lib/session.mjs +++ b/lib/session.mjs @@ -2,8 +2,11 @@ * session.mjs — Kernel Managed Auth session management * Checks auth status before browser creation, triggers re-auth when needed. * + * Looks up auth connections by domain (not stored ID) so it stays in sync + * even when connections are recreated. + * * Flow: - * 1. Check connection status via SDK + * 1. List connections for domain, find the one matching our profile * 2. If AUTHENTICATED → good to go * 3. If NEEDS_AUTH + can_reauth → trigger login(), poll until done * 4. If NEEDS_AUTH + !can_reauth → return false (caller should alert + skip) @@ -14,29 +17,45 @@ import { } from './constants.mjs'; const require = createRequire(import.meta.url); +const PLATFORM_DOMAINS = { + linkedin: 'linkedin.com', + wellfound: 'wellfound.com', +}; + function getKernel(apiKey) { const Kernel = require(KERNEL_SDK_PATH); return new Kernel({ apiKey }); } +/** + * Find auth connection for a platform by domain lookup. + * Returns the connection object or null. + */ +async function findConnection(kernel, platform) { + const domain = PLATFORM_DOMAINS[platform]; + if (!domain) return null; + + const page = await kernel.auth.connections.list({ domain }); + const connections = []; + for await (const conn of page) { + connections.push(conn); + } + + if (connections.length === 0) return null; + // If multiple connections for same domain, prefer AUTHENTICATED + return connections.find(c => c.status === 'AUTHENTICATED') || connections[0]; +} + /** * Check auth connection status and re-auth if needed. * Returns { ok: true } or { ok: false, reason: string } */ -export async function ensureAuth(platform, apiKey, connectionIds = {}) { - const connectionId = connectionIds[platform]; - if (!connectionId) { - return { ok: false, reason: `no connection ID configured for ${platform}` }; - } - +export async function ensureAuth(platform, apiKey) { const kernel = getKernel(apiKey); - // Check current status - let conn; - try { - conn = await kernel.auth.connections.retrieve(connectionId); - } catch (e) { - return { ok: false, reason: `connection ${connectionId} not found: ${e.message}` }; + const conn = await findConnection(kernel, platform); + if (!conn) { + return { ok: false, reason: `no auth connection found for ${platform} (domain: ${PLATFORM_DOMAINS[platform]})` }; } if (conn.status === 'AUTHENTICATED') { @@ -45,13 +64,13 @@ export async function ensureAuth(platform, apiKey, connectionIds = {}) { // NEEDS_AUTH — can we auto re-auth? if (!conn.can_reauth) { - return { ok: false, reason: `${platform} needs manual re-login (can_reauth=false). Go to Kernel dashboard or run: kernel auth connections login ${connectionId}` }; + return { ok: false, reason: `${platform} needs manual re-login (can_reauth=false). Run: kernel auth connections login ${conn.id}` }; } // Trigger re-auth with stored credentials console.log(` 🔄 ${platform} session expired — re-authenticating...`); try { - await kernel.auth.connections.login(connectionId); + await kernel.auth.connections.login(conn.id); } catch (e) { return { ok: false, reason: `re-auth login() failed: ${e.message}` }; } @@ -60,22 +79,23 @@ export async function ensureAuth(platform, apiKey, connectionIds = {}) { const start = Date.now(); while (Date.now() - start < SESSION_REFRESH_POLL_TIMEOUT) { await new Promise(r => setTimeout(r, SESSION_REFRESH_POLL_WAIT)); + let updated; try { - conn = await kernel.auth.connections.retrieve(connectionId); + updated = await kernel.auth.connections.retrieve(conn.id); } catch (e) { return { ok: false, reason: `polling failed: ${e.message}` }; } - if (conn.status === 'AUTHENTICATED') { + if (updated.status === 'AUTHENTICATED') { console.log(` ✅ ${platform} re-authenticated`); return { ok: true }; } - if (conn.flow_status === 'FAILED') { - return { ok: false, reason: `re-auth failed: ${conn.error_message || conn.error_code || 'unknown'}` }; + if (updated.flow_status === 'FAILED') { + return { ok: false, reason: `re-auth failed: ${updated.error_message || updated.error_code || 'unknown'}` }; } - if (conn.flow_status === 'EXPIRED' || conn.flow_status === 'CANCELED') { - return { ok: false, reason: `re-auth ${conn.flow_status.toLowerCase()}` }; + if (updated.flow_status === 'EXPIRED' || updated.flow_status === 'CANCELED') { + return { ok: false, reason: `re-auth ${updated.flow_status.toLowerCase()}` }; } }