import { type ActionFunctionArgs, type LoaderFunctionArgs, redirect, } from "@remix-run/node"; import { Form, useLoaderData } from "@remix-run/react"; import { getUser, requireWorkpace } from "~/services/session.server"; import { oauth2Service, OAuth2Errors, type OAuth2AuthorizeRequest, } from "~/services/oauth2.server"; import { Button } from "~/components/ui/button"; import { Card, CardContent } from "~/components/ui/card"; import Logo from "~/components/logo/logo"; import { AlignLeft, Pen, User, Mail, Shield, Database, LoaderCircle, ArrowRightLeft, } from "lucide-react"; import { useState } from "react"; import { getIconForAuthorise } from "~/components/icon-utils"; export const loader = async ({ request }: LoaderFunctionArgs) => { // Check if user is authenticated const user = await getUser(request); if (!user) { // Redirect to login with return URL const url = new URL(request.url); const loginUrl = new URL("/login", request.url); loginUrl.searchParams.set("redirectTo", url.pathname + url.search); return redirect(loginUrl.toString()); } const url = new URL(request.url); let scopeParam = url.searchParams.get("scope") || "mcp"; // If scope is present, normalize it to comma-separated format // Handle both space-separated (from URL encoding) and comma-separated scopes if (scopeParam) { // First, try splitting by spaces (common in OAuth2 URLs) let scopes = scopeParam.split(/\s+/).filter((s) => s.length > 0); // If no spaces found, try splitting by commas if (scopes.length === 1) { scopes = scopeParam .split(",") .map((s) => s.trim()) .filter((s) => s.length > 0); } scopeParam = scopes.join(","); } else { throw new Error("Scope is not found"); } const params: OAuth2AuthorizeRequest = { client_id: url.searchParams.get("client_id") || "", redirect_uri: url.searchParams.get("redirect_uri") || "", response_type: url.searchParams.get("response_type") || "", scope: scopeParam, state: url.searchParams.get("state") || undefined, code_challenge: url.searchParams.get("code_challenge") || undefined, code_challenge_method: url.searchParams.get("code_challenge_method") || undefined, }; // Validate required parameters if (!params.client_id || !params.redirect_uri || !params.response_type) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Missing required parameters${params.state ? `&state=${params.state}` : ""}`, ); } // Only support authorization code flow if (params.response_type !== "code") { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.UNSUPPORTED_RESPONSE_TYPE}&error_description=Only authorization code flow is supported${params.state ? `&state=${params.state}` : ""}`, ); } try { // Validate client const client = await oauth2Service.validateClient(params.client_id); // Validate redirect URI if (!oauth2Service.validateRedirectUri(client, params.redirect_uri)) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`, ); } // Validate scopes if (!oauth2Service.validateScopes(client, params.scope || "")) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_SCOPE}&error_description=Invalid scope${params.state ? `&state=${params.state}` : ""}`, ); } return { user, client, params, }; } catch (error) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_CLIENT}&error_description=Invalid client${params.state ? `&state=${params.state}` : ""}`, ); } }; export const action = async ({ request }: ActionFunctionArgs) => { const user = await getUser(request); const workspace = await requireWorkpace(request); if (!user) { return redirect("/login"); } const formData = await request.formData(); const action = formData.get("action"); const params: OAuth2AuthorizeRequest = { client_id: formData.get("client_id") as string, redirect_uri: formData.get("redirect_uri") as string, response_type: formData.get("response_type") as string, scope: (formData.get("scope") as string) || undefined, state: (formData.get("state") as string) || undefined, code_challenge: (formData.get("code_challenge") as string) || undefined, code_challenge_method: (formData.get("code_challenge_method") as string) || undefined, }; if (action === "deny") { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.ACCESS_DENIED}&error_description=User denied access${params.state ? `&state=${params.state}` : ""}`, ); } if (action === "allow") { try { // Validate client again const client = await oauth2Service.validateClient(params.client_id); if (!oauth2Service.validateRedirectUri(client, params.redirect_uri)) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid redirect URI${params.state ? `&state=${params.state}` : ""}`, ); } // Create authorization code const authCode = await oauth2Service.createAuthorizationCode({ clientId: params.client_id, userId: user.id, redirectUri: params.redirect_uri, scope: params.scope, state: params.state, codeChallenge: params.code_challenge, codeChallengeMethod: params.code_challenge_method, workspaceId: workspace.id, }); // Redirect back to client with authorization code const redirectUrl = new URL(params.redirect_uri); redirectUrl.searchParams.set("code", authCode); if (params.state) { redirectUrl.searchParams.set("state", params.state); } return redirect(redirectUrl.toString()); } catch (error) { return redirect( `${params.redirect_uri}?error=${OAuth2Errors.SERVER_ERROR}&error_description=Failed to create authorization code${params.state ? `&state=${params.state}` : ""}`, ); } } return redirect( `${params.redirect_uri}?error=${OAuth2Errors.INVALID_REQUEST}&error_description=Invalid action${params.state ? `&state=${params.state}` : ""}`, ); }; export default function OAuthAuthorize() { const { user, client, params } = useLoaderData(); const [isRedirecting, setIsRedirecting] = useState(false); const getScopeIcon = (scope: string) => { switch (scope) { case "profile": return ; case "email": return ; case "openid": return ; case "integration": return ; case "read": return ; case "write": return ; default: return ; } }; const getScopeDescription = (scope: string) => { switch (scope) { case "profile": return "View your basic profile information"; case "email": return "View your email address"; case "openid": return "Verify your identity using OpenID Connect"; case "integration": return "Access and manage your workspace integrations"; case "read": return "Read access to your account"; case "write": return "Write access to your account"; case "mcp": return "Access to memory and integrations"; default: return `Access to ${scope}`; } }; return (
{getIconForAuthorise(client.name, 40, client.logoUrl)}

{client.name} is requesting access

Authenticating with your {user.name} account

Permissions

    {params.scope?.split(",").map((scope, index, arr) => { const trimmedScope = scope.trim(); const isFirst = index === 0; const isLast = index === arr.length - 1; return (
  • {getScopeIcon(trimmedScope)}
    {getScopeDescription(trimmedScope)}
  • ); })}
{isRedirecting ? (
Redirecting to the page... (Close this page if it doesn't redirect in 5 seconds)
) : (
{ // Only show loading if allow is clicked const form = e.target as HTMLFormElement; const allowBtn = form.querySelector( 'button[name="action"][value="allow"]', ); if ((e.nativeEvent as SubmitEvent).submitter === allowBtn) { setIsRedirecting(true); } }} > {params.scope && ( )} {params.state && ( )} {params.code_challenge && ( )} {params.code_challenge_method && ( )}
)}
); }