mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-10 07:27:57 -05:00
fix(db): add more options for SSL connection, add envvar for base64 db cert (#1533)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# Database (Required)
|
||||
DATABASE_URL="postgresql://postgres:password@localhost:5432/postgres"
|
||||
# DATABASE_SSL=TRUE # Optional: Enable SSL for database connections (defaults to FALSE)
|
||||
# DATABASE_SSL=disable # Optional: SSL mode (disable, prefer, require, verify-ca, verify-full)
|
||||
# DATABASE_SSL_CA= # Optional: Base64-encoded CA certificate (required for verify-ca/verify-full)
|
||||
# To generate: cat your-ca.crt | base64 | tr -d '\n'
|
||||
|
||||
# PostgreSQL Port (Optional) - defaults to 5432 if not specified
|
||||
# POSTGRES_PORT=5432
|
||||
|
||||
@@ -94,8 +94,6 @@ export async function GET(request: NextRequest) {
|
||||
workflowUpdatedAt: workflow.updatedAt,
|
||||
}
|
||||
|
||||
// Optimized query: Start by filtering workflows in the workspace with user permissions
|
||||
// This ensures we scan only relevant logs instead of the entire table
|
||||
const baseQuery = db
|
||||
.select(selectColumns)
|
||||
.from(workflowExecutionLogs)
|
||||
@@ -103,7 +101,7 @@ export async function GET(request: NextRequest) {
|
||||
workflow,
|
||||
and(
|
||||
eq(workflowExecutionLogs.workflowId, workflow.id),
|
||||
eq(workflow.workspaceId, params.workspaceId) // Filter workspace during join!
|
||||
eq(workflow.workspaceId, params.workspaceId)
|
||||
)
|
||||
)
|
||||
.innerJoin(
|
||||
@@ -184,7 +182,7 @@ export async function GET(request: NextRequest) {
|
||||
.limit(params.limit)
|
||||
.offset(params.offset)
|
||||
|
||||
// Get total count for pagination using the same optimized join structure
|
||||
// Get total count for pagination using the same join structure
|
||||
const countQuery = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(workflowExecutionLogs)
|
||||
@@ -192,7 +190,7 @@ export async function GET(request: NextRequest) {
|
||||
workflow,
|
||||
and(
|
||||
eq(workflowExecutionLogs.workflowId, workflow.id),
|
||||
eq(workflow.workspaceId, params.workspaceId) // Same optimization
|
||||
eq(workflow.workspaceId, params.workspaceId)
|
||||
)
|
||||
)
|
||||
.innerJoin(
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function GET(request: NextRequest) {
|
||||
const conditions = buildLogFilters(filters)
|
||||
const orderBy = getOrderBy(params.order)
|
||||
|
||||
// Build and execute query - optimized to filter workspace during join
|
||||
// Build and execute query
|
||||
const baseQuery = db
|
||||
.select({
|
||||
id: workflowExecutionLogs.id,
|
||||
@@ -128,7 +128,7 @@ export async function GET(request: NextRequest) {
|
||||
workflow,
|
||||
and(
|
||||
eq(workflowExecutionLogs.workflowId, workflow.id),
|
||||
eq(workflow.workspaceId, params.workspaceId) // Filter workspace during join!
|
||||
eq(workflow.workspaceId, params.workspaceId)
|
||||
)
|
||||
)
|
||||
.innerJoin(
|
||||
|
||||
@@ -18,6 +18,8 @@ import { getEnv, isTruthy } from '@/lib/env'
|
||||
import { isHosted } from '@/lib/environment'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useOrganizationStore } from '@/stores/organization'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useSubscriptionStore } from '@/stores/subscription/store'
|
||||
|
||||
const isBillingEnabled = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED'))
|
||||
|
||||
@@ -200,6 +202,21 @@ export function SettingsNavigation({
|
||||
{navigationItems.map((item) => (
|
||||
<div key={item.id} className='mb-1'>
|
||||
<button
|
||||
onMouseEnter={() => {
|
||||
switch (item.id) {
|
||||
case 'general':
|
||||
useGeneralStore.getState().loadSettings()
|
||||
break
|
||||
case 'subscription':
|
||||
useSubscriptionStore.getState().loadData()
|
||||
break
|
||||
case 'team':
|
||||
useOrganizationStore.getState().loadData()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}}
|
||||
onClick={() => onSectionChange(item.id)}
|
||||
className={cn(
|
||||
'group flex h-9 w-full cursor-pointer items-center rounded-[8px] px-2 py-2 font-medium font-sans text-sm transition-colors',
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getVisiblePlans,
|
||||
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription-permissions'
|
||||
import { useOrganizationStore } from '@/stores/organization'
|
||||
import { useGeneralStore } from '@/stores/settings/general/store'
|
||||
import { useSubscriptionStore } from '@/stores/subscription/store'
|
||||
|
||||
const CONSTANTS = {
|
||||
@@ -531,32 +532,14 @@ export function Subscription({ onOpenChange }: SubscriptionProps) {
|
||||
}
|
||||
|
||||
function BillingUsageNotificationsToggle() {
|
||||
const [enabled, setEnabled] = useState<boolean | null>(null)
|
||||
const isLoading = useGeneralStore((s) => s.isBillingUsageNotificationsLoading)
|
||||
const enabled = useGeneralStore((s) => s.isBillingUsageNotificationsEnabled)
|
||||
const setEnabled = useGeneralStore((s) => s.setBillingUsageNotificationsEnabled)
|
||||
const loadSettings = useGeneralStore((s) => s.loadSettings)
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true
|
||||
const load = async () => {
|
||||
const res = await fetch('/api/users/me/settings')
|
||||
const json = await res.json()
|
||||
const current = json?.data?.billingUsageNotificationsEnabled
|
||||
if (isMounted) setEnabled(current !== false)
|
||||
}
|
||||
load()
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const update = async (next: boolean) => {
|
||||
setEnabled(next)
|
||||
await fetch('/api/users/me/settings', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ billingUsageNotificationsEnabled: next }),
|
||||
})
|
||||
}
|
||||
|
||||
if (enabled === null) return null
|
||||
void loadSettings()
|
||||
}, [loadSettings])
|
||||
|
||||
return (
|
||||
<div className='mt-4 flex items-center justify-between'>
|
||||
@@ -564,7 +547,13 @@ function BillingUsageNotificationsToggle() {
|
||||
<span className='font-medium text-sm'>Usage notifications</span>
|
||||
<span className='text-muted-foreground text-xs'>Email me when I reach 80% usage</span>
|
||||
</div>
|
||||
<Switch checked={enabled} onCheckedChange={(v: boolean) => update(v)} />
|
||||
<Switch
|
||||
checked={!!enabled}
|
||||
disabled={isLoading}
|
||||
onCheckedChange={(v: boolean) => {
|
||||
void setEnabled(v)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ export const env = createEnv({
|
||||
server: {
|
||||
// Core Database & Authentication
|
||||
DATABASE_URL: z.string().url(), // Primary database connection string
|
||||
DATABASE_SSL: z.boolean().optional(), // Enable SSL for database connections (defaults to false)
|
||||
DATABASE_SSL: z.enum(['disable', 'prefer', 'require', 'verify-ca', 'verify-full']).optional(), // PostgreSQL SSL mode
|
||||
DATABASE_SSL_CA: z.string().optional(), // Base64-encoded CA certificate for SSL verification
|
||||
BETTER_AUTH_URL: z.string().url(), // Base URL for Better Auth service
|
||||
BETTER_AUTH_SECRET: z.string().min(32), // Secret key for Better Auth JWT signing
|
||||
DISABLE_REGISTRATION: z.boolean().optional(), // Flag to disable new user registration
|
||||
|
||||
@@ -1,17 +1,44 @@
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import * as schema from '@sim/db'
|
||||
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/db'
|
||||
import { and, eq, or, sql } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import { env, isTruthy } from '@/lib/env'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import { loadWorkflowFromNormalizedTables } from '@/lib/workflows/db-helpers'
|
||||
|
||||
const logger = createLogger('SocketDatabase')
|
||||
|
||||
const connectionString = env.DATABASE_URL
|
||||
const useSSL = env.DATABASE_SSL === undefined ? false : isTruthy(env.DATABASE_SSL)
|
||||
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = env.DATABASE_SSL
|
||||
|
||||
if (!sslMode) return undefined
|
||||
if (sslMode === 'disable') return false
|
||||
if (sslMode === 'prefer') return 'prefer'
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
logger.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const socketDb = drizzle(
|
||||
postgres(connectionString, {
|
||||
prepare: false,
|
||||
@@ -20,7 +47,7 @@ const socketDb = drizzle(
|
||||
max: 25,
|
||||
onnotice: () => {},
|
||||
debug: false,
|
||||
ssl: useSSL ? 'require' : false,
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
}),
|
||||
{ schema }
|
||||
)
|
||||
@@ -169,7 +196,7 @@ export async function persistWorkflowOperation(workflowId: string, operation: an
|
||||
const { operation: op, target, payload, timestamp, userId } = operation
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
// Handle different operation types within the transaction first
|
||||
// Handle different operation types within the transaction
|
||||
switch (target) {
|
||||
case 'block':
|
||||
await handleBlockOperationTx(tx, workflowId, op, payload, userId)
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import * as schema from '@sim/db/schema'
|
||||
import { workflowBlocks, workflowEdges } from '@sim/db/schema'
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import type { Server } from 'socket.io'
|
||||
import { env, isTruthy } from '@/lib/env'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const connectionString = env.DATABASE_URL
|
||||
const useSSL = env.DATABASE_SSL === undefined ? false : isTruthy(env.DATABASE_SSL)
|
||||
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = env.DATABASE_SSL
|
||||
|
||||
if (!sslMode) return undefined
|
||||
if (sslMode === 'disable') return false
|
||||
if (sslMode === 'prefer') return 'prefer'
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
console.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const db = drizzle(
|
||||
postgres(connectionString, {
|
||||
prepare: false,
|
||||
@@ -17,7 +44,7 @@ const db = drizzle(
|
||||
connect_timeout: 20,
|
||||
max: 5,
|
||||
onnotice: () => {},
|
||||
ssl: useSSL ? 'require' : false,
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
}),
|
||||
{ schema }
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import * as schema from './schema'
|
||||
@@ -10,20 +11,52 @@ if (!connectionString) {
|
||||
throw new Error('Missing DATABASE_URL environment variable')
|
||||
}
|
||||
|
||||
function isTruthy(value: string | undefined): boolean {
|
||||
if (!value) return false
|
||||
return value.toLowerCase() === 'true' || value === '1'
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = process.env.DATABASE_SSL?.toLowerCase()
|
||||
|
||||
if (!sslMode) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (sslMode === 'disable') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (sslMode === 'prefer') {
|
||||
return 'prefer'
|
||||
}
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (process.env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(process.env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
console.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid DATABASE_SSL mode: ${sslMode}. Must be one of: disable, prefer, require, verify-ca, verify-full`
|
||||
)
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL)
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const postgresClient = postgres(connectionString, {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 30,
|
||||
max: 80,
|
||||
onnotice: () => {},
|
||||
ssl: useSSL ? 'require' : false,
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
})
|
||||
|
||||
const drizzleClient = drizzle(postgresClient, { schema })
|
||||
|
||||
211
packages/db/scripts/deregister-sso-provider.ts
Normal file
211
packages/db/scripts/deregister-sso-provider.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Deregister SSO Provider Script
|
||||
*
|
||||
* This script removes an SSO provider from the database for a specific user.
|
||||
*
|
||||
* Usage: bun run packages/db/scripts/deregister-sso-provider.ts
|
||||
*
|
||||
* Required Environment Variables:
|
||||
* DATABASE_URL=your-database-url
|
||||
* SSO_USER_EMAIL=user@domain.com (user whose SSO provider to remove)
|
||||
* SSO_PROVIDER_ID=provider-id (optional, if not provided will remove all providers for user)
|
||||
*/
|
||||
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import { ssoProvider, user } from '../schema'
|
||||
|
||||
// Simple console logger
|
||||
const logger = {
|
||||
info: (message: string, meta?: any) => {
|
||||
const timestamp = new Date().toISOString()
|
||||
console.log(
|
||||
`[${timestamp}] [INFO] [DeregisterSSODB] ${message}`,
|
||||
meta ? JSON.stringify(meta, null, 2) : ''
|
||||
)
|
||||
},
|
||||
error: (message: string, meta?: any) => {
|
||||
const timestamp = new Date().toISOString()
|
||||
console.error(
|
||||
`[${timestamp}] [ERROR] [DeregisterSSODB] ${message}`,
|
||||
meta ? JSON.stringify(meta, null, 2) : ''
|
||||
)
|
||||
},
|
||||
warn: (message: string, meta?: any) => {
|
||||
const timestamp = new Date().toISOString()
|
||||
console.warn(
|
||||
`[${timestamp}] [WARN] [DeregisterSSODB] ${message}`,
|
||||
meta ? JSON.stringify(meta, null, 2) : ''
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// Get database URL from environment
|
||||
const CONNECTION_STRING = process.env.DATABASE_URL
|
||||
if (!CONNECTION_STRING) {
|
||||
console.error('❌ DATABASE_URL environment variable is required')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = process.env.DATABASE_SSL?.toLowerCase()
|
||||
|
||||
if (!sslMode) return undefined
|
||||
if (sslMode === 'disable') return false
|
||||
if (sslMode === 'prefer') return 'prefer'
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (process.env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(process.env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
console.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid DATABASE_SSL mode: ${sslMode}. Must be one of: disable, prefer, require, verify-ca, verify-full`
|
||||
)
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const postgresClient = postgres(CONNECTION_STRING, {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 30,
|
||||
max: 10,
|
||||
onnotice: () => {},
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
})
|
||||
const db = drizzle(postgresClient)
|
||||
|
||||
async function getUser(email: string): Promise<{ id: string; email: string } | null> {
|
||||
try {
|
||||
const users = await db.select().from(user).where(eq(user.email, email))
|
||||
if (users.length === 0) {
|
||||
logger.error(`No user found with email: ${email}`)
|
||||
return null
|
||||
}
|
||||
return { id: users[0].id, email: users[0].email }
|
||||
} catch (error) {
|
||||
logger.error('Failed to query user:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function deregisterSSOProvider(): Promise<boolean> {
|
||||
try {
|
||||
const userEmail = process.env.SSO_USER_EMAIL
|
||||
if (!userEmail) {
|
||||
logger.error('❌ SSO_USER_EMAIL environment variable is required')
|
||||
logger.error('')
|
||||
logger.error('Example usage:')
|
||||
logger.error(
|
||||
' SSO_USER_EMAIL=admin@company.com bun run packages/db/scripts/deregister-sso-provider.ts'
|
||||
)
|
||||
logger.error('')
|
||||
logger.error('Optional: SSO_PROVIDER_ID=provider-id (to remove specific provider)')
|
||||
return false
|
||||
}
|
||||
|
||||
// Get user
|
||||
const targetUser = await getUser(userEmail)
|
||||
if (!targetUser) {
|
||||
return false
|
||||
}
|
||||
|
||||
logger.info(`Found user: ${targetUser.email} (ID: ${targetUser.id})`)
|
||||
|
||||
// Get SSO providers for this user
|
||||
const providers = await db
|
||||
.select()
|
||||
.from(ssoProvider)
|
||||
.where(eq(ssoProvider.userId, targetUser.id))
|
||||
|
||||
if (providers.length === 0) {
|
||||
logger.warn(`No SSO providers found for user: ${targetUser.email}`)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.info(`Found ${providers.length} SSO provider(s) for user ${targetUser.email}`)
|
||||
for (const provider of providers) {
|
||||
logger.info(` - Provider ID: ${provider.providerId}, Domain: ${provider.domain}`)
|
||||
}
|
||||
|
||||
// Check if specific provider ID was requested
|
||||
const specificProviderId = process.env.SSO_PROVIDER_ID
|
||||
|
||||
if (specificProviderId) {
|
||||
// Delete specific provider
|
||||
const providerToDelete = providers.find((p) => p.providerId === specificProviderId)
|
||||
if (!providerToDelete) {
|
||||
logger.error(`Provider '${specificProviderId}' not found for user ${targetUser.email}`)
|
||||
return false
|
||||
}
|
||||
|
||||
await db
|
||||
.delete(ssoProvider)
|
||||
.where(
|
||||
and(eq(ssoProvider.userId, targetUser.id), eq(ssoProvider.providerId, specificProviderId))
|
||||
)
|
||||
|
||||
logger.info(
|
||||
`✅ Successfully deleted SSO provider '${specificProviderId}' for user ${targetUser.email}`
|
||||
)
|
||||
} else {
|
||||
// Delete all providers for this user
|
||||
await db.delete(ssoProvider).where(eq(ssoProvider.userId, targetUser.id))
|
||||
|
||||
logger.info(
|
||||
`✅ Successfully deleted all ${providers.length} SSO provider(s) for user ${targetUser.email}`
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to deregister SSO provider:', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
return false
|
||||
} finally {
|
||||
try {
|
||||
await postgresClient.end({ timeout: 5 })
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🗑️ Deregister SSO Provider Script')
|
||||
console.log('====================================')
|
||||
console.log('This script removes SSO provider records from the database.\n')
|
||||
|
||||
const success = await deregisterSSOProvider()
|
||||
|
||||
if (success) {
|
||||
console.log('\n🎉 SSO provider deregistration completed successfully!')
|
||||
process.exit(0)
|
||||
} else {
|
||||
console.log('\n💥 SSO deregistration failed. Check the logs above for details.')
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle script execution
|
||||
main().catch((error) => {
|
||||
logger.error('Script execution failed:', { error })
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -3,6 +3,7 @@
|
||||
// This script is intentionally self-contained for execution in the migrations image.
|
||||
// Do not import from the main app code; duplicate minimal schema and DB setup here.
|
||||
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import { sql } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
@@ -117,20 +118,44 @@ const workflowDeploymentVersion = pgTable(
|
||||
)
|
||||
|
||||
// ---------- DB client ----------
|
||||
function isTruthy(value: string | undefined): boolean {
|
||||
if (!value) return false
|
||||
return value.toLowerCase() === 'true' || value === '1'
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = process.env.DATABASE_SSL?.toLowerCase()
|
||||
|
||||
if (!sslMode) return undefined
|
||||
if (sslMode === 'disable') return false
|
||||
if (sslMode === 'prefer') return 'prefer'
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (process.env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(process.env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
console.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid DATABASE_SSL mode: ${sslMode}. Must be one of: disable, prefer, require, verify-ca, verify-full`
|
||||
)
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL)
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const postgresClient = postgres(CONNECTION_STRING, {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 30,
|
||||
max: 10,
|
||||
onnotice: () => {},
|
||||
ssl: useSSL ? 'require' : false,
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
})
|
||||
const db = drizzle(postgresClient)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
* SSO_SAML_WANT_ASSERTIONS_SIGNED=true (optional, defaults to false)
|
||||
*/
|
||||
|
||||
import type { ConnectionOptions } from 'node:tls'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
@@ -140,20 +141,44 @@ if (!CONNECTION_STRING) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function isTruthy(value: string | undefined): boolean {
|
||||
if (!value) return false
|
||||
return value.toLowerCase() === 'true' || value === '1'
|
||||
const getSSLConfig = () => {
|
||||
const sslMode = process.env.DATABASE_SSL?.toLowerCase()
|
||||
|
||||
if (!sslMode) return undefined
|
||||
if (sslMode === 'disable') return false
|
||||
if (sslMode === 'prefer') return 'prefer'
|
||||
|
||||
const sslConfig: ConnectionOptions = {}
|
||||
|
||||
if (sslMode === 'require') {
|
||||
sslConfig.rejectUnauthorized = false
|
||||
} else if (sslMode === 'verify-ca' || sslMode === 'verify-full') {
|
||||
sslConfig.rejectUnauthorized = true
|
||||
if (process.env.DATABASE_SSL_CA) {
|
||||
try {
|
||||
const ca = Buffer.from(process.env.DATABASE_SSL_CA, 'base64').toString('utf-8')
|
||||
sslConfig.ca = ca
|
||||
} catch (error) {
|
||||
console.error('Failed to parse DATABASE_SSL_CA:', error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Invalid DATABASE_SSL mode: ${sslMode}. Must be one of: disable, prefer, require, verify-ca, verify-full`
|
||||
)
|
||||
}
|
||||
|
||||
return sslConfig
|
||||
}
|
||||
|
||||
const useSSL = process.env.DATABASE_SSL === undefined ? false : isTruthy(process.env.DATABASE_SSL)
|
||||
|
||||
const sslConfig = getSSLConfig()
|
||||
const postgresClient = postgres(CONNECTION_STRING, {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 30,
|
||||
max: 10,
|
||||
onnotice: () => {},
|
||||
ssl: useSSL ? 'require' : false,
|
||||
...(sslConfig !== undefined && { ssl: sslConfig }),
|
||||
})
|
||||
const db = drizzle(postgresClient)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user