core/apps/webapp/app/routes/api.oauth.clients.tsx
2025-08-21 18:42:43 +05:30

182 lines
4.5 KiB
TypeScript

import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
json,
} from "@remix-run/node";
import { requireAuth } from "~/utils/auth-helper";
import crypto from "crypto";
import { prisma } from "~/db.server";
// GET /api/oauth/clients - List OAuth clients for user's workspace
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireAuth(request);
try {
// Get user's workspace
const userRecord = await prisma.user.findUnique({
where: { id: user.id },
include: { Workspace: true },
});
if (!userRecord?.Workspace) {
return json({ error: "No workspace found" }, { status: 404 });
}
const clients = await prisma.oAuthClient.findMany({
where: { workspaceId: userRecord.Workspace.id },
select: {
id: true,
clientId: true,
name: true,
description: true,
redirectUris: true,
allowedScopes: true,
requirePkce: true,
logoUrl: true,
homepageUrl: true,
isActive: true,
createdAt: true,
updatedAt: true,
createdBy: {
select: {
id: true,
email: true,
name: true,
},
},
},
orderBy: { createdAt: "desc" },
});
return json({ clients });
} catch (error) {
console.error("Error fetching OAuth clients:", error);
return json({ error: "Internal server error" }, { status: 500 });
}
};
// POST /api/oauth/clients - Create new OAuth client
export const action = async ({ request }: ActionFunctionArgs) => {
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, { status: 405 });
}
const user = await requireAuth(request);
try {
const body = await request.json();
const {
name,
description,
redirectUris,
allowedScopes,
requirePkce,
logoUrl,
homepageUrl,
} = body;
// Validate required fields
if (!name || !redirectUris) {
return json(
{ error: "Name and redirectUris are required" },
{ status: 400 },
);
}
// Validate scopes
const validScopes = [
// Authentication scopes (Google-style)
"profile",
"email",
"openid",
// Integration scope
"integration",
"integration:read",
"integration:credentials",
"integration:manage",
"integration:webhook",
// MCP scope
"mcp",
"mcp:read",
"mcp:write",
];
const requestedScopes = Array.isArray(allowedScopes)
? allowedScopes
: [allowedScopes || "read"];
const invalidScopes = requestedScopes.filter(
(scope) => !validScopes.includes(scope),
);
if (invalidScopes.length > 0) {
return json(
{
error: `Invalid scopes: ${invalidScopes.join(", ")}. Valid scopes are: ${validScopes.join(", ")}`,
},
{ status: 400 },
);
}
// Get user's workspace
const userRecord = await prisma.user.findUnique({
where: { id: user.id },
include: { Workspace: true },
});
if (!userRecord?.Workspace) {
return json({ error: "No workspace found" }, { status: 404 });
}
if (!userRecord?.admin) {
return json({ error: "No access to create OAuth app" }, { status: 404 });
}
// Generate client credentials
const clientId = crypto.randomUUID();
const clientSecret = crypto.randomBytes(32).toString("hex");
// Create OAuth client
const client = await prisma.oAuthClient.create({
data: {
clientId,
clientSecret,
name,
description: description || null,
redirectUris: Array.isArray(redirectUris)
? redirectUris.join(",")
: redirectUris,
allowedScopes: requestedScopes.join(","),
requirePkce: requirePkce || false,
logoUrl: logoUrl || null,
homepageUrl: homepageUrl || null,
workspaceId: userRecord.Workspace.id,
createdById: user.id,
},
select: {
id: true,
clientId: true,
clientSecret: true,
name: true,
description: true,
redirectUris: true,
allowedScopes: true,
requirePkce: true,
logoUrl: true,
homepageUrl: true,
isActive: true,
createdAt: true,
},
});
return json({
success: true,
client,
message: "OAuth client created successfully",
});
} catch (error) {
console.error("Error creating OAuth client:", error);
return json({ error: "Internal server error" }, { status: 500 });
}
};