trails/apps/journal/app/routes/api.sync.disconnect.$provider.ts
Ullrich Schäfer b60cd96736
Revoke Wahoo tokens on disconnect; surface OAuth errors
Disconnecting a sync provider now calls the provider's revoke endpoint
(DELETE /v1/permissions for Wahoo) before dropping the local row, so
tokens don't accumulate against Wahoo's per-(app,user) cap. The token
exchange now classifies Wahoo's "Too many unrevoked access tokens" 400
as a distinct OAuthError code, and the connections settings page shows
a localized banner for that and other connect failures instead of a
silent ?error=sync_failed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 10:03:50 +02:00

32 lines
1.2 KiB
TypeScript

import { redirect, data } from "react-router";
import type { Route } from "./+types/api.sync.disconnect.$provider";
import { getSessionUser } from "~/lib/auth.server";
import { getProvider } from "~/lib/sync/registry";
import { deleteConnection, getConnection } from "~/lib/sync/connections.server";
export async function action({ params, request }: Route.ActionArgs) {
const user = await getSessionUser(request);
if (!user) return redirect("/auth/login");
const provider = getProvider(params.provider);
if (!provider) return data({ error: "Unknown provider" }, { status: 404 });
if (provider.revoke) {
const conn = await getConnection(user.id, provider.id);
if (conn) {
try {
await provider.revoke({
accessToken: conn.accessToken,
refreshToken: conn.refreshToken,
expiresAt: conn.expiresAt,
});
} catch (e) {
// Best-effort: token may already be expired/revoked. Drop the local row regardless.
console.warn(`Failed to revoke ${provider.id} token (continuing with disconnect):`, e);
}
}
}
await deleteConnection(user.id, provider.id);
return redirect("/settings");
}