Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
fb5975c
feat(oauth): desktop public-client PKCE flow for Google integrations
robdmac Jul 6, 2026
4c5193a
build(desktop): bake OAuth client creds into the bundled capnp at bui…
robdmac Jul 6, 2026
6a18b4a
feat(oauth): GitHub device flow for the desktop public client
robdmac Jul 7, 2026
5febe2e
fix(desktop): enable control-plane outbound HTTPS; tighten SSRF surface
robdmac Jul 7, 2026
c3596f6
feat(desktop): gate dev-auth behind a per-boot surface token
robdmac Jul 7, 2026
3a8fb03
feat(desktop): equip the orcabot CLI with the surface token; document…
robdmac Jul 7, 2026
1144725
test(desktop): security smoke for the dev-auth surface-token gate
robdmac Jul 7, 2026
4ce444a
build(desktop): sync resources into target/release/resources after a …
robdmac Jul 7, 2026
ee9d40e
feat(frontend): GitHub device-code modal for desktop connect
robdmac Jul 7, 2026
8950384
fix(frontend): send surface token on the desktop auth bootstrap
robdmac Jul 7, 2026
2293c20
fix(desktop): allow X-Orcabot-Surface through CORS; robust auth boots…
robdmac Jul 7, 2026
7959cf9
feat(desktop): OS-browser OAuth connect (open_url + connectViaBrowser…
robdmac Jul 7, 2026
c0cc108
feat(desktop): OS-browser OAuth connect for all popup-based providers
robdmac Jul 7, 2026
08d9879
fix(desktop): pass dev-auth + surface token as query params for OS-br…
robdmac Jul 7, 2026
e1e59bf
fix(desktop): detect OAuth connect on app focus; graceful close-tab
robdmac Jul 7, 2026
88f236c
fix(desktop): detect OAuth connect via native Tauri focus event; bake…
robdmac Jul 7, 2026
09d146f
feat(desktop): hide Slack, Discord, and X from the canvas palette
robdmac Jul 7, 2026
a943a87
feat(oauth): Microsoft PKCE public-client flow (Outlook/Calendar/Team…
robdmac Jul 7, 2026
ac1479e
feat(oauth): Microsoft token refresh works on the public-client path
robdmac Jul 7, 2026
b00d01e
feat(desktop): hide WhatsApp on desktop + hide empty messaging section
robdmac Jul 7, 2026
349186c
fix(desktop): address review — bake only non-confidential creds; devi…
robdmac Jul 7, 2026
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
25 changes: 23 additions & 2 deletions controlplane/src/auth/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,37 @@ export async function authenticate(
return authenticateWithCfAccеss(request, env);
}

// Fall back to dev auth if enabled
// Fall back to dev auth if enabled. Dev-auth is header-spoofable, and on
// desktop a process inside the sandbox VM can reach the control plane — so when
// a per-boot SURFACE_TOKEN is provisioned, only honor dev-auth for requests
// from the trusted host frontend (matching X-Orcabot-Surface). No token
// (cloud, local dev, older builds) = unchanged behavior.
const devAuthEnabled = env.DEV_AUTH_ENABLED === 'true';
if (devAuthEnabled) {
if (devAuthEnabled && devAuthSurfaceTrusted(request, env)) {
return authenticateDevMоde(request, env);
}

// No auth method available
return { user: null, isAuthenticated: false };
}

/**
* Desktop trust boundary: dev-auth is header-spoofable and the sandbox VM can
* reach the control plane. When a per-boot SURFACE_TOKEN is provisioned, only
* the host frontend (which sends the matching X-Orcabot-Surface header) may use
* dev-auth. When unset (cloud / local dev / older builds), enforcement is off.
*/
function devAuthSurfaceTrusted(request: Request, env: Env): boolean {
const expected = env.SURFACE_TOKEN;
if (!expected) return true;
// Header for fetch/XHR; query param for top-level browser navigations (OAuth
// connect opened in the OS browser), which can't set custom headers.
const got =
request.headers.get('X-Orcabot-Surface') ||
new URL(request.url).searchParams.get('surface');
return got === expected;
}

// Production: Cloudflare Access JWT validation
async function authenticateWithCfAccеss(
request: Request,
Expand Down
9 changes: 8 additions & 1 deletion controlplane/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export { ASRStreamProxy } from './asr/ASRStreamProxy';

// CORS headers (base - origin is added dynamically)
const CORS_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS';
const CORS_ALLOWED_HEADERS = 'Content-Type, X-User-ID, X-User-Email, X-User-Name';
const CORS_ALLOWED_HEADERS = 'Content-Type, X-User-ID, X-User-Email, X-User-Name, X-Orcabot-Surface';

/**
* Parse allowed origins from env. Returns null if all origins allowed (dev mode).
Expand Down Expand Up @@ -1267,6 +1267,11 @@ async function handleRequest(request: Request, env: EnvWithBindings, ctx: Pick<E
if (authError && env.DEV_AUTH_ENABLED !== 'true') {
return authError;
}
// embed-check makes a server-side GET of a caller-supplied URL — an
// exfil/SSRF-shaped surface. A leaked PAT (full user authority) must not be
// able to drive it, and private hosts are already blocked below.
const patError = rejectPatAuth(auth);
if (patError) return patError;

const targetUrlParam = url.searchParams.get('url');
if (!targetUrlParam) {
Expand Down Expand Up @@ -2011,6 +2016,8 @@ async function handleRequest(request: Request, env: EnvWithBindings, ctx: Pick<E
'POST google/drive/sync/large': integrations.syncGооgleDriveLargeFiles,
'GET github/connect': integrations.cоnnectGithub,
'GET github/callback': (request, env) => integrations.callbackGithub(request, env),
'POST github/device/start': integrations.deviceStartGithub,
'POST github/device/poll': integrations.devicePollGithub,
'GET github': integrations.getGithubIntegratiоn,
'GET github/repos': integrations.getGithubRepоs,
'GET github/history': integrations.getGithubRepoHistory,
Expand Down
6 changes: 4 additions & 2 deletions controlplane/src/integration-policies/token-refresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,17 +218,19 @@ async function refreshOAuthToken(
} else if (provider === 'teams' || provider === 'outlook' || provider === 'onedrive' || provider === 'outlook_calendar') {
const clientId = env.MICROSOFT_CLIENT_ID || env.ONEDRIVE_CLIENT_ID;
const clientSecret = env.MICROSOFT_CLIENT_SECRET || env.ONEDRIVE_CLIENT_SECRET;
if (!clientId || !clientSecret) {
// Desktop public client: PKCE-issued tokens refresh with no secret.
const isPublicClient = env.OAUTH_PUBLIC_CLIENT === 'true';
if (!clientId || (!isPublicClient && !clientSecret)) {
console.error(`[token-refresh] Microsoft OAuth not configured for ${provider} refresh (userIntegrationId=${userIntegrationId})`);
return null;
}
tokenUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
body = new URLSearchParams({
client_id: clientId,
client_secret: clientSecret,
grant_type: 'refresh_token',
refresh_token: refreshToken,
});
if (!isPublicClient) body.set('client_secret', clientSecret!);
} else {
// Browser and other providers don't use OAuth refresh
console.warn(`[token-refresh] OAuth refresh not supported for provider: ${provider}`);
Expand Down
Loading
Loading