@osiris-ai/postgres-sdk
Version:
Osiris Postgres SDK
1,007 lines (1,004 loc) • 33.8 kB
JavaScript
import { Pool, Client } from 'pg';
import { z } from 'zod';
import { SecretSharingAuthenticator } from '@osiris-ai/sdk';
// @osiris-ai/postgres-sdk - PostgreSQL SDK for Authentication and API Clients
var PostgresDatabaseAdapter = class {
pool;
schema;
config;
initialized = false;
constructor(config) {
this.config = config;
this.schema = config.schema || "public";
console.log("\u{1F527} Initializing PostgreSQL adapter with config:", {
host: config.host,
database: config.database,
schema: this.schema
});
}
async ensureInitialized() {
if (this.initialized && this.pool) return;
try {
this.pool = new Pool({
connectionString: this.config.connectionString,
host: this.config.host,
port: this.config.port,
database: this.config.database,
user: this.config.user,
password: this.config.password,
ssl: this.config.ssl ? { rejectUnauthorized: false } : void 0
});
await this.initialize();
this.initialized = true;
} catch (error) {
throw new Error(
`Failed to initialize PostgreSQL connection: ${error.message}`
);
}
}
async initialize() {
try {
await this.pool.query(`CREATE SCHEMA IF NOT EXISTS ${this.schema};`);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.schema}.connections (
id UUID PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
package_id TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('active', 'inactive', 'pending')),
services TEXT[] NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
is_direct_auth BOOLEAN NOT NULL DEFAULT TRUE
);
`);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.schema}.authentications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
connection_id UUID NOT NULL,
user_id TEXT NOT NULL,
service TEXT NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT,
expires_in INTEGER,
scope TEXT,
token_type TEXT DEFAULT 'bearer',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
user_info JSONB,
UNIQUE(connection_id, service),
FOREIGN KEY (connection_id) REFERENCES ${this.schema}.connections(id) ON DELETE CASCADE
);
`);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.schema}.sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
package_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
connected_services JSONB NOT NULL DEFAULT '{}',
is_direct_auth BOOLEAN NOT NULL DEFAULT TRUE,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'expired', 'completed'))
);
`);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.schema}.secrets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
connection_id UUID NOT NULL,
service TEXT NOT NULL,
credentials JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(connection_id, service),
FOREIGN KEY (connection_id) REFERENCES ${this.schema}.connections(id) ON DELETE CASCADE
);
`);
await this.pool.query(`
CREATE TABLE IF NOT EXISTS ${this.schema}.tokens (
deployment_id TEXT NOT NULL,
service TEXT NOT NULL,
user_id TEXT NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT,
expires_in INTEGER,
scope TEXT,
token_type TEXT DEFAULT 'bearer',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
user_info JSONB,
PRIMARY KEY (deployment_id, service)
);
`);
await this.pool.query(`
CREATE INDEX IF NOT EXISTS authentications_service_idx ON ${this.schema}.authentications (service);
CREATE INDEX IF NOT EXISTS authentications_connection_id_idx ON ${this.schema}.authentications (connection_id);
CREATE INDEX IF NOT EXISTS sessions_package_id_idx ON ${this.schema}.sessions (package_id);
CREATE INDEX IF NOT EXISTS sessions_expires_at_idx ON ${this.schema}.sessions (expires_at);
CREATE INDEX IF NOT EXISTS connections_package_id_idx ON ${this.schema}.connections (package_id);
CREATE INDEX IF NOT EXISTS secrets_connection_id_idx ON ${this.schema}.secrets (connection_id);
CREATE INDEX IF NOT EXISTS tokens_deployment_id_idx ON ${this.schema}.tokens (deployment_id);
`);
} catch (error) {
console.log("\u274C Database initialization error:", error);
throw new Error(`Failed to initialize database schema: ${error.message}`);
}
}
// Authentication management
async storeAuthentication(auth) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
INSERT INTO ${this.schema}.authentications (
connection_id, service, access_token, refresh_token,
expires_in, scope, token_type, user_info, user_id
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (connection_id, service)
DO UPDATE SET
access_token = $3,
refresh_token = $4,
expires_in = $5,
scope = $6,
token_type = $7,
user_info = $8,
user_id = $9,
updated_at = NOW()
RETURNING *
`, [
auth.connectionId,
auth.service,
auth.token.access_token,
auth.token.refresh_token || null,
auth.token.expires_in || null,
auth.token.scope || null,
auth.token.token_type || "bearer",
auth.userInfo,
auth.token.user_id
]);
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
token: {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
},
userInfo: row.user_info,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to store authentication: ${error.message}`);
}
}
async updateAuthentication(connectionId, service, updates) {
await this.ensureInitialized();
try {
const updateFields = ["updated_at = NOW()"];
const values = [];
let paramCount = 1;
if (updates.token) {
if (updates.token.access_token) {
updateFields.push(`access_token = $${paramCount}`);
values.push(updates.token.access_token);
paramCount++;
}
if (updates.token.refresh_token !== void 0) {
updateFields.push(`refresh_token = $${paramCount}`);
values.push(updates.token.refresh_token || null);
paramCount++;
}
if (updates.token.expires_in !== void 0) {
updateFields.push(`expires_in = $${paramCount}`);
values.push(updates.token.expires_in || null);
paramCount++;
}
if (updates.token.scope !== void 0) {
updateFields.push(`scope = $${paramCount}`);
values.push(updates.token.scope || null);
paramCount++;
}
if (updates.token.token_type) {
updateFields.push(`token_type = $${paramCount}`);
values.push(updates.token.token_type);
paramCount++;
}
}
if (updates.userInfo) {
updateFields.push(`user_info = $${paramCount}`);
values.push(updates.userInfo);
paramCount++;
}
values.push(connectionId, service);
const result = await this.pool.query(`
UPDATE ${this.schema}.authentications
SET ${updateFields.join(", ")}
WHERE connection_id = $${paramCount} AND service = $${paramCount + 1}
RETURNING *
`, values);
if (result.rows.length === 0) {
throw new Error(`Authentication with connection ID ${connectionId} and service ${service} not found`);
}
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
token: {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
},
userInfo: row.user_info,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to update authentication: ${error.message}`);
}
}
async getAuthentication(connectionId, service) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.authentications
WHERE connection_id = $1 AND service = $2
`, [connectionId, service]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
token: {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
},
userInfo: row.user_info,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to get authentication: ${error.message}`);
}
}
async getAuthenticationsForConnection(connectionId) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.authentications
WHERE connection_id = $1
ORDER BY created_at DESC
`, [connectionId]);
return result.rows.map((row) => ({
id: row.id,
connectionId: row.connection_id,
service: row.service,
token: {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
},
userInfo: row.user_info,
createdAt: row.created_at,
updatedAt: row.updated_at
}));
} catch (error) {
throw new Error(`Failed to get authentications for connection: ${error.message}`);
}
}
async deleteAuthentication(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
DELETE FROM ${this.schema}.authentications
WHERE id = $1
`, [id]);
return result.rowCount > 0;
} catch (error) {
throw new Error(`Failed to delete authentication: ${error.message}`);
}
}
async getSessions() {
await this.ensureInitialized();
const result = await this.pool.query(`SELECT * FROM ${this.schema}.sessions`);
return result.rows.map((row) => ({
id: row.id,
packageId: row.package_id,
name: row.name,
description: row.description,
createdAt: row.created_at,
expiresAt: row.expires_at,
connectedServices: row.connected_services,
isDirectAuth: row.is_direct_auth,
status: row.status
}));
}
// Session management
async storeSession(session) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
INSERT INTO ${this.schema}.sessions (
package_id, name, description, expires_at,
connected_services, is_direct_auth, status
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`, [
session.packageId,
session.name,
session.description || null,
session.expiresAt,
JSON.stringify(session.connectedServices),
session.isDirectAuth,
session.status
]);
const row = result.rows[0];
return {
id: row.id,
packageId: row.package_id,
name: row.name,
description: row.description,
createdAt: row.created_at,
expiresAt: row.expires_at,
connectedServices: row.connected_services,
isDirectAuth: row.is_direct_auth,
status: row.status
};
} catch (error) {
throw new Error(`Failed to store session: ${error.message}`);
}
}
async updateSession(id, updates) {
await this.ensureInitialized();
try {
const updateFields = [];
const values = [];
let paramCount = 1;
if (updates.connectedServices) {
updateFields.push(`connected_services = $${paramCount}`);
values.push(JSON.stringify(updates.connectedServices));
paramCount++;
}
if (updates.status) {
updateFields.push(`status = $${paramCount}`);
values.push(updates.status);
paramCount++;
}
if (updateFields.length === 0) {
return this.getSession(id);
}
values.push(id);
const result = await this.pool.query(`
UPDATE ${this.schema}.sessions
SET ${updateFields.join(", ")}
WHERE id = $${paramCount}
RETURNING *
`, values);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
packageId: row.package_id,
name: row.name,
description: row.description,
createdAt: row.created_at,
expiresAt: row.expires_at,
connectedServices: row.connected_services,
isDirectAuth: row.is_direct_auth,
status: row.status
};
} catch (error) {
throw new Error(`Failed to update session: ${error.message}`);
}
}
async getSession(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.sessions
WHERE id = $1
`, [id]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
packageId: row.package_id,
name: row.name,
description: row.description,
createdAt: row.created_at,
expiresAt: row.expires_at,
connectedServices: row.connected_services,
isDirectAuth: row.is_direct_auth,
status: row.status
};
} catch (error) {
throw new Error(`Failed to get session: ${error.message}`);
}
}
async deleteSession(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
DELETE FROM ${this.schema}.sessions
WHERE id = $1
`, [id]);
return result.rowCount > 0;
} catch (error) {
throw new Error(`Failed to delete session: ${error.message}`);
}
}
async cleanupExpiredSessions() {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
DELETE FROM ${this.schema}.sessions
WHERE expires_at < NOW()
`);
return result.rowCount;
} catch (error) {
throw new Error(`Failed to cleanup expired sessions: ${error.message}`);
}
}
// Connection management
async storeConnection(connection) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
INSERT INTO ${this.schema}.connections (
id, name, description, package_id, status,
services, is_direct_auth
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
`, [
connection.id,
connection.name,
connection.description || null,
connection.packageId,
connection.status,
connection.services,
connection.isDirectAuth
]);
const row = result.rows[0];
return {
id: row.id,
userId: connection.userId,
name: row.name,
description: row.description,
packageId: row.package_id,
status: row.status,
services: row.services,
createdAt: row.created_at,
updatedAt: row.updated_at,
isDirectAuth: row.is_direct_auth
};
} catch (error) {
throw new Error(`Failed to store connection: ${error.message}`);
}
}
async updateConnection(id, updates) {
await this.ensureInitialized();
try {
const updateFields = ["updated_at = NOW()"];
const values = [];
let paramCount = 1;
if (updates.name) {
updateFields.push(`name = $${paramCount}`);
values.push(updates.name);
paramCount++;
}
if (updates.description !== void 0) {
updateFields.push(`description = $${paramCount}`);
values.push(updates.description || null);
paramCount++;
}
if (updates.status) {
updateFields.push(`status = $${paramCount}`);
values.push(updates.status);
paramCount++;
}
if (updates.services) {
updateFields.push(`services = $${paramCount}`);
values.push(updates.services);
paramCount++;
}
values.push(id);
const result = await this.pool.query(`
UPDATE ${this.schema}.connections
SET ${updateFields.join(", ")}
WHERE id = $${paramCount}
RETURNING *
`, values);
if (result.rows.length === 0) {
throw new Error(`Connection with id ${id} not found`);
}
const row = result.rows[0];
return {
id: row.id,
userId: row.user_id,
name: row.name,
description: row.description,
packageId: row.package_id,
status: row.status,
services: row.services,
createdAt: row.created_at,
updatedAt: row.updated_at,
isDirectAuth: row.is_direct_auth
};
} catch (error) {
throw new Error(`Failed to update connection: ${error.message}`);
}
}
async getConnection(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.connections
WHERE id = $1
`, [id]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
userId: row.user_id,
name: row.name,
description: row.description,
packageId: row.package_id,
status: row.status,
services: row.services,
createdAt: row.created_at,
updatedAt: row.updated_at,
isDirectAuth: row.is_direct_auth
};
} catch (error) {
throw new Error(`Failed to get connection: ${error.message}`);
}
}
async getConnectionsForPackage(packageId) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.connections
WHERE package_id = $1
ORDER BY created_at DESC
`, [packageId]);
return result.rows.map((row) => ({
id: row.id,
userId: row.user_id,
name: row.name,
description: row.description,
packageId: row.package_id,
status: row.status,
services: row.services,
createdAt: row.created_at,
updatedAt: row.updated_at,
isDirectAuth: row.is_direct_auth
}));
} catch (error) {
throw new Error(`Failed to get connections for package: ${error.message}`);
}
}
async deleteConnection(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
DELETE FROM ${this.schema}.connections
WHERE id = $1
`, [id]);
return result.rowCount > 0;
} catch (error) {
throw new Error(`Failed to delete connection: ${error.message}`);
}
}
// Secret management
async storeSecret(secret) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
INSERT INTO ${this.schema}.secrets (
connection_id, service, credentials
) VALUES ($1, $2, $3)
ON CONFLICT (connection_id, service)
DO UPDATE SET
credentials = $3,
updated_at = NOW()
RETURNING *
`, [
secret.connectionId,
secret.service,
JSON.stringify(secret.credentials)
]);
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
credentials: row.credentials,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to store secret: ${error.message}`);
}
}
async updateSecret(id, updates) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
UPDATE ${this.schema}.secrets
SET credentials = $1, updated_at = NOW()
WHERE id = $2
RETURNING *
`, [JSON.stringify(updates.credentials), id]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
credentials: row.credentials,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to update secret: ${error.message}`);
}
}
async getSecretsForConnection(connectionId) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.secrets
WHERE connection_id = $1
ORDER BY created_at DESC
`, [connectionId]);
return result.rows.map((row) => ({
id: row.id,
connectionId: row.connection_id,
service: row.service,
credentials: row.credentials,
createdAt: row.created_at,
updatedAt: row.updated_at
}));
} catch (error) {
throw new Error(`Failed to get secrets for connection: ${error.message}`);
}
}
async getSecret(connectionId, service) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.secrets
WHERE connection_id = $1 AND service = $2
`, [connectionId, service]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
id: row.id,
connectionId: row.connection_id,
service: row.service,
credentials: row.credentials,
createdAt: row.created_at,
updatedAt: row.updated_at
};
} catch (error) {
throw new Error(`Failed to get secret: ${error.message}`);
}
}
async deleteSecret(id) {
await this.ensureInitialized();
try {
const result = await this.pool.query(`
DELETE FROM ${this.schema}.secrets
WHERE id = $1
`, [id]);
return result.rowCount > 0;
} catch (error) {
throw new Error(`Failed to delete secret: ${error.message}`);
}
}
// Legacy methods for backward compatibility
async storeTokenForService(deploymentId, service, token, userInfo) {
await this.ensureInitialized();
try {
if (!deploymentId || !service || !token.access_token) {
throw new Error("deploymentId, service, and access_token are required");
}
await this.pool.query(`
INSERT INTO ${this.schema}.tokens (
deployment_id, service, user_id, access_token, refresh_token,
expires_in, scope, token_type, user_info
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (deployment_id, service)
DO UPDATE SET
user_id = $3,
access_token = $4,
refresh_token = $5,
expires_in = $6,
scope = $7,
token_type = $8,
updated_at = NOW(),
user_info = $9
`, [
deploymentId,
service,
token.user_id,
token.access_token,
token.refresh_token || null,
token.expires_in || null,
token.scope || null,
token.token_type || "bearer",
userInfo
]);
} catch (error) {
throw new Error(`Failed to store token for deployment ${deploymentId}, service ${service}: ${error.message}`);
}
}
async updateTokenForService(deploymentId, service, token, userInfo) {
await this.ensureInitialized();
try {
if (!deploymentId || !service) {
throw new Error("deploymentId and service are required");
}
if (!token && !userInfo) {
await this.pool.query(`
DELETE FROM ${this.schema}.tokens
WHERE deployment_id = $1 AND service = $2
`, [deploymentId, service]);
return;
}
const updateFields = ["updated_at = NOW()"];
const values = [];
let paramCount = 1;
if (token) {
if (token.access_token) {
updateFields.push(`access_token = $${paramCount}`);
values.push(token.access_token);
paramCount++;
}
if (token.refresh_token !== void 0) {
updateFields.push(`refresh_token = $${paramCount}`);
values.push(token.refresh_token || null);
paramCount++;
}
if (token.expires_in !== void 0) {
updateFields.push(`expires_in = $${paramCount}`);
values.push(token.expires_in || null);
paramCount++;
}
if (token.scope !== void 0) {
updateFields.push(`scope = $${paramCount}`);
values.push(token.scope || null);
paramCount++;
}
if (token.token_type) {
updateFields.push(`token_type = $${paramCount}`);
values.push(token.token_type);
paramCount++;
}
if (token.user_id) {
updateFields.push(`user_id = $${paramCount}`);
values.push(token.user_id);
paramCount++;
}
}
if (userInfo) {
updateFields.push(`user_info = $${paramCount}`);
values.push(userInfo);
paramCount++;
}
values.push(deploymentId);
values.push(service);
await this.pool.query(`
UPDATE ${this.schema}.tokens
SET ${updateFields.join(", ")}
WHERE deployment_id = $${paramCount} AND service = $${paramCount + 1}
`, values);
} catch (error) {
throw new Error(`Failed to update token for deployment ${deploymentId}, service ${service}: ${error.message}`);
}
}
async getTokensForDeployment(deploymentId) {
await this.ensureInitialized();
try {
if (!deploymentId) {
throw new Error("deploymentId is required");
}
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.tokens
WHERE deployment_id = $1
`, [deploymentId]);
const tokens = /* @__PURE__ */ new Map();
for (const row of result.rows) {
tokens.set(row.service, {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
});
}
return tokens;
} catch (error) {
throw new Error(`Failed to get tokens for deployment ${deploymentId}: ${error.message}`);
}
}
async getTokenForService(deploymentId, service) {
await this.ensureInitialized();
try {
if (!deploymentId || !service) {
throw new Error("deploymentId and service are required");
}
const result = await this.pool.query(`
SELECT * FROM ${this.schema}.tokens
WHERE deployment_id = $1 AND service = $2
`, [deploymentId, service]);
if (result.rows.length === 0) {
return null;
}
const row = result.rows[0];
return {
access_token: row.access_token,
refresh_token: row.refresh_token,
expires_in: row.expires_in,
scope: row.scope,
token_type: row.token_type,
user_id: row.user_id
};
} catch (error) {
throw new Error(`Failed to get token for deployment ${deploymentId}, service ${service}: ${error.message}`);
}
}
async close() {
try {
if (this.pool) {
await this.pool.end();
this.pool = null;
this.initialized = false;
}
} catch (error) {
throw new Error(`Failed to close database connection: ${error.message}`);
}
}
};
var PostgresSecretSchema = z.object({
db_url: z.string().url({ message: "Invalid database URL format" })
});
var PostgresSecretSharingAuthenticator = class extends SecretSharingAuthenticator {
secrets = null;
constructor() {
super("postgres", PostgresSecretSchema);
}
set(secrets) {
const result = PostgresSecretSchema.safeParse(secrets);
if (!result.success) {
return false;
}
this.secrets = result.data;
return true;
}
getDbUrl() {
return this.secrets?.db_url ?? null;
}
/**
* Execute SQL queries against the PostgreSQL database
* @param params Action parameters containing SQL query
* @param secrets Database connection secrets
* @returns Query results or error response
*/
async action(params, secrets) {
if (!params.data) {
return {
status: 400,
statusText: "No data provided",
data: null
};
}
const sql = params.data.sql;
const queryParams = params.data.params;
if (!sql || !queryParams) {
return {
status: 400,
statusText: "No SQL query provided",
data: null
};
}
const validation = this.validateSql(sql);
if (!validation.isValid) {
return {
status: 400,
statusText: validation.error || "Invalid SQL query",
data: null
};
}
if (!this.secrets?.db_url) {
return {
status: 400,
statusText: "No database connection configured",
data: null
};
}
const client = new Client({ connectionString: this.secrets.db_url });
try {
await client.connect();
const result = await client.query(sql, queryParams);
await client.end();
return {
status: 200,
statusText: "OK",
data: {
rows: result.rows,
rowCount: result.rowCount,
command: result.command
},
headers: {}
};
} catch (error) {
await client.end().catch(() => {
});
return {
status: 500,
statusText: error.message || "Database query failed",
data: null
};
}
}
/**
* Validate SQL query for safety (basic checks)
* @param sql SQL query to validate
* @returns true if query appears safe
*/
validateSql(sql) {
const trimmedSql = sql.trim().toLowerCase();
const dangerousPatterns = [
/^\s*drop\s+/,
/^\s*delete\s+.*\s+where\s+1\s*=\s*1/,
/^\s*truncate\s+/,
/^\s*alter\s+/,
/;\s*drop\s+/,
/;\s*delete\s+/,
/;\s*truncate\s+/
];
for (const pattern of dangerousPatterns) {
if (pattern.test(trimmedSql)) {
return {
isValid: false,
error: "SQL query contains potentially dangerous operations"
};
}
}
return { isValid: true };
}
};
export { PostgresDatabaseAdapter, PostgresSecretSharingAuthenticator };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map