From e758add8ae88d9482d0e69ca473447ff94b8aa74 Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Tue, 7 Jul 2026 12:34:52 +0000 Subject: [PATCH] Support Workforce bearer token as alternative to email/password OAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds bearer_token secret field — if configured, the auth service uses it directly and skips the OAuth password grant entirely. Falls back to email/password flow if no token is set. Co-Authored-By: Claude Sonnet 4.6 --- src/integrations/schema.js | 2 +- src/routes/integrations.js | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/integrations/schema.js b/src/integrations/schema.js index 194c31d..0f0325a 100644 --- a/src/integrations/schema.js +++ b/src/integrations/schema.js @@ -30,7 +30,7 @@ export const INTEGRATIONS = { workforce: { name: 'Workforce', configFields: ['base_url', 'sync_hours'], - secretFields: ['email', 'password'], + secretFields: ['bearer_token', 'email', 'password'], }, smtp: { name: 'SMTP Email', diff --git a/src/routes/integrations.js b/src/routes/integrations.js index aefc330..0fba04a 100644 --- a/src/routes/integrations.js +++ b/src/routes/integrations.js @@ -89,15 +89,25 @@ export async function integrationRoutes(app) { else if (req.params.slug === 'resos') await resosTest(creds) else if (req.params.slug === 'workforce') { const baseUrl = creds.base_url || 'https://my.workforce.com' - const res = await fetch(`${baseUrl}/api/oauth/token`, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ grant_type: 'password', username: creds.email, password: creds.password, scope: 'platform' }).toString(), - signal: AbortSignal.timeout(8000), - }) - if (!res.ok) throw new Error(`Workforce auth failed (${res.status})`) - const data = await res.json() - if (!data.access_token) throw new Error('No access token returned') + if (creds.bearer_token) { + // Test bearer token directly with a lightweight API call + const res = await fetch(`${baseUrl}/api/v2/users?page=1&page_size=1`, { + headers: { Authorization: `Bearer ${creds.bearer_token}` }, + signal: AbortSignal.timeout(8000), + }) + if (!res.ok) throw new Error(`Workforce bearer token invalid (${res.status})`) + } else { + if (!creds.email || !creds.password) throw new Error('No bearer token or email/password configured') + const res = await fetch(`${baseUrl}/api/oauth/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ grant_type: 'password', username: creds.email, password: creds.password, scope: 'platform' }).toString(), + signal: AbortSignal.timeout(8000), + }) + if (!res.ok) throw new Error(`Workforce auth failed (${res.status})`) + const data = await res.json() + if (!data.access_token) throw new Error('No access token returned') + } } else if (req.params.slug === 'smtp') { const nodemailer = await import('nodemailer')