auth.ts•2.32 kB
/**
* JWT validation and credential fetching
*/
import type { Env, MCPJWTPayload, CoziCredentials, BrandCastCredentialsResponse } from './types';
/**
* Validate MCP JWT and extract payload
*/
export async function validateMCPToken(
token: string,
env: Env
): Promise<MCPJWTPayload> {
// For Cloudflare Workers, we'll use Web Crypto API to verify JWT
// This is a simplified version - in production you'd want to use a proper JWT library
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT format');
}
// Decode payload
const payloadBase64 = parts[1];
const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
const payload = JSON.parse(payloadJson) as MCPJWTPayload;
// Verify expiration
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
throw new Error('JWT expired');
}
// Verify audience
if (payload.aud !== 'https://mcp.familycast.app' && payload.aud !== 'https://mcp.brandcast.app') {
throw new Error('Invalid JWT audience');
}
// Verify scope
if (!payload.scope || !payload.scope.includes('mcp:access')) {
throw new Error('Invalid JWT scope');
}
// TODO: Verify signature using env.JWT_SECRET
// For MVP, we'll trust the payload if it passes basic validation
// In production, implement proper HMAC-SHA256 signature verification
return payload;
}
/**
* Fetch Cozi credentials from BrandCast internal API
*/
export async function getCoziCredentials(
userId: string,
env: Env
): Promise<CoziCredentials> {
const url = `${env.BRANDCAST_API_URL}/api/internal/user-credentials`;
const response = await fetch(url, {
method: 'GET',
headers: {
'X-API-Key': env.INTERNAL_API_KEY,
'X-User-ID': userId,
'X-Service': 'cozi',
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Failed to fetch Cozi credentials: ${response.status} ${errorText}`);
}
const data = await response.json() as BrandCastCredentialsResponse;
if (!data.success || !data.data) {
throw new Error(data.error?.message || 'Failed to fetch credentials');
}
return {
accessToken: data.data.accessToken,
expiresAt: data.data.expiresAt,
metadata: data.data.metadata,
};
}