core/apps/webapp/app/routes/api.oauth.clients.tsx
Harshith Mullapudi 714399cf41
Feat: OAuth support for external apps (#22)
* Feat: OAuth support for external apps

* Fix: OAuth screen

---------

Co-authored-by: Manoj K <saimanoj58@gmail.com>
2025-07-19 16:44:15 +05:30

149 lines
3.8 KiB
TypeScript

import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
json,
} from "@remix-run/node";
import { PrismaClient } from "@prisma/client";
import { requireAuth } from "~/utils/auth-helper";
import crypto from "crypto";
const prisma = new PrismaClient();
// 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 },
);
}
// 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 });
}
// 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: Array.isArray(allowedScopes)
? allowedScopes.join(",")
: allowedScopes || "read",
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. Save the client_secret securely - it won't be shown again.",
});
} catch (error) {
console.error("Error creating OAuth client:", error);
return json({ error: "Internal server error" }, { status: 500 });
}
};