mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 17:08:27 +00:00
Feat: add mcp oauth2.1 support
This commit is contained in:
parent
e6da6ad7c5
commit
8ec974f942
@ -36,6 +36,10 @@ setInterval(
|
||||
// MCP request body schema
|
||||
const MCPRequestSchema = z.object({}).passthrough();
|
||||
|
||||
const SourceParams = z.object({
|
||||
source: z.string().optional(),
|
||||
});
|
||||
|
||||
// Search parameters schema for MCP tool
|
||||
const SearchParamsSchema = z.object({
|
||||
query: z.string().describe("The search query in third person perspective"),
|
||||
@ -55,10 +59,11 @@ const handleMCPRequest = async (
|
||||
request: Request,
|
||||
body: any,
|
||||
authentication: any,
|
||||
params: z.infer<typeof SourceParams>,
|
||||
) => {
|
||||
const sessionId = request.headers.get("mcp-session-id") as string | undefined;
|
||||
const source = request.headers.get("source") as string | undefined;
|
||||
|
||||
const source =
|
||||
request.headers.get("source") || (params.source as string | undefined);
|
||||
if (!source) {
|
||||
return json(
|
||||
{
|
||||
@ -241,17 +246,18 @@ const handleDelete = async (request: Request, authentication: any) => {
|
||||
const { action, loader } = createHybridActionApiRoute(
|
||||
{
|
||||
body: MCPRequestSchema,
|
||||
searchParams: SourceParams,
|
||||
allowJWT: true,
|
||||
authorization: {
|
||||
action: "mcp",
|
||||
},
|
||||
corsStrategy: "all",
|
||||
},
|
||||
async ({ body, authentication, request }) => {
|
||||
async ({ body, authentication, request, searchParams }) => {
|
||||
const method = request.method;
|
||||
|
||||
if (method === "POST") {
|
||||
return await handleMCPRequest(request, body, authentication);
|
||||
return await handleMCPRequest(request, body, authentication, searchParams);
|
||||
} else if (method === "DELETE") {
|
||||
return await handleDelete(request, authentication);
|
||||
} else {
|
||||
|
||||
@ -30,7 +30,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
let scopeParam = url.searchParams.get("scope") || undefined;
|
||||
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
|
||||
|
||||
59
apps/webapp/app/routes/oauth.register.tsx
Normal file
59
apps/webapp/app/routes/oauth.register.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { json } from "@remix-run/node";
|
||||
import { type ActionFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { oauth2Service } from "~/services/oauth2.server";
|
||||
|
||||
// Dynamic Client Registration for MCP clients (Claude, etc.)
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
if (request.method !== "POST") {
|
||||
throw new Response("Method Not Allowed", { status: 405 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { client_name, redirect_uris, grant_types, response_types } = body;
|
||||
|
||||
// Validate required fields
|
||||
if (!redirect_uris || !Array.isArray(redirect_uris) || redirect_uris.length === 0) {
|
||||
return json(
|
||||
{ error: "invalid_request", error_description: "redirect_uris is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Create MCP client with special handling
|
||||
const client = await oauth2Service.createDynamicClient({
|
||||
name: client_name || "MCP Client",
|
||||
redirectUris: redirect_uris,
|
||||
grantTypes: grant_types || ["authorization_code"],
|
||||
responseTypes: response_types || ["code"],
|
||||
clientType: "mcp", // Special flag for MCP clients
|
||||
requirePkce: true,
|
||||
allowedScopes: "mcp",
|
||||
});
|
||||
|
||||
return json ({
|
||||
client_id: client.clientId,
|
||||
client_secret: client.clientSecret, // Include if confidential client
|
||||
client_id_issued_at: Math.floor(Date.now() / 1000),
|
||||
grant_types: client.grantTypes.split(","),
|
||||
response_types: ["code"],
|
||||
redirect_uris: client.redirectUris.split(","),
|
||||
scope: client.allowedScopes,
|
||||
token_endpoint_auth_method: "client_secret_post",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Dynamic client registration error:", error);
|
||||
return json(
|
||||
{
|
||||
error: "invalid_request",
|
||||
error_description: "Failed to register client"
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent GET requests
|
||||
export async function loader() {
|
||||
throw new Response("Method Not Allowed", { status: 405 });
|
||||
}
|
||||
@ -691,6 +691,50 @@ export class OAuth2Service {
|
||||
scope: storedRefreshToken.scope || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async createDynamicClient(params: {
|
||||
name: string;
|
||||
redirectUris: string[];
|
||||
grantTypes?: string[];
|
||||
clientType?: string;
|
||||
responseTypes?: string[];
|
||||
requirePkce?: boolean;
|
||||
allowedScopes?: string;
|
||||
description?: string;
|
||||
workspaceId?: string;
|
||||
createdById?: string;
|
||||
}) {
|
||||
// Generate secure client credentials
|
||||
const clientId = crypto.randomBytes(16).toString("hex");
|
||||
const clientSecret = crypto.randomBytes(32).toString("hex");
|
||||
|
||||
// Default values for MCP clients
|
||||
const grantTypes = params.grantTypes || [
|
||||
"authorization_code",
|
||||
"refresh_token",
|
||||
];
|
||||
const allowedScopes = params.allowedScopes || "mcp";
|
||||
const requirePkce = params.requirePkce ?? true; // Default to true for security
|
||||
|
||||
const client = await prisma.oAuthClient.create({
|
||||
data: {
|
||||
clientId,
|
||||
clientSecret,
|
||||
name: params.name,
|
||||
description:
|
||||
params.description ||
|
||||
`Dynamically registered ${params.clientType || "client"}`,
|
||||
redirectUris: params.redirectUris.join(","),
|
||||
grantTypes: grantTypes.join(","),
|
||||
allowedScopes,
|
||||
requirePkce,
|
||||
clientType: "mcp",
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
export const oauth2Service = new OAuth2Service();
|
||||
|
||||
@ -635,11 +635,13 @@ async function wrapResponse(
|
||||
}
|
||||
|
||||
// New hybrid authentication types and functions
|
||||
export type HybridAuthenticationResult = ApiAuthenticationResultSuccess | {
|
||||
ok: true;
|
||||
type: "COOKIE";
|
||||
userId: string;
|
||||
};
|
||||
export type HybridAuthenticationResult =
|
||||
| ApiAuthenticationResultSuccess
|
||||
| {
|
||||
ok: true;
|
||||
type: "COOKIE";
|
||||
userId: string;
|
||||
};
|
||||
|
||||
async function authenticateHybridRequest(
|
||||
request: Request,
|
||||
@ -766,10 +768,9 @@ export function createHybridActionApiRoute<
|
||||
}
|
||||
|
||||
try {
|
||||
const authenticationResult = await authenticateHybridRequest(
|
||||
request,
|
||||
{ allowJWT },
|
||||
);
|
||||
const authenticationResult = await authenticateHybridRequest(request, {
|
||||
allowJWT,
|
||||
});
|
||||
|
||||
if (!authenticationResult) {
|
||||
return await wrapResponse(
|
||||
|
||||
@ -44,10 +44,27 @@ async function init() {
|
||||
|
||||
app.use(morgan("tiny"));
|
||||
|
||||
app.get("/.well-known/oauth-authorization-server", (req, res) => {
|
||||
res.json({
|
||||
issuer: process.env.APP_ORIGIN,
|
||||
authorization_endpoint: `${process.env.APP_ORIGIN}/oauth/authorize`,
|
||||
token_endpoint: `${process.env.APP_ORIGIN}/oauth/token`,
|
||||
registration_endpoint: `${process.env.APP_ORIGIN}/oauth/register`,
|
||||
scopes_supported: ["mcp"],
|
||||
response_types_supported: ["code"],
|
||||
grant_types_supported: [
|
||||
"authorization_code",
|
||||
"refresh_token",
|
||||
"client_credentials",
|
||||
],
|
||||
code_challenge_methods_supported: ["S256"],
|
||||
token_endpoint_auth_methods_supported: ["client_secret_basic", "none"],
|
||||
});
|
||||
});
|
||||
|
||||
// handle SSR requests
|
||||
app.all("*", remixHandler);
|
||||
|
||||
|
||||
const port = process.env.REMIX_APP_PORT || 3000;
|
||||
app.listen(port, () =>
|
||||
console.log(`Express server listening at http://localhost:${port}`),
|
||||
|
||||
207
docs/oauth-integration-webhook-implementation.md
Normal file
207
docs/oauth-integration-webhook-implementation.md
Normal file
@ -0,0 +1,207 @@
|
||||
# OAuth Integration Webhook Implementation
|
||||
|
||||
This document describes the implementation of webhook notifications for OAuth applications when users connect new integrations, following the existing trigger-based architecture.
|
||||
|
||||
## Architecture
|
||||
|
||||
The implementation follows the established pattern used in the Echo system:
|
||||
|
||||
- **Integration Creation**: Happens in `integration-run` trigger
|
||||
- **Webhook Delivery**: Uses dedicated trigger task for asynchronous processing
|
||||
- **Error Handling**: Non-blocking - webhook failures don't affect integration creation
|
||||
|
||||
## Implementation Components
|
||||
|
||||
### 1. OAuth Integration Webhook Delivery Task
|
||||
|
||||
**File**: `apps/webapp/app/trigger/webhooks/oauth-integration-webhook-delivery.ts`
|
||||
|
||||
This is a dedicated trigger task that handles webhook delivery to OAuth applications:
|
||||
|
||||
```typescript
|
||||
export const oauthIntegrationWebhookTask = task({
|
||||
id: "oauth-integration-webhook-delivery",
|
||||
queue: oauthIntegrationWebhookQueue,
|
||||
run: async (payload: OAuthIntegrationWebhookPayload) => {
|
||||
// Implementation
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
|
||||
- Finds OAuth clients with `integration` scope for the user
|
||||
- Sends webhook notifications with integration details
|
||||
- Includes HMAC signature verification
|
||||
- Provides detailed delivery status tracking
|
||||
- Non-blocking error handling
|
||||
|
||||
### 2. Integration into Integration-Run Trigger
|
||||
|
||||
**File**: `apps/webapp/app/trigger/integrations/integration-run.ts`
|
||||
|
||||
Modified the `handleAccountMessage` function to trigger webhook notifications:
|
||||
|
||||
```typescript
|
||||
async function handleAccountMessage(...) {
|
||||
// Create integration account
|
||||
const integrationAccount = await createIntegrationAccount({...});
|
||||
|
||||
// Trigger OAuth integration webhook notifications
|
||||
try {
|
||||
await triggerOAuthIntegrationWebhook(integrationAccount.id, userId);
|
||||
} catch (error) {
|
||||
// Log error but don't fail integration creation
|
||||
}
|
||||
|
||||
return integrationAccount;
|
||||
}
|
||||
```
|
||||
|
||||
**Integration Points**:
|
||||
|
||||
- Triggered after successful integration account creation
|
||||
- Works for all integration types (OAuth, API key, MCP)
|
||||
- Maintains existing integration creation flow
|
||||
|
||||
## Webhook Flow
|
||||
|
||||
### 1. Integration Connection
|
||||
|
||||
When a user connects a new integration:
|
||||
|
||||
1. Integration runs through `IntegrationEventType.SETUP`
|
||||
2. CLI returns "account" message
|
||||
3. `handleAccountMessage` creates integration account
|
||||
4. `triggerOAuthIntegrationWebhook` is called
|
||||
5. Webhook delivery task is queued
|
||||
|
||||
### 2. Webhook Delivery
|
||||
|
||||
The webhook delivery task:
|
||||
|
||||
1. Queries OAuth clients with:
|
||||
- `integration` scope in `allowedScopes`
|
||||
- Active `OAuthIntegrationGrant` for the user
|
||||
- Configured `webhookUrl`
|
||||
2. Sends HTTP POST to each webhook URL
|
||||
3. Logs delivery results
|
||||
|
||||
### 3. Webhook Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "integration.connected",
|
||||
"user_id": "user_uuid",
|
||||
"integration": {
|
||||
"id": "integration_account_uuid",
|
||||
"provider": "linear",
|
||||
"account_id": "external_account_id",
|
||||
"mcp_endpoint": "mcp://core.ai/linear/external_account_id",
|
||||
"name": "Linear",
|
||||
"icon": "https://example.com/linear-icon.png"
|
||||
},
|
||||
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Security Features
|
||||
|
||||
### HMAC Signature
|
||||
|
||||
If OAuth client has `webhookSecret` configured:
|
||||
|
||||
```typescript
|
||||
const signature = crypto
|
||||
.createHmac("sha256", client.webhookSecret)
|
||||
.update(payloadString)
|
||||
.digest("hex");
|
||||
headers["X-Webhook-Secret"] = signature;
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `User-Agent: Echo-OAuth-Webhooks/1.0`
|
||||
- `X-Webhook-Delivery: ${deliveryId}`
|
||||
- `X-Webhook-Event: integration.connected`
|
||||
- `X-Webhook-Secret: ${signature}` (if secret configured)
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Non-Blocking Design
|
||||
|
||||
- Webhook delivery failures do NOT affect integration creation
|
||||
- Errors are logged but don't throw exceptions
|
||||
- Integration process continues normally
|
||||
|
||||
### Retry Strategy
|
||||
|
||||
Currently, the system uses Trigger.dev's built-in retry mechanism:
|
||||
|
||||
- Failed webhook deliveries will be retried automatically
|
||||
- Exponential backoff for temporary failures
|
||||
- Dead letter queue for permanent failures
|
||||
|
||||
### Logging
|
||||
|
||||
Comprehensive logging includes:
|
||||
|
||||
- Integration account details
|
||||
- OAuth client information
|
||||
- HTTP response status and body
|
||||
- Error messages and stack traces
|
||||
- Delivery success/failure counts
|
||||
|
||||
## Database Requirements
|
||||
|
||||
The implementation requires these existing database relationships:
|
||||
|
||||
### OAuthClient
|
||||
|
||||
- `webhookUrl`: Target URL for notifications
|
||||
- `webhookSecret`: Optional HMAC secret
|
||||
- `allowedScopes`: Must include "integration"
|
||||
|
||||
### OAuthIntegrationGrant
|
||||
|
||||
- Links OAuth clients to users
|
||||
- `isActive`: Must be true for notifications
|
||||
- `userId`: Target user for the integration
|
||||
|
||||
### IntegrationAccount
|
||||
|
||||
- Created during integration setup
|
||||
- Includes `integrationDefinition` relationship
|
||||
- Contains provider-specific configuration
|
||||
|
||||
## Testing
|
||||
|
||||
To test the webhook delivery:
|
||||
|
||||
1. **Create OAuth Client** with integration scope:
|
||||
|
||||
```sql
|
||||
UPDATE "OAuthClient"
|
||||
SET "allowedScopes" = 'profile,email,openid,integration',
|
||||
"webhookUrl" = 'https://your-webhook-endpoint.com/webhooks'
|
||||
WHERE "clientId" = 'your-client-id';
|
||||
```
|
||||
|
||||
2. **Grant Integration Access** through OAuth flow with `integration` scope
|
||||
|
||||
3. **Connect Integration** (Linear, Slack, etc.) - webhooks will be triggered automatically
|
||||
|
||||
4. **Monitor Logs** for delivery status and any errors
|
||||
|
||||
## Advantages of This Approach
|
||||
|
||||
1. **Follows Existing Patterns**: Uses the same trigger-based architecture as other webhook systems
|
||||
2. **Scalable**: Leverages Trigger.dev's queue system for handling high volumes
|
||||
3. **Reliable**: Built-in retry and error handling
|
||||
4. **Non-Blocking**: Integration creation is never blocked by webhook issues
|
||||
5. **Comprehensive**: Works with all integration types and OAuth flows
|
||||
6. **Secure**: Includes HMAC signature verification and proper headers
|
||||
7. **Observable**: Detailed logging for monitoring and debugging
|
||||
|
||||
This implementation ensures that OAuth applications are immediately notified when users connect new integrations, while maintaining the reliability and scalability of the existing system architecture.
|
||||
159
docs/webhook-delivery-architecture.md
Normal file
159
docs/webhook-delivery-architecture.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Webhook Delivery Architecture
|
||||
|
||||
This document describes the refactored webhook delivery system that eliminates code duplication by using common utilities.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The webhook delivery system now follows a clean separation of concerns:
|
||||
|
||||
1. **Common Utilities** (`webhook-delivery-utils.ts`) - Shared HTTP delivery logic
|
||||
2. **Activity Webhooks** (`webhook-delivery.ts`) - Workspace-based activity notifications
|
||||
3. **OAuth Integration Webhooks** (`oauth-integration-webhook-delivery.ts`) - OAuth app integration notifications
|
||||
|
||||
## Common Utilities (`webhook-delivery-utils.ts`)
|
||||
|
||||
### Core Function: `deliverWebhook()`
|
||||
|
||||
Handles the common HTTP delivery logic for both webhook types:
|
||||
|
||||
```typescript
|
||||
export async function deliverWebhook(params: WebhookDeliveryParams): Promise<{
|
||||
success: boolean;
|
||||
deliveryResults: DeliveryResult[];
|
||||
summary: { total: number; successful: number; failed: number };
|
||||
}>;
|
||||
```
|
||||
|
||||
**Features:**
|
||||
|
||||
- Generic payload support (works with any webhook structure)
|
||||
- Configurable User-Agent strings
|
||||
- HMAC signature verification with different header formats
|
||||
- 30-second timeout
|
||||
- Comprehensive error handling and logging
|
||||
- Detailed delivery results
|
||||
|
||||
### Helper Function: `prepareWebhookTargets()`
|
||||
|
||||
Converts simple webhook configurations to the standardized target format:
|
||||
|
||||
```typescript
|
||||
export function prepareWebhookTargets(
|
||||
webhooks: Array<{ url: string; secret?: string | null }>
|
||||
): WebhookTarget[];
|
||||
```
|
||||
|
||||
## Activity Webhooks (`webhook-delivery.ts`)
|
||||
|
||||
**Purpose:** Send notifications to workspace webhook configurations when activities are created.
|
||||
|
||||
**Payload Structure:**
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "activity.created",
|
||||
"timestamp": "2024-01-15T10:30:00.000Z",
|
||||
"data": {
|
||||
"id": "activity_id",
|
||||
"text": "Activity content",
|
||||
"sourceURL": "https://source.url",
|
||||
"integrationAccount": { ... },
|
||||
"workspace": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- Uses `X-Hub-Signature-256` header for HMAC verification
|
||||
- Logs delivery results to `WebhookDeliveryLog` table
|
||||
- Targets all active workspace webhook configurations
|
||||
|
||||
## OAuth Integration Webhooks (`oauth-integration-webhook-delivery.ts`)
|
||||
|
||||
**Purpose:** Notify OAuth applications when users connect new integrations.
|
||||
|
||||
**Payload Structure:**
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "integration.connected",
|
||||
"user_id": "user_uuid",
|
||||
"integration": {
|
||||
"id": "integration_account_id",
|
||||
"provider": "linear",
|
||||
"account_id": "external_account_id",
|
||||
"mcp_endpoint": "mcp://core.ai/linear/external_account_id",
|
||||
"name": "Linear",
|
||||
"icon": "https://example.com/icon.png"
|
||||
},
|
||||
"timestamp": "2024-01-15T10:30:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- Uses `X-Webhook-Secret` header for HMAC verification
|
||||
- Custom User-Agent: `Echo-OAuth-Webhooks/1.0`
|
||||
- Targets OAuth clients with `integration` scope and webhook URLs
|
||||
|
||||
## Shared Features
|
||||
|
||||
Both webhook types benefit from the common utilities:
|
||||
|
||||
### Security
|
||||
|
||||
- HMAC-SHA256 signature verification
|
||||
- Configurable secrets per webhook target
|
||||
- Proper HTTP headers for identification
|
||||
|
||||
### Reliability
|
||||
|
||||
- 30-second request timeout
|
||||
- Comprehensive error handling
|
||||
- Non-blocking webhook failures
|
||||
|
||||
### Observability
|
||||
|
||||
- Detailed logging at each step
|
||||
- Delivery success/failure tracking
|
||||
- Response status and body capture (limited)
|
||||
|
||||
### Performance
|
||||
|
||||
- Parallel webhook delivery
|
||||
- Efficient target preparation
|
||||
- Minimal memory footprint
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Activity Webhooks
|
||||
|
||||
- Triggered from: `apps/webapp/app/routes/api.v1.activity.tsx`
|
||||
- Function: `triggerWebhookDelivery(activityId, workspaceId)`
|
||||
|
||||
### OAuth Integration Webhooks
|
||||
|
||||
- Triggered from: `apps/webapp/app/trigger/integrations/integration-run.ts`
|
||||
- Function: `triggerOAuthIntegrationWebhook(integrationAccountId, userId)`
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Code Reuse**: Common HTTP delivery logic eliminates duplication
|
||||
2. **Maintainability**: Single place to update delivery logic
|
||||
3. **Consistency**: Same headers, timeouts, and error handling across webhook types
|
||||
4. **Flexibility**: Easy to add new webhook types by reusing common utilities
|
||||
5. **Testing**: Easier to test common logic independently
|
||||
6. **Security**: Consistent HMAC implementation across all webhook types
|
||||
|
||||
## Adding New Webhook Types
|
||||
|
||||
To add a new webhook type:
|
||||
|
||||
1. Create a new trigger task file (e.g., `new-webhook-delivery.ts`)
|
||||
2. Define your payload structure
|
||||
3. Use `deliverWebhook()` with your payload and targets
|
||||
4. Add your event type to `WebhookEventType` in utils
|
||||
5. Update HMAC header logic in `deliverWebhook()` if needed
|
||||
|
||||
This architecture provides a solid foundation for webhook delivery that can easily scale to support additional webhook types while maintaining code quality and consistency.
|
||||
@ -0,0 +1,10 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "OAuthClient" DROP CONSTRAINT "OAuthClient_createdById_fkey";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "OAuthClient" ADD COLUMN "clientType" TEXT NOT NULL DEFAULT 'regular',
|
||||
ALTER COLUMN "workspaceId" DROP NOT NULL,
|
||||
ALTER COLUMN "createdById" DROP NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OAuthClient" ADD CONSTRAINT "OAuthClient_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@ -287,6 +287,8 @@ model OAuthClient {
|
||||
// PKCE support
|
||||
requirePkce Boolean @default(false)
|
||||
|
||||
clientType String @default("regular")
|
||||
|
||||
// Client metadata
|
||||
logoUrl String?
|
||||
homepageUrl String?
|
||||
@ -299,12 +301,12 @@ model OAuthClient {
|
||||
isActive Boolean @default(true)
|
||||
|
||||
// Workspace relationship (like GitHub orgs)
|
||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String
|
||||
workspace Workspace? @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
|
||||
workspaceId String?
|
||||
|
||||
// Created by user (for audit trail)
|
||||
createdBy User @relation(fields: [createdById], references: [id])
|
||||
createdById String
|
||||
createdBy User? @relation(fields: [createdById], references: [id])
|
||||
createdById String?
|
||||
|
||||
// Relations
|
||||
oauthAuthorizationCodes OAuthAuthorizationCode[]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user