Skip to content
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
0e504ce
Add /tmp to .gitignore
rishikesh-major Mar 3, 2026
d5c47f8
feat: add OAuthGate server + client components for user-level OAuth c…
rishikesh-major Mar 13, 2026
648fb51
fix: remove cookies().set() — not allowed in server components
rishikesh-major Mar 13, 2026
2e0a0c5
fix: remove iframe postMessage bridge from OAuthGateScreen
rishikesh-major Mar 14, 2026
3b76924
fix: split resource API URL for SSR vs browser OAuth redirects
rishikesh-major Mar 15, 2026
d076485
fix: resolve lint errors in OAuth gate components
rishikesh-major Mar 15, 2026
b092de2
fix: fetch auth URL from endpoint instead of navigating directly
rishikesh-major Mar 15, 2026
f218cff
fix: resolve auth URLs server-side in OAuthGate SSR
rishikesh-major Mar 15, 2026
5f5c2b4
fix: remove unused RESOURCE_API_BROWSER_URL constant
rishikesh-major Mar 15, 2026
e4d26fa
fix: always use popup for OAuth flow in OAuthGate
rishikesh-major Mar 15, 2026
8e9c22e
refactor: simplify OAuthGate to redirect to platform-hosted connect page
rishikesh-major Mar 17, 2026
39e30fb
fix: move JSX out of try/catch to satisfy react-hooks/error-boundarie…
rishikesh-major Mar 17, 2026
b2ef611
refactor: extract buildConnectUrl helper
rishikesh-major Mar 17, 2026
aa3278e
refactor: redirect to Major dashboard OAuth connect page instead of g…
rishikesh-major Mar 17, 2026
b8f1773
revert: remove unrelated .gitignore change
rishikesh-major Mar 17, 2026
558aca3
fix: remove hardcoded fallback URLs, inline buildConnectUrl, fail ope…
rishikesh-major Mar 17, 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
65 changes: 65 additions & 0 deletions components/oauth-gate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import type { ReactNode } from "react";

const RESOURCE_API_URL = process.env.RESOURCE_API_URL;
const MAJOR_APP_URL = process.env.MAJOR_APP_URL;

interface StatusResponse {
providers: Record<string, { status: string }>;
connectToken?: string;
}

async function getConnectToken(userJwt: string): Promise<string | null> {
if (!RESOURCE_API_URL) {
return null;
}

try {
const res = await fetch(`${RESOURCE_API_URL}/user-oauth/status`, {
headers: { "x-major-user-jwt": userJwt },
cache: "no-store",
});

if (!res.ok) {
return null;
}

const data = (await res.json()) as StatusResponse;
return data.connectToken ?? null;
} catch {
// Fail open — platform/network issues should not block deployed apps
return null;
}
}

/**
* OAuthGate — server component that checks whether the current user has
* connected all required OAuth providers for this app. If any are missing,
* it redirects to the platform-hosted connect page on the Major dashboard
* where the user can authenticate. After all providers are connected, the
* connect page redirects back here and the app renders normally.
*/
export async function OAuthGate({ children }: { children: ReactNode }) {
if (!MAJOR_APP_URL) {
return <>{children}</>;
}

const h = await headers();
const userJwt = h.get("x-major-user-jwt");

if (!userJwt) {
return <>{children}</>;
}

const connectToken = await getConnectToken(userJwt);

if (connectToken) {
const proto = h.get("x-forwarded-proto") || "https";
const host = h.get("host");
const returnUrl = `${proto}://${host}/`;
redirect(`${MAJOR_APP_URL}/oauth/connect?token=${encodeURIComponent(connectToken)}&returnUrl=${encodeURIComponent(returnUrl)}`);
}

return <>{children}</>;
}