plugin-connections
Version:
Connection management plugin for Twitter authentication and other services
926 lines (916 loc) • 31.7 kB
JavaScript
import {
TwitterApi
} from "./chunk-3TTRDC2T.js";
// src/schema/service-credentials.ts
import {
uuid,
timestamp,
jsonb,
boolean,
index,
unique,
pgSchema
} from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
var pluginConnectionsSchema = pgSchema("plugin_connections");
var serviceTypeEnum = pluginConnectionsSchema.enum("service_type", [
"twitter",
"discord",
"telegram",
"github",
"google",
"facebook",
"linkedin",
"instagram",
"tiktok",
"youtube",
"other"
]);
var serviceCredentialsTable = pluginConnectionsSchema.table(
"service_credentials",
{
id: uuid("id").primaryKey().default(sql`gen_random_uuid()`),
agentId: uuid("agent_id").notNull(),
serviceName: serviceTypeEnum("service_name").notNull(),
credentials: jsonb("credentials").notNull().default("{}"),
isActive: boolean("is_active").default(true).notNull(),
expiresAt: timestamp("expires_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true }).default(sql`now()`).notNull(),
updatedAt: timestamp("updated_at", { withTimezone: true }).default(sql`now()`).notNull()
},
(table) => [
// Indexes for performance
index("service_credentials_agent_id_idx").on(table.agentId),
index("service_credentials_service_name_idx").on(table.serviceName),
index("service_credentials_is_active_idx").on(table.isActive),
index("service_credentials_created_at_idx").on(table.createdAt),
// Unique constraint for one active credential per agent/service
unique("service_credentials_agent_service_unique").on(
table.agentId,
table.serviceName
)
]
);
// src/schema/index.ts
var schema = {
serviceCredentialsTable
};
// src/routes.ts
import { logger as logger2 } from "@elizaos/core";
// src/utils/plugin-loader.ts
import { logger } from "@elizaos/core";
async function updateAndRegisterPlugin(runtime, settingsToUpdate, pluginPackageName) {
if (Object.values(settingsToUpdate).some((value) => !!value)) {
logger.info(`Attempting to dynamically load/reload ${pluginPackageName}...`);
try {
for (const [key, value] of Object.entries(settingsToUpdate)) {
runtime.setSetting(key, value);
}
const { default: plugin2 } = await import(pluginPackageName);
await runtime.registerPlugin(plugin2);
logger.info(`Successfully loaded/reloaded ${pluginPackageName}.`);
} catch (error) {
logger.error(`Failed to dynamically load/reload ${pluginPackageName}`, error);
}
} else {
logger.info(
`No valid credentials found for ${pluginPackageName}. Skipping plugin load.`
);
}
}
// src/routes.ts
var routes = [
// Test route to verify routes are working
{
name: "connections-test",
path: "/connections/test",
type: "GET",
handler: async (req, res) => {
logger2.info("\u{1F9EA} Test route called successfully!");
res.json({ message: "Connections routes are working!", timestamp: /* @__PURE__ */ new Date() });
}
},
// Get all connection statuses
{
name: "connections-list",
path: "/connections",
type: "GET",
handler: async (req, res) => {
try {
const { agentId } = req.query;
if (!agentId) {
return res.status(400).json({ error: "Agent ID is required" });
}
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
const supportedServices = ["twitter"];
const connections = await Promise.all(
supportedServices.map(async (service) => {
const status = await authService.getConnectionStatus(
agentId,
service
);
return {
service,
...status,
// Add service-specific metadata
displayName: service === "twitter" ? "Twitter/X" : service,
icon: service === "twitter" ? "twitter" : service,
color: service === "twitter" ? "#1DA1F2" : "#6B7280",
description: service === "twitter" ? "Connect to post tweets and interact with your audience" : `Connect to ${service}`
};
})
);
res.json({
agentId,
connections,
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
});
} catch (error) {
logger2.error("Connections list failed:", error);
res.status(500).json({
error: "Failed to fetch connections",
details: error instanceof Error ? error.message : String(error)
});
}
}
},
// Get Twitter connection status
{
name: "twitter-status",
path: "/connections/twitter/status",
type: "GET",
handler: async (req, res) => {
try {
const { agentId } = req.query;
if (!agentId) {
return res.status(400).json({ error: "Agent ID is required" });
}
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
const status = await authService.getConnectionStatus(agentId, "twitter");
res.json(status);
} catch (error) {
logger2.error("Twitter status check failed:", error);
res.status(500).json({
error: "Failed to check Twitter status",
details: error instanceof Error ? error.message : String(error)
});
}
}
},
// TWITTER AUTHENTICATION ROUTES
// Initiate Twitter OAuth connection
{
name: "twitter-connect",
path: "/connections/twitter/connect",
type: "POST",
handler: async (req, res) => {
try {
const { agentId, returnUrl } = req.body;
if (!agentId) {
return res.status(400).json({ error: "Agent ID is required" });
}
logger2.info(`Initiating Twitter OAuth for agent: ${agentId}`);
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
const consumerKey = process.env.TWITTER_API_KEY;
const consumerSecret = process.env.TWITTER_API_SECRET_KEY;
if (!consumerKey || !consumerSecret) {
return res.status(500).json({
error: "Twitter API credentials not configured. Please set TWITTER_API_KEY and TWITTER_API_SECRET_KEY."
});
}
const twitterApi = new TwitterApi({
appKey: consumerKey,
appSecret: consumerSecret
});
const callbackUrl = `${req.protocol}://${req.get("host")}/api/connections/twitter/callback`;
logger2.info(`\u{1F517} Attempting to generate Twitter auth link with callback: ${callbackUrl}`);
logger2.info(`\u{1F511} Using API key: ${consumerKey?.substring(0, 8)}...`);
let authLink;
try {
authLink = await twitterApi.generateAuthLink(callbackUrl, {
linkMode: "authorize"
// Use 'authorize' for web apps
});
logger2.info(`\u2705 Twitter auth link generated successfully: ${authLink.url}`);
} catch (twitterError) {
logger2.error(`\u274C Twitter API error:`, {
error: twitterError,
message: twitterError instanceof Error ? twitterError.message : String(twitterError),
callbackUrl,
consumerKey: consumerKey?.substring(0, 8) + "..."
});
throw twitterError;
}
const state = authService.generateOAuthState();
await authService.createOAuthSession(
agentId,
"twitter",
state,
returnUrl
);
authService.storeTempCredentials(authLink.oauth_token, {
oauth_token_secret: authLink.oauth_token_secret,
agentId,
state,
returnUrl
});
res.json({
authUrl: authLink.url
});
} catch (error) {
logger2.error("Twitter OAuth initiation failed:", error);
res.status(500).json({
error: "Failed to initiate Twitter authentication",
details: error instanceof Error ? error.message : String(error)
});
}
}
},
// Handle Twitter OAuth callback
{
name: "twitter-callback",
path: "/connections/twitter/callback",
type: "GET",
handler: async (req, res) => {
try {
const { oauth_token, oauth_verifier } = req.query;
if (!oauth_token || !oauth_verifier) {
return res.status(400).json({
error: "Missing required OAuth parameters"
});
}
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
const tempCredentials = authService.getTempCredentials(oauth_token);
if (!tempCredentials) {
return res.status(400).json({
error: "Invalid or expired OAuth session"
});
}
const { oauth_token_secret, agentId, state, returnUrl } = tempCredentials;
const consumerKey = process.env.TWITTER_API_KEY;
const consumerSecret = process.env.TWITTER_API_SECRET_KEY;
if (!consumerKey || !consumerSecret) {
return res.status(500).json({
error: "Twitter API credentials not configured"
});
}
const twitterApi = new TwitterApi({
appKey: consumerKey,
appSecret: consumerSecret,
accessToken: oauth_token,
accessSecret: oauth_token_secret
});
const { accessToken, accessSecret } = await twitterApi.login(oauth_verifier);
const userApi = new TwitterApi({
appKey: consumerKey,
appSecret: consumerSecret,
accessToken,
accessSecret
});
const user = await userApi.v2.me();
const credentials = {
apiKey: consumerKey,
apiSecretKey: consumerSecret,
accessToken,
accessTokenSecret: accessSecret,
userId: user.data.id,
username: user.data.username
};
await authService.storeCredentials(agentId, "twitter", credentials);
await updateAndRegisterPlugin(
req.runtime,
{
TWITTER_ACCESS_TOKEN: credentials.accessToken,
TWITTER_ACCESS_TOKEN_SECRET: credentials.accessTokenSecret
},
"@elizaos/plugin-twitter"
);
authService.deleteTempCredentials(oauth_token);
const redirectUrl = new URL(`${req.protocol}://${req.get("host")}/chat/${agentId}`);
redirectUrl.searchParams.set("oauth", "success");
redirectUrl.searchParams.set("service", "twitter");
res.redirect(redirectUrl.toString());
} catch (error) {
logger2.error("Twitter OAuth callback failed:", error);
res.status(500).json({
error: "Failed to complete Twitter authentication",
details: error instanceof Error ? error.message : String(error)
});
}
}
},
// Disconnect from Twitter
{
name: "twitter-disconnect",
path: "/connections/twitter/disconnect",
type: "POST",
handler: async (req, res) => {
try {
logger2.info(`\u{1F50C} Twitter disconnect route called for URL: ${req.url}, Method: ${req.method}`);
logger2.info(`\u{1F50C} Request body:`, req.body);
const { agentId } = req.body;
if (!agentId) {
return res.status(400).json({ error: "Agent ID is required" });
}
logger2.info(`Processing Twitter disconnect for agent: ${agentId}`);
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
await authService.revokeCredentials(agentId, "twitter");
await req.runtime.updateAgent(agentId, {
settings: {
secrets: {
TWITTER_ACCESS_TOKEN: null,
TWITTER_ACCESS_TOKEN_SECRET: null
}
}
});
const twitterService = req.runtime.getService("twitter");
if (twitterService && typeof twitterService.stop === "function") {
logger2.warn("\u26A0\uFE0F Stopping Twitter service after disconnect");
await twitterService.stop();
}
res.json({
success: true,
service: "twitter",
message: "Twitter connection disconnected successfully"
});
} catch (error) {
logger2.error("Twitter disconnect failed:", error);
res.status(500).json({
error: "Failed to disconnect Twitter",
details: error instanceof Error ? error.message : String(error)
});
}
}
},
// Test Twitter connection
{
name: "twitter-test",
path: "/connections/twitter/test",
type: "POST",
handler: async (req, res) => {
try {
const { agentId } = req.body;
if (!agentId) {
return res.status(400).json({ error: "Agent ID is required" });
}
logger2.info(`Testing Twitter connection for agent: ${agentId}`);
const authService = req.runtime?.getService("auth");
if (!authService) {
return res.status(500).json({ error: "Auth service not available" });
}
const credentials = await authService.getCredentials(agentId, "twitter");
if (!credentials) {
return res.status(404).json({
error: "No credentials found for Twitter"
});
}
const testResult = await testTwitterConnection(credentials);
res.json({
success: true,
service: "twitter",
test: testResult,
timestamp: (/* @__PURE__ */ new Date()).toISOString()
});
} catch (error) {
logger2.error("Twitter connection test failed:", error);
res.status(500).json({
error: "Failed to test Twitter connection",
details: error instanceof Error ? error.message : String(error)
});
}
}
}
];
async function testTwitterConnection(credentials) {
try {
const { TwitterApi: TwitterApi2 } = await import("./esm-KK6GTMEV.js");
const client = new TwitterApi2({
appKey: credentials.apiKey,
appSecret: credentials.apiSecretKey,
accessToken: credentials.accessToken,
accessSecret: credentials.accessTokenSecret
});
const user = await client.v2.me();
return {
success: true,
user: {
id: user.data.id,
username: user.data.username,
name: user.data.name
},
rateLimit: {
remaining: user.rateLimit?.remaining,
reset: user.rateLimit?.reset
}
};
} catch (error) {
logger2.error("Twitter connection test failed:", error);
return {
success: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
// src/services/auth.service.ts
import { Service, logger as logger4, getSalt as getSalt2, encryptObjectValues } from "@elizaos/core";
import { LRUCache } from "lru-cache";
// src/utils/credentials.ts
import { logger as logger3 } from "@elizaos/core";
import { eq, and } from "drizzle-orm";
import { decryptObjectValues, getSalt } from "@elizaos/core";
async function getCredentials(runtime, serviceName) {
if (!runtime.db) {
return null;
}
try {
const result = await runtime.db.select().from(serviceCredentialsTable).where(
and(
eq(serviceCredentialsTable.agentId, runtime.agentId),
eq(serviceCredentialsTable.serviceName, serviceName),
eq(serviceCredentialsTable.isActive, true)
)
).limit(1);
if (result.length === 0) {
return null;
}
const credentialData = result[0].credentials;
if (credentialData && credentialData.encryptedData) {
const salt = getSalt();
return decryptObjectValues(credentialData.encryptedData, salt);
}
return credentialData;
} catch (error) {
logger3.error(`Failed to get credentials for ${serviceName}:`, error);
return null;
}
}
// src/services/auth.service.ts
import { randomBytes } from "crypto";
var _AuthService = class _AuthService extends Service {
get databaseService() {
const dbService = this.runtime.getService("database");
if (!dbService) {
throw new Error("Database service not available");
}
return dbService;
}
constructor(runtime) {
super(runtime);
this.oauthCache = new LRUCache({
max: 1e3,
ttl: 1e3 * 60 * 15
// 15 minutes
});
}
get capabilityDescription() {
return "Authentication service for managing agent credentials to external services";
}
/**
* Generate a secure random state parameter for OAuth
*/
generateOAuthState() {
return randomBytes(32).toString("hex");
}
static async start(runtime) {
const service = new _AuthService(runtime);
return service;
}
static async stop(runtime) {
const service = runtime.getService(_AuthService.serviceType);
if (service) {
await service.stop();
}
}
async stop() {
}
async storeCredentials(agentId, service, credentials) {
try {
const salt = getSalt2();
const encryptedCredentials = encryptObjectValues(credentials, salt);
await this.databaseService.storeCredentials(agentId, service, {
encryptedData: encryptedCredentials
});
} catch (error) {
logger4.error(`Failed to store credentials for service '${service}':`, error);
throw error;
}
}
async getCredentials(agentId, service) {
return getCredentials(this.runtime, service);
}
async revokeCredentials(agentId, service) {
if (service !== "twitter") {
return;
}
await this.databaseService.deleteCredentials(agentId, service);
}
async getConnectionStatus(agentId, service) {
const credentials = await this.getCredentials(agentId, service);
const settingsKeys = {
twitter: ["TWITTER_ACCESS_TOKEN", "TWITTER_ACCESS_TOKEN_SECRET"]
};
const serviceSettingKeys = settingsKeys[service];
const settings = serviceSettingKeys?.reduce(
(acc, key) => {
acc[key] = this.runtime.getSetting(key);
return acc;
},
{}
);
const settingsToCredentialKeyMap = {
twitter: {
TWITTER_ACCESS_TOKEN: "accessToken",
TWITTER_ACCESS_TOKEN_SECRET: "accessTokenSecret"
}
};
const isPending = (() => {
if (!credentials) {
return false;
}
if (settings) {
const keyMap = settingsToCredentialKeyMap[service];
if (!keyMap) return false;
return Object.keys(settings).some((settingKey) => {
const credentialKey = keyMap[settingKey];
return settings[settingKey] && settings[settingKey] !== credentials[credentialKey];
});
}
return false;
})();
return {
serviceName: service,
isConnected: !!credentials,
isPending,
lastChecked: /* @__PURE__ */ new Date()
};
}
storeTempCredentials(key, credentials) {
this.oauthCache.set(`temp:${key}`, credentials);
}
getTempCredentials(key) {
const credentials = this.oauthCache.get(`temp:${key}`);
return credentials ? credentials : null;
}
deleteTempCredentials(key) {
this.oauthCache.delete(`temp:${key}`);
}
async createOAuthSession(agentId, service, state, returnUrl) {
const sessionData = {
id: crypto.randomUUID(),
agentId,
serviceName: service,
state,
returnUrl,
expiresAt: new Date(Date.now() + 15 * 60 * 1e3),
// 15 minutes
createdAt: /* @__PURE__ */ new Date()
};
this.oauthCache.set(`session:${state}`, sessionData);
}
};
_AuthService.serviceType = "auth";
var AuthService = _AuthService;
// src/services/database.service.ts
import { Service as Service2, logger as logger5 } from "@elizaos/core";
import { eq as eq2, and as and2, sql as sql2 } from "drizzle-orm";
var _DatabaseService = class _DatabaseService extends Service2 {
// The runtime is essential for database access.
constructor(runtime) {
super(runtime);
this.runtime = runtime;
this.tablesCreated = false;
}
get capabilityDescription() {
return "Database service for managing connections plugin database operations";
}
/**
* Direct access to the database via the runtime.
*/
get db() {
if (!this.runtime.db) {
throw new Error("Database not available. Ensure @elizaos/plugin-sql is loaded before this plugin.");
}
return this.runtime.db;
}
/**
* Creates and starts the database service.
*/
static async start(runtime) {
logger5.info("Starting DatabaseService for connections plugin...");
const service = new _DatabaseService(runtime);
await service.initialize();
logger5.info("DatabaseService for connections started successfully.");
return service;
}
/**
* Instance-specific stop method.
* This service does not manage any persistent connections or intervals,
* so no specific cleanup is required here.
*/
async stop() {
}
/**
* Initializes the database service and creates tables if they don't exist.
*/
async initialize() {
try {
await this.createTables();
} catch (error) {
logger5.error("Failed to initialize DatabaseService for connections:", error);
throw error;
}
}
/**
* Creates all necessary tables for the connections plugin.
*/
async createTables() {
if (this.tablesCreated) {
return;
}
try {
logger5.info("Attempting to create database tables for connections plugin...");
await this.db.transaction(async (tx) => {
await tx.execute(sql2`CREATE SCHEMA IF NOT EXISTS plugin_connections`);
await tx.execute(sql2`
DO $$ BEGIN
CREATE TYPE plugin_connections.service_type AS ENUM (
'twitter', 'discord', 'telegram', 'github', 'google',
'facebook', 'linkedin', 'instagram', 'tiktok', 'youtube', 'other'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
`);
await tx.execute(sql2`
CREATE TABLE IF NOT EXISTS plugin_connections.service_credentials (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_id UUID NOT NULL,
service_name plugin_connections.service_type NOT NULL,
credentials JSONB NOT NULL DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT true,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT service_credentials_agent_service_unique UNIQUE (agent_id, service_name)
);
`);
await tx.execute(sql2`CREATE INDEX IF NOT EXISTS service_credentials_agent_id_idx ON plugin_connections.service_credentials (agent_id)`);
await tx.execute(sql2`CREATE INDEX IF NOT EXISTS service_credentials_service_name_idx ON plugin_connections.service_credentials (service_name)`);
});
this.tablesCreated = true;
logger5.info("Database tables for connections plugin created or already exist.");
} catch (error) {
logger5.error("Failed to create database tables for connections plugin:", error);
throw error;
}
}
/**
* Validate database connection.
*/
async validateConnection() {
try {
await this.db.execute(sql2`SELECT 1`);
} catch (error) {
throw new Error(`Database connection failed: ${error}`);
}
}
/**
* Store encrypted credentials for a service.
*/
async storeCredentials(agentId, service, credentials) {
try {
await this.db.execute(sql2`
INSERT INTO plugin_connections.service_credentials (
agent_id, service_name, credentials, is_active, updated_at
) VALUES (
${agentId}, ${service}, ${JSON.stringify(credentials)}, true, now()
)
ON CONFLICT (agent_id, service_name)
DO UPDATE SET
credentials = ${JSON.stringify(credentials)},
is_active = true,
updated_at = now()
`);
} catch (error) {
logger5.error(`Failed to store credentials for service '${service}':`, error);
throw error;
}
}
/**
* Get encrypted credentials for a service.
*/
async getCredentials(agentId, service) {
try {
const result = await this.db.select().from(serviceCredentialsTable).where(
and2(
eq2(serviceCredentialsTable.agentId, agentId),
eq2(serviceCredentialsTable.serviceName, service),
eq2(serviceCredentialsTable.isActive, true)
)
).limit(1);
if (result.length === 0) {
return null;
}
return result[0].credentials;
} catch (error) {
logger5.error(`Failed to get credentials for service '${service}':`, error);
throw error;
}
}
/**
* Delete credentials for a service.
*/
async deleteCredentials(agentId, service) {
try {
await this.db.delete(serviceCredentialsTable).where(
and2(
eq2(serviceCredentialsTable.agentId, agentId),
eq2(serviceCredentialsTable.serviceName, service)
)
);
} catch (error) {
logger5.error(`Failed to delete credentials for service '${service}':`, error);
throw error;
}
}
/**
* Get connection status for all services for an agent.
*/
async getConnectionStatus(agentId) {
try {
const credentials = await this.db.select().from(serviceCredentialsTable).where(eq2(serviceCredentialsTable.agentId, agentId));
return credentials.map((cred) => ({
serviceName: cred.serviceName,
isConnected: cred.isActive === true,
isPending: false,
// Default to false, auth service will determine actual state
lastChecked: cred.updatedAt
}));
} catch (error) {
logger5.error("Failed to get connection status:", error);
throw error;
}
}
/**
* Check if service has credentials.
*/
async hasCredentials(agentId, service) {
try {
const credentials = await this.getCredentials(agentId, service);
return credentials !== null;
} catch (error) {
logger5.error(`Failed to check credentials for service '${service}':`, error);
return false;
}
}
};
_DatabaseService.serviceType = "database";
var DatabaseService = _DatabaseService;
// src/plugin.ts
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
var __filename = fileURLToPath(import.meta.url);
var __dirname = path.dirname(__filename);
var frontendDist = path.resolve(__dirname, "frontend");
var frontPagePath = path.resolve(frontendDist, "index.html");
var assetsPath = path.resolve(frontendDist, "assets");
console.log("*** Using frontendDist:", frontendDist);
console.log("*** frontPagePath", frontPagePath);
console.log("*** assetsPath", assetsPath);
var plugin = {
name: "connections",
description: "Connection management plugin for Twitter authentication and other services",
dependencies: ["@elizaos/plugin-sql"],
schema,
async init(config, runtime) {
console.log("Connections Plugin: Initializing...");
const credentials = await getCredentials(runtime, "twitter");
if (credentials) {
await updateAndRegisterPlugin(
runtime,
{
TWITTER_ACCESS_TOKEN: credentials.accessToken,
TWITTER_ACCESS_TOKEN_SECRET: credentials.accessTokenSecret
},
"@elizaos/plugin-twitter"
);
}
},
routes: [
// Frontend routes
{
type: "GET",
path: "/panels/connections",
name: "Connections",
public: true,
handler: async (_req, res, runtime) => {
const connectionsHtmlPath = path.resolve(frontendDist, "index.html");
console.log("*** Checking for HTML file at:", connectionsHtmlPath);
console.log("*** File exists:", fs.existsSync(connectionsHtmlPath));
if (fs.existsSync(connectionsHtmlPath)) {
let htmlContent = fs.readFileSync(connectionsHtmlPath, "utf-8");
const agentId = runtime.agentId;
const config = {
agentId,
apiBase: `http://localhost:3000`
// This could be configurable
};
htmlContent = htmlContent.replace(
/window\.ELIZA_CONFIG = \{[^}]+\};/,
`window.ELIZA_CONFIG = ${JSON.stringify(config)};`
);
res.setHeader("Content-Type", "text/html");
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Content-Security-Policy", "frame-ancestors 'self' http://localhost:* https://localhost:*");
res.send(htmlContent);
} else {
res.status(404).send("Connections HTML file not found");
}
}
},
{
type: "GET",
path: "/panels/connections/assets/*",
public: true,
handler: async (req, res, _runtime) => {
const fullPath = req.path;
const assetRelativePath = fullPath.replace(/^\/api\/panels\/connections\/assets\//, "").replace(/^\/panels\/connections\/assets\//, "");
console.log("*** Connections assets - fullPath:", fullPath);
console.log("*** Connections assets - assetRelativePath:", assetRelativePath);
if (!assetRelativePath) {
return res.status(400).send("Invalid asset path");
}
const filePath = path.resolve(assetsPath, assetRelativePath);
console.log("*** Connections assets - filePath:", filePath);
if (!filePath.startsWith(assetsPath)) {
return res.status(403).send("Forbidden");
}
if (fs.existsSync(filePath)) {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Content-Security-Policy", "frame-ancestors 'self' http://localhost:* https://localhost:*");
res.sendFile(filePath);
} else {
res.status(404).send("Asset not found");
}
}
},
{
type: "GET",
path: "/assets/*",
public: true,
handler: async (req, res, _runtime) => {
const fullPath = req.path;
const assetRelativePath = fullPath.replace(/^\/api\/assets\//, "").replace(/^\/assets\//, "");
console.log("*** Direct assets - fullPath:", fullPath);
console.log("*** Direct assets - assetRelativePath:", assetRelativePath);
if (!assetRelativePath) {
return res.status(400).send("Invalid asset path");
}
const filePath = path.resolve(assetsPath, assetRelativePath);
console.log("*** Direct assets - filePath:", filePath);
if (!filePath.startsWith(assetsPath)) {
return res.status(403).send("Forbidden");
}
if (fs.existsSync(filePath)) {
res.setHeader("X-Frame-Options", "SAMEORIGIN");
res.setHeader("Content-Security-Policy", "frame-ancestors 'self' http://localhost:* https://localhost:*");
res.sendFile(filePath);
} else {
res.status(404).send("Asset not found");
}
}
},
// All connection and authentication routes
...routes.map((route) => ({
...route,
handler: async (req, res, runtime) => {
req.runtime = runtime;
return route.handler(req, res);
}
}))
],
services: [DatabaseService, AuthService]
};
var plugin_default = plugin;
export {
AuthService,
DatabaseService,
plugin_default as default
};
//# sourceMappingURL=index.js.map