mirror of https://github.com/nocodb/nocodb
Pranav C
2 months ago
10 changed files with 724 additions and 0 deletions
@ -0,0 +1,112 @@ |
|||||||
|
import { SqlClientFactory, MetaTable, decryptPropIfRequired, encryptPropIfRequired } from 'nocodb'; |
||||||
|
|
||||||
|
export class SecretManager { |
||||||
|
|
||||||
|
private sqlClient; |
||||||
|
|
||||||
|
constructor(private oldSecret: string, private newSecret: string, private config: any) { |
||||||
|
this.sqlClient = SqlClientFactory.create(this.config.meta.db); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// validate config by checking if database config is valid
|
||||||
|
async validateConfig() { |
||||||
|
// use the sqlClientFactory to create a new sql client and then use testConnection to test the connection
|
||||||
|
const isValid = await this.sqlClient.testConnection(); |
||||||
|
if (!isValid) { |
||||||
|
throw new Error('Invalid database configuration'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async validateAndExtract() { |
||||||
|
const sources = await this.sqlClient.knex(MetaTable.SOURCES).where(qb => { |
||||||
|
qb.where('is_meta', false).orWhere('is_meta', null) |
||||||
|
}); |
||||||
|
|
||||||
|
const integrations = await this.sqlClient.knex(MetaTable.INTEGRATIONS).where(qb => { |
||||||
|
qb.where('is_meta', false).orWhere('is_meta', null) |
||||||
|
}); |
||||||
|
|
||||||
|
const sourcesToUpdate: Record<string, any>[] = []; |
||||||
|
const integrationsToUpdate: Record<string, any>[] = []; |
||||||
|
|
||||||
|
|
||||||
|
let isValid = false; |
||||||
|
for (const source of sources) { |
||||||
|
try { |
||||||
|
const decrypted = decryptPropIfRequired({ |
||||||
|
data: source, |
||||||
|
secret: this.oldSecret, |
||||||
|
prop: 'config' |
||||||
|
}); |
||||||
|
isValid = true; |
||||||
|
sourcesToUpdate.push({ ...source, config: decrypted }); |
||||||
|
} catch (e) { |
||||||
|
console.log(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (const integration of integrations) { |
||||||
|
try { |
||||||
|
const decrypted = decryptPropIfRequired({ |
||||||
|
data: integration, |
||||||
|
secret: this.oldSecret, |
||||||
|
prop: 'config' |
||||||
|
}); |
||||||
|
isValid = true; |
||||||
|
integrationsToUpdate.push({ ...integration, config: decrypted }); |
||||||
|
} catch (e) { |
||||||
|
console.log(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if all of the decyptions are failed then throw an error
|
||||||
|
if (!isValid) { |
||||||
|
throw new Error('Invalid old secret or no sources/integrations found'); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return { sourcesToUpdate, integrationsToUpdate }; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
async updateSecret( |
||||||
|
sourcesToUpdate: Record<string, any>[], |
||||||
|
integrationsToUpdate: Record<string, any>[] |
||||||
|
) { |
||||||
|
// start transaction
|
||||||
|
const transaction = await this.sqlClient.transaction(); |
||||||
|
|
||||||
|
try { |
||||||
|
// update sources
|
||||||
|
for (const source of sourcesToUpdate) { |
||||||
|
await transaction(MetaTable.SOURCES).update({ |
||||||
|
config: encryptPropIfRequired({ |
||||||
|
data: source, |
||||||
|
secret: this.newSecret, |
||||||
|
prop: 'config' |
||||||
|
}) |
||||||
|
}).where('id', source.id); |
||||||
|
} |
||||||
|
|
||||||
|
// update integrations
|
||||||
|
for (const integration of integrationsToUpdate) { |
||||||
|
await transaction(MetaTable.INTEGRATIONS).update({ |
||||||
|
config: encryptPropIfRequired({ |
||||||
|
data: integration, |
||||||
|
secret: this.newSecret, |
||||||
|
prop: 'config' |
||||||
|
}) |
||||||
|
}).where('id', integration.id); |
||||||
|
} |
||||||
|
|
||||||
|
await transaction.commit(); |
||||||
|
|
||||||
|
} catch (e) { |
||||||
|
console.log(e); |
||||||
|
await transaction.rollback(); |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
import figlet from "figlet"; |
||||||
|
|
||||||
|
console.log(figlet.textSync("Nocodb Secret CLI")); |
||||||
|
|
||||||
|
import { Command } from 'commander'; |
||||||
|
import { getNocoConfig } from "./nc-config"; |
||||||
|
import { SecretManager } from "./core/SecretManager"; |
||||||
|
|
||||||
|
const program = new Command(); |
||||||
|
|
||||||
|
program |
||||||
|
.version('1.0.0') |
||||||
|
.description('NocoDB Secret CLI') |
||||||
|
.arguments('<oldSecret> <newSecret>') |
||||||
|
.action(async (key, value) => { |
||||||
|
|
||||||
|
const config = await getNocoConfig(); |
||||||
|
|
||||||
|
if (!key || !value) { |
||||||
|
console.error('Error: Both key and value are required.'); |
||||||
|
program.help(); |
||||||
|
} else { |
||||||
|
const secretManager = new SecretManager(key, value, config); |
||||||
|
|
||||||
|
// validate meta db config which is resolved from env variables
|
||||||
|
await secretManager.validateConfig(); |
||||||
|
|
||||||
|
// validate old secret
|
||||||
|
const { sourcesToUpdate, integrationsToUpdate } = await secretManager.validateAndExtract(); |
||||||
|
|
||||||
|
|
||||||
|
// update sources and integrations
|
||||||
|
await secretManager.updateSecret(sourcesToUpdate, integrationsToUpdate); |
||||||
|
|
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Add error handling
|
||||||
|
program.exitOverride(); |
||||||
|
|
||||||
|
program.parse(process.argv); |
@ -0,0 +1,95 @@ |
|||||||
|
import * as path from 'path'; |
||||||
|
import fs from 'fs'; |
||||||
|
import { promisify } from 'util'; |
||||||
|
import { getToolDir, metaUrlToDbConfig } from './helpers'; |
||||||
|
import { DriverClient } from './interfaces'; |
||||||
|
import type { DbConfig } from './interfaces'; |
||||||
|
import { SqlClientFactory } from 'nocodb'; |
||||||
|
|
||||||
|
export class NcConfig { |
||||||
|
meta: { |
||||||
|
db: DbConfig; |
||||||
|
} = { |
||||||
|
db: { |
||||||
|
client: DriverClient.SQLITE, |
||||||
|
connection: { |
||||||
|
filename: 'noco.db', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
toolDir: string; |
||||||
|
|
||||||
|
credentialSecret?: string; |
||||||
|
|
||||||
|
private constructor() { |
||||||
|
this.toolDir = getToolDir(); |
||||||
|
} |
||||||
|
|
||||||
|
public static async create(param: { |
||||||
|
meta: { |
||||||
|
metaUrl?: string; |
||||||
|
metaJson?: string; |
||||||
|
metaJsonFile?: string; |
||||||
|
}; |
||||||
|
secret?: string; |
||||||
|
credentialSecret?: string; |
||||||
|
}): Promise<NcConfig> { |
||||||
|
const { meta, secret } = |
||||||
|
param; |
||||||
|
|
||||||
|
const ncConfig = new NcConfig(); |
||||||
|
|
||||||
|
|
||||||
|
ncConfig.credentialSecret = param.credentialSecret; |
||||||
|
|
||||||
|
|
||||||
|
if (ncConfig.meta?.db?.connection?.filename) { |
||||||
|
ncConfig.meta.db.connection.filename = path.join( |
||||||
|
ncConfig.toolDir, |
||||||
|
ncConfig.meta.db.connection.filename, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (meta?.metaUrl) { |
||||||
|
ncConfig.meta.db = await metaUrlToDbConfig(meta.metaUrl); |
||||||
|
} else if (meta?.metaJson) { |
||||||
|
ncConfig.meta.db = JSON.parse(meta.metaJson); |
||||||
|
} else if (meta?.metaJsonFile) { |
||||||
|
if (!(await promisify(fs.exists)(meta.metaJsonFile))) { |
||||||
|
throw new Error(`NC_DB_JSON_FILE not found: ${meta.metaJsonFile}`); |
||||||
|
} |
||||||
|
const fileContent = await promisify(fs.readFile)(meta.metaJsonFile, { |
||||||
|
encoding: 'utf8', |
||||||
|
}); |
||||||
|
ncConfig.meta.db = JSON.parse(fileContent); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return ncConfig; |
||||||
|
} |
||||||
|
|
||||||
|
public static async createByEnv(): Promise<NcConfig> { |
||||||
|
return NcConfig.create({ |
||||||
|
meta: { |
||||||
|
metaUrl: process.env.NC_DB, |
||||||
|
metaJson: process.env.NC_DB_JSON, |
||||||
|
metaJsonFile: process.env.NC_DB_JSON_FILE, |
||||||
|
}, |
||||||
|
secret: process.env.NC_AUTH_JWT_SECRET, |
||||||
|
credentialSecret: process.env.NC_KEY_CREDENTIAL_ENCRYPT, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const getNocoConfig = () =>{ |
||||||
|
return NcConfig.create({ |
||||||
|
meta: { |
||||||
|
metaUrl: process.env.NC_DB, |
||||||
|
metaJson: process.env.NC_DB_JSON, |
||||||
|
metaJsonFile: process.env.NC_DB_JSON_FILE, |
||||||
|
}, |
||||||
|
secret: process.env.NC_AUTH_JWT_SECRET, |
||||||
|
credentialSecret: process.env.NC_KEY_CREDENTIAL_ENCRYPT, |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
export const driverClientMapping = { |
||||||
|
mysql: 'mysql2', |
||||||
|
mariadb: 'mysql2', |
||||||
|
postgres: 'pg', |
||||||
|
postgresql: 'pg', |
||||||
|
sqlite: 'sqlite3', |
||||||
|
mssql: 'mssql', |
||||||
|
}; |
||||||
|
|
||||||
|
export const defaultClientPortMapping = { |
||||||
|
mysql: 3306, |
||||||
|
mysql2: 3306, |
||||||
|
postgres: 5432, |
||||||
|
pg: 5432, |
||||||
|
mssql: 1433, |
||||||
|
}; |
||||||
|
|
||||||
|
export const defaultConnectionConfig: any = { |
||||||
|
// https://github.com/knex/knex/issues/97
|
||||||
|
// timezone: process.env.NC_TIMEZONE || 'UTC',
|
||||||
|
dateStrings: true, |
||||||
|
}; |
||||||
|
|
||||||
|
// default knex options
|
||||||
|
export const defaultConnectionOptions = { |
||||||
|
pool: { |
||||||
|
min: 0, |
||||||
|
max: 10, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export const avoidSSL = [ |
||||||
|
'localhost', |
||||||
|
'127.0.0.1', |
||||||
|
'host.docker.internal', |
||||||
|
'172.17.0.1', |
||||||
|
]; |
||||||
|
|
||||||
|
export const knownQueryParams = [ |
||||||
|
{ |
||||||
|
parameter: 'database', |
||||||
|
aliases: ['d', 'db'], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'password', |
||||||
|
aliases: ['p'], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'user', |
||||||
|
aliases: ['u'], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'title', |
||||||
|
aliases: ['t'], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'keyFilePath', |
||||||
|
aliases: [], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'certFilePath', |
||||||
|
aliases: [], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'caFilePath', |
||||||
|
aliases: [], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'ssl', |
||||||
|
aliases: [], |
||||||
|
}, |
||||||
|
{ |
||||||
|
parameter: 'options', |
||||||
|
aliases: ['opt', 'opts'], |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
export enum DriverClient { |
||||||
|
MYSQL = 'mysql2', |
||||||
|
MYSQL_LEGACY = 'mysql', |
||||||
|
MSSQL = 'mssql', |
||||||
|
PG = 'pg', |
||||||
|
SQLITE = 'sqlite3', |
||||||
|
SNOWFLAKE = 'snowflake', |
||||||
|
DATABRICKS = 'databricks', |
||||||
|
} |
@ -0,0 +1,326 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
import { URL } from 'url'; |
||||||
|
import { promisify } from 'util'; |
||||||
|
import parseDbUrl from 'parse-database-url'; |
||||||
|
import { |
||||||
|
avoidSSL, |
||||||
|
defaultClientPortMapping, |
||||||
|
defaultConnectionConfig, |
||||||
|
defaultConnectionOptions, |
||||||
|
driverClientMapping, |
||||||
|
knownQueryParams, |
||||||
|
} from './constants'; |
||||||
|
import { DriverClient } from './interfaces'; |
||||||
|
import type { Connection, DbConfig } from './interfaces'; |
||||||
|
|
||||||
|
export async function prepareEnv() { |
||||||
|
if (process.env.NC_DATABASE_URL_FILE || process.env.DATABASE_URL_FILE) { |
||||||
|
const database_url = await promisify(fs.readFile)( |
||||||
|
(process.env.NC_DATABASE_URL_FILE || process.env.DATABASE_URL_FILE) as string, |
||||||
|
'utf-8', |
||||||
|
); |
||||||
|
process.env.NC_DB = jdbcToXcUrl(database_url); |
||||||
|
} else if (process.env.NC_DATABASE_URL || process.env.DATABASE_URL) { |
||||||
|
process.env.NC_DB = jdbcToXcUrl( |
||||||
|
(process.env.NC_DATABASE_URL || process.env.DATABASE_URL) as string, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function getToolDir() { |
||||||
|
return process.env.NC_TOOL_DIR || process.cwd(); |
||||||
|
} |
||||||
|
|
||||||
|
export function jdbcToXcConfig(url: string): DbConfig { |
||||||
|
// drop the jdbc prefix
|
||||||
|
url.replace(/^jdbc:/, ''); |
||||||
|
|
||||||
|
const config = parseDbUrl(url); |
||||||
|
|
||||||
|
const parsedConfig: Connection = {}; |
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(config)) { |
||||||
|
const fnd = knownQueryParams.find( |
||||||
|
(param) => param.parameter === key || param.aliases.includes(key), |
||||||
|
); |
||||||
|
if (fnd) { |
||||||
|
parsedConfig[fnd.parameter] = value; |
||||||
|
} else { |
||||||
|
parsedConfig[key] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!parsedConfig?.port) { |
||||||
|
parsedConfig.port = |
||||||
|
defaultClientPortMapping[ |
||||||
|
driverClientMapping[parsedConfig.driver as DriverClient] || parsedConfig.driver |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
const { driver, ...connectionConfig } = parsedConfig; |
||||||
|
|
||||||
|
const client = driverClientMapping[driver as DriverClient] || driver; |
||||||
|
|
||||||
|
if ( |
||||||
|
client === 'pg' && |
||||||
|
!connectionConfig?.ssl && |
||||||
|
!avoidSSL.includes(connectionConfig.host as string) |
||||||
|
) { |
||||||
|
connectionConfig.ssl = true; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
client: client, |
||||||
|
connection: { |
||||||
|
...connectionConfig, |
||||||
|
}, |
||||||
|
} as DbConfig; |
||||||
|
} |
||||||
|
|
||||||
|
export function jdbcToXcUrl(url: string): string { |
||||||
|
// drop the jdbc prefix
|
||||||
|
url.replace(/^jdbc:/, ''); |
||||||
|
|
||||||
|
const config = parseDbUrl(url); |
||||||
|
|
||||||
|
const parsedConfig: Connection = {}; |
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(config)) { |
||||||
|
const fnd = knownQueryParams.find( |
||||||
|
(param) => param.parameter === key || param.aliases.includes(key), |
||||||
|
); |
||||||
|
if (fnd) { |
||||||
|
parsedConfig[fnd.parameter] = value; |
||||||
|
} else { |
||||||
|
parsedConfig[key] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!parsedConfig?.port) { |
||||||
|
parsedConfig.port = |
||||||
|
defaultClientPortMapping[ |
||||||
|
driverClientMapping[parsedConfig.driver as DriverClient] || parsedConfig.driver |
||||||
|
]; |
||||||
|
} |
||||||
|
|
||||||
|
const { driver, host, port, database, user, password, ...extra } = |
||||||
|
parsedConfig; |
||||||
|
|
||||||
|
const extraParams: string[] = []; |
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(extra)) { |
||||||
|
extraParams.push(`${key}=${encodeURIComponent(String(value))}`); |
||||||
|
} |
||||||
|
|
||||||
|
const res = `${driverClientMapping[driver as DriverClient] || driver}://${host}${ |
||||||
|
port ? `:${port}` : '' |
||||||
|
}?${user ? `u=${encodeURIComponent(user)}&` : ''}${ |
||||||
|
password ? `p=${encodeURIComponent(password)}&` : '' |
||||||
|
}${database ? `d=${encodeURIComponent(database)}&` : ''}${extraParams.join( |
||||||
|
'&', |
||||||
|
)}`;
|
||||||
|
|
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
export function xcUrlToDbConfig( |
||||||
|
urlString: string, |
||||||
|
key = '', |
||||||
|
type?: string, |
||||||
|
): DbConfig { |
||||||
|
const url = new URL(urlString); |
||||||
|
|
||||||
|
let dbConfig: DbConfig; |
||||||
|
|
||||||
|
if (url.protocol.startsWith('sqlite3')) { |
||||||
|
dbConfig = { |
||||||
|
client: 'sqlite3', |
||||||
|
connection: { |
||||||
|
client: 'sqlite3', |
||||||
|
connection: { |
||||||
|
filename: |
||||||
|
url.searchParams.get('d') || url.searchParams.get('database'), |
||||||
|
}, |
||||||
|
database: url.searchParams.get('d') || url.searchParams.get('database'), |
||||||
|
}, |
||||||
|
} as any; |
||||||
|
} else { |
||||||
|
const parsedQuery = {}; |
||||||
|
for (const [key, value] of url.searchParams.entries()) { |
||||||
|
const fnd = knownQueryParams.find( |
||||||
|
(param) => param.parameter === key || param.aliases.includes(key), |
||||||
|
); |
||||||
|
if (fnd) { |
||||||
|
parsedQuery[fnd.parameter] = value; |
||||||
|
} else { |
||||||
|
parsedQuery[key] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dbConfig = { |
||||||
|
client: url.protocol.replace(':', '') as DriverClient, |
||||||
|
connection: { |
||||||
|
...parsedQuery, |
||||||
|
host: url.hostname, |
||||||
|
port: +url.port, |
||||||
|
}, |
||||||
|
acquireConnectionTimeout: 600000, |
||||||
|
}; |
||||||
|
|
||||||
|
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { |
||||||
|
dbConfig.connection.ssl = true; |
||||||
|
} |
||||||
|
|
||||||
|
if ( |
||||||
|
url.searchParams.get('keyFilePath') && |
||||||
|
url.searchParams.get('certFilePath') && |
||||||
|
url.searchParams.get('caFilePath') |
||||||
|
) { |
||||||
|
dbConfig.connection.ssl = { |
||||||
|
keyFilePath: url.searchParams.get('keyFilePath') as string, |
||||||
|
certFilePath: url.searchParams.get('certFilePath') as string, |
||||||
|
caFilePath: url.searchParams.get('caFilePath') as string, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* TODO check if this is needed |
||||||
|
if (config && !config.title) { |
||||||
|
config.title = |
||||||
|
url.searchParams.get('t') || |
||||||
|
url.searchParams.get('title') || |
||||||
|
this.generateRandomTitle(); |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
Object.assign(dbConfig, { |
||||||
|
meta: { |
||||||
|
tn: 'nc_evolutions', |
||||||
|
allSchemas: |
||||||
|
!!url.searchParams.get('allSchemas') || |
||||||
|
!(url.searchParams.get('d') || url.searchParams.get('database')), |
||||||
|
api: { |
||||||
|
prefix: url.searchParams.get('apiPrefix') || '', |
||||||
|
swagger: true, |
||||||
|
type: |
||||||
|
type || |
||||||
|
((url.searchParams.get('api') || url.searchParams.get('a')) as any) || |
||||||
|
'rest', |
||||||
|
}, |
||||||
|
dbAlias: url.searchParams.get('dbAlias') || `db${key}`, |
||||||
|
metaTables: 'db', |
||||||
|
migrations: { |
||||||
|
disabled: false, |
||||||
|
name: 'nc_evolutions', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
return dbConfig; |
||||||
|
} |
||||||
|
|
||||||
|
export async function metaUrlToDbConfig(urlString): Promise<DbConfig> { |
||||||
|
const url = new URL(urlString); |
||||||
|
|
||||||
|
let dbConfig: DbConfig; |
||||||
|
|
||||||
|
if (url.protocol.startsWith('sqlite3')) { |
||||||
|
const db = url.searchParams.get('d') || url.searchParams.get('database'); |
||||||
|
dbConfig = { |
||||||
|
client: DriverClient.SQLITE, |
||||||
|
connection: { |
||||||
|
filename: db as string, |
||||||
|
}, |
||||||
|
...(db === ':memory:' |
||||||
|
? { |
||||||
|
pool: { |
||||||
|
min: 1, |
||||||
|
max: 1, |
||||||
|
// disposeTimeout: 360000*1000,
|
||||||
|
idleTimeoutMillis: 360000 * 1000, |
||||||
|
}, |
||||||
|
} |
||||||
|
: {}), |
||||||
|
}; |
||||||
|
} else { |
||||||
|
const parsedQuery = {}; |
||||||
|
for (const [key, value] of url.searchParams.entries()) { |
||||||
|
const fnd = knownQueryParams.find( |
||||||
|
(param) => param.parameter === key || param.aliases.includes(key), |
||||||
|
); |
||||||
|
if (fnd) { |
||||||
|
parsedQuery[fnd.parameter] = value; |
||||||
|
} else { |
||||||
|
parsedQuery[key] = value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dbConfig = { |
||||||
|
client: url.protocol.replace(':', '') as DriverClient, |
||||||
|
connection: { |
||||||
|
...defaultConnectionConfig, |
||||||
|
...parsedQuery, |
||||||
|
host: url.hostname, |
||||||
|
port: +url.port, |
||||||
|
}, |
||||||
|
acquireConnectionTimeout: 600000, |
||||||
|
...defaultConnectionOptions, |
||||||
|
...(url.searchParams.has('search_path') |
||||||
|
? { |
||||||
|
searchPath: url.searchParams.get('search_path')?.split(','), |
||||||
|
} |
||||||
|
: {}), |
||||||
|
}; |
||||||
|
|
||||||
|
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { |
||||||
|
dbConfig.connection.ssl = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
url.searchParams.forEach((_value, key) => { |
||||||
|
let value: any = _value; |
||||||
|
if (value === 'true') { |
||||||
|
value = true; |
||||||
|
} else if (value === 'false') { |
||||||
|
value = false; |
||||||
|
} else if (/^\d+$/.test(value)) { |
||||||
|
value = +value; |
||||||
|
} |
||||||
|
// todo: implement config read from JSON file or JSON env val read
|
||||||
|
if ( |
||||||
|
!['password', 'p', 'database', 'd', 'user', 'u', 'search_path'].includes( |
||||||
|
key, |
||||||
|
) |
||||||
|
) { |
||||||
|
key.split('.').reduce((obj, k, i, arr) => { |
||||||
|
return (obj[k] = i === arr.length - 1 ? value : obj[k] || {}); |
||||||
|
}, dbConfig); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if ( |
||||||
|
dbConfig?.connection?.ssl && |
||||||
|
typeof dbConfig?.connection?.ssl === 'object' |
||||||
|
) { |
||||||
|
if (dbConfig.connection.ssl.caFilePath && !dbConfig.connection.ssl.ca) { |
||||||
|
dbConfig.connection.ssl.ca = ( |
||||||
|
await promisify(fs.readFile)(dbConfig.connection.ssl.caFilePath) |
||||||
|
).toString(); |
||||||
|
delete dbConfig.connection.ssl.caFilePath; |
||||||
|
} |
||||||
|
if (dbConfig.connection.ssl.keyFilePath && !dbConfig.connection.ssl.key) { |
||||||
|
dbConfig.connection.ssl.key = ( |
||||||
|
await promisify(fs.readFile)(dbConfig.connection.ssl.keyFilePath) |
||||||
|
).toString(); |
||||||
|
delete dbConfig.connection.ssl.keyFilePath; |
||||||
|
} |
||||||
|
if (dbConfig.connection.ssl.certFilePath && !dbConfig.connection.ssl.cert) { |
||||||
|
dbConfig.connection.ssl.cert = ( |
||||||
|
await promisify(fs.readFile)(dbConfig.connection.ssl.certFilePath) |
||||||
|
).toString(); |
||||||
|
delete dbConfig.connection.ssl.certFilePath; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return dbConfig; |
||||||
|
} |
@ -0,0 +1,4 @@ |
|||||||
|
export * from './helpers'; |
||||||
|
export * from './interfaces'; |
||||||
|
export * from './constants'; |
||||||
|
export * from './NcConfig'; |
@ -0,0 +1,39 @@ |
|||||||
|
import { DriverClient } from './constants'; |
||||||
|
|
||||||
|
interface Connection { |
||||||
|
driver?: DriverClient; |
||||||
|
host?: string; |
||||||
|
port?: number; |
||||||
|
database?: string; |
||||||
|
user?: string; |
||||||
|
password?: string; |
||||||
|
ssl?: |
||||||
|
| boolean |
||||||
|
| { |
||||||
|
ca?: string; |
||||||
|
cert?: string; |
||||||
|
key?: string; |
||||||
|
caFilePath?: string; |
||||||
|
certFilePath?: string; |
||||||
|
keyFilePath?: string; |
||||||
|
}; |
||||||
|
filename?: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface DbConfig { |
||||||
|
client: DriverClient; |
||||||
|
connection: Connection; |
||||||
|
acquireConnectionTimeout?: number; |
||||||
|
useNullAsDefault?: boolean; |
||||||
|
pool?: { |
||||||
|
min?: number; |
||||||
|
max?: number; |
||||||
|
idleTimeoutMillis?: number; |
||||||
|
}; |
||||||
|
migrations?: { |
||||||
|
directory?: string; |
||||||
|
tableName?: string; |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export { DriverClient, Connection, DbConfig }; |
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"rootDir": "src", |
||||||
|
"outDir": "dist", |
||||||
|
"strict": true, |
||||||
|
"target": "es6", |
||||||
|
"module": "commonjs", |
||||||
|
"sourceMap": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"moduleResolution": "node", |
||||||
|
"skipLibCheck": true, |
||||||
|
"noImplicitAny": false |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue