Skip to content

Commit df017ee

Browse files
Adding support for insecure connections with plain TCP socket
1 parent 9393500 commit df017ee

File tree

7 files changed

+89
-44
lines changed

7 files changed

+89
-44
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sqlitecloud/drivers",
3-
"version": "0.0.40",
3+
"version": "0.0.41",
44
"description": "SQLiteCloud drivers for Typescript/Javascript in edge, web and node clients",
55
"main": "./lib/index.js",
66
"types": "./lib/index.d.ts",

src/transport-tls.ts

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22
* transport-tls.ts - handles low level communication with sqlitecloud server via tls socket and binary protocol
33
*/
44

5-
import { SQLiteCloudConfig, SQLCloudRowsetMetadata, SQLiteCloudError, SQLiteCloudDataTypes, ErrorCallback, ResultsCallback } from './types'
5+
import { SQLiteCloudConfig, SQLiteCloudError, ErrorCallback, ResultsCallback, SQLCloudRowsetMetadata, SQLiteCloudDataTypes } from './types'
66
import { SQLiteCloudRowset } from './rowset'
7-
import { ConnectionTransport, getInitializationCommands } from './connection'
8-
import { anonimizeError, anonimizeCommand } from './connection'
7+
import { ConnectionTransport, getInitializationCommands, anonimizeError, anonimizeCommand } from './connection'
98

10-
import tls, { TLSSocket } from 'tls'
9+
import net from 'net'
10+
import tls from 'tls'
1111
const lz4 = require('lz4js')
1212

13-
/**
14-
* The server communicates with clients via commands defined
15-
* in the SQLiteCloud Server Protocol (SCSP), see more at:
16-
* https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
17-
*/
13+
// The server communicates with clients via commands defined in
14+
// SQLiteCloud Server Protocol (SCSP), see more at:
15+
// https://github.com/sqlitecloud/sdk/blob/master/PROTOCOL.md
16+
1817
const CMD_STRING = '+'
1918
const CMD_ZEROSTRING = '!'
2019
const CMD_ERROR = '-'
@@ -34,6 +33,7 @@ const CMD_ARRAY = '='
3433

3534
/**
3635
* Implementation of SQLiteCloudConnection that connects directly to the database via tls socket and raw, binary protocol.
36+
* Connects with plain socket with no encryption is the ?insecure=1 parameter is specified.
3737
* SQLiteCloud low-level connection, will do messaging, handle socket, authentication, etc.
3838
* A connection socket is established when the connection is created and closed when the connection is closed.
3939
* All operations are serialized by waiting for any pending operations to complete. Once a connection is closed,
@@ -43,7 +43,7 @@ export class TlsSocketTransport implements ConnectionTransport {
4343
/** Configuration passed to connect */
4444
private config?: SQLiteCloudConfig
4545
/** Currently opened tls socket used to communicated with SQLiteCloud server */
46-
private socket?: tls.TLSSocket | null
46+
private socket?: tls.TLSSocket | net.Socket | null
4747

4848
/** True if connection is open */
4949
get connected(): boolean {
@@ -69,36 +69,51 @@ export class TlsSocketTransport implements ConnectionTransport {
6969

7070
this.config = config
7171

72-
// connect to tls socket, initialize connection, setup event handlers
73-
this.socket = tls.connect(this.config.port as number, this.config.host, this.config.tlsOptions, () => {
74-
if (!this.socket?.authorized) {
75-
const anonimizedError = anonimizeError((this.socket as TLSSocket).authorizationError)
76-
console.error('Connection was not authorized', anonimizedError)
77-
this.close()
78-
finish(new SQLiteCloudError('Connection was not authorized', { cause: anonimizedError }))
79-
} else {
80-
// the connection was closed before it was even opened,
81-
// eg. client closed the connection before the server accepted it
82-
if (this.socket === null) {
83-
finish(new SQLiteCloudError('Connection was closed before it was done opening'))
84-
return
85-
}
86-
87-
// send initialization commands
88-
console.assert(this.socket, 'Connection already closed')
89-
const commands = getInitializationCommands(config)
90-
this.processCommands(commands, error => {
91-
if (error && this.socket) {
92-
this.close()
93-
}
94-
if (callback) {
95-
callback?.call(this, error)
96-
callback = undefined
97-
}
98-
finish(error)
99-
})
72+
if (config.insecure) {
73+
// connect to plain socket, without encryption, only if insecure parameter specified
74+
// this option is mainly for testing purposes and is not available on production nodes
75+
// which would need to connect using tls and proper certificates as per code below
76+
const connectionOptions: net.SocketConnectOpts = {
77+
host: config.host,
78+
port: config.port as number
10079
}
101-
})
80+
this.socket = net.connect(connectionOptions, () => {
81+
console.warn(`TlsTransport.connect - connected to ${config.host}:${config.port} using insecure protocol`)
82+
callback?.call(this, null)
83+
})
84+
} else {
85+
// connect to tls socket, initialize connection, setup event handlers
86+
this.socket = tls.connect(this.config.port as number, this.config.host, this.config.tlsOptions, () => {
87+
const tlsSocket = this.socket as tls.TLSSocket
88+
if (!tlsSocket?.authorized) {
89+
const anonimizedError = anonimizeError(tlsSocket.authorizationError)
90+
console.error('Connection was not authorized', anonimizedError)
91+
this.close()
92+
finish(new SQLiteCloudError('Connection was not authorized', { cause: anonimizedError }))
93+
} else {
94+
// the connection was closed before it was even opened,
95+
// eg. client closed the connection before the server accepted it
96+
if (this.socket === null) {
97+
finish(new SQLiteCloudError('Connection was closed before it was done opening'))
98+
return
99+
}
100+
101+
// send initialization commands
102+
console.assert(this.socket, 'Connection already closed')
103+
const commands = getInitializationCommands(config)
104+
this.processCommands(commands, error => {
105+
if (error && this.socket) {
106+
this.close()
107+
}
108+
if (callback) {
109+
callback?.call(this, error)
110+
callback = undefined
111+
}
112+
finish(error)
113+
})
114+
}
115+
})
116+
}
102117

103118
this.socket.on('close', () => {
104119
this.socket = null
@@ -408,9 +423,9 @@ function parseRowset(buffer: Buffer, spaceIndex: number): SQLiteCloudRowset {
408423
* Parse a chunk of a chunked rowset command, eg:
409424
* *LEN 0:VERS NROWS NCOLS DATA
410425
*/
411-
function parseRowsetChunks(buffers: Buffer[]) {
426+
export function parseRowsetChunks(buffers: Buffer[]) {
412427
let metadata: SQLCloudRowsetMetadata = { version: 1, numberOfColumns: 0, numberOfRows: 0, columns: [] }
413-
const data = []
428+
const data: any[] = []
414429

415430
for (let i = 0; i < buffers.length; i++) {
416431
let buffer: Buffer = buffers[i]
@@ -456,7 +471,7 @@ function popIntegers(buffer: Buffer, numberOfIntegers = 1): { data: number[]; fw
456471
}
457472

458473
/** Parse command, extract its data, return the data and the buffer moved to the first byte after the command */
459-
function popData(buffer: Buffer): { data: SQLiteCloudDataTypes | SQLiteCloudRowset; fwdBuffer: Buffer } {
474+
export function popData(buffer: Buffer): { data: SQLiteCloudDataTypes | SQLiteCloudRowset; fwdBuffer: Buffer } {
460475
function popResults(data: any) {
461476
const fwdBuffer = buffer.subarray(commandEnd)
462477
return { data, fwdBuffer }
@@ -511,7 +526,7 @@ function popData(buffer: Buffer): { data: SQLiteCloudDataTypes | SQLiteCloudRows
511526
}
512527

513528
/** Format a command to be sent via SCSP protocol */
514-
function formatCommand(command: string): string {
529+
export function formatCommand(command: string): string {
515530
const commandLength = Buffer.byteLength(command, 'utf-8')
516531
return `+${commandLength} ${command}`
517532
}

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export interface SQLiteCloudConfig {
2525
host?: string
2626
/** Port number for tls socket */
2727
port?: number
28+
/** Connect using plain TCP port, without TLS encryption, NOT RECOMMENDED, TEST ONLY */
29+
insecure?: boolean
30+
2831
/** Optional query timeout passed directly to TLS socket */
2932
timeout?: number
3033
/** Name of database to open */

src/utilities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export function validateConfiguration(config: SQLiteCloudConfig): SQLiteCloudCon
139139
config.compression = parseBoolean(config.compression)
140140
config.createDatabase = parseBoolean(config.createDatabase)
141141
config.nonlinearizable = parseBoolean(config.nonlinearizable)
142+
config.insecure = parseBoolean(config.insecure)
142143

143144
if (!config.username || !config.password || !config.host) {
144145
console.error('SQLiteCloudConnection.validateConfiguration - missing arguments', config)

test/connection-tls.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SQLiteCloudError } from '../src/index'
66
import { SQLiteCloudConnection, anonimizeCommand } from '../src/connection'
77
import {
88
CHINOOK_DATABASE_URL,
9+
INSECURE_DATABASE_URL,
910
LONG_TIMEOUT,
1011
getTestingConfig,
1112
getChinookConfig,
@@ -85,6 +86,26 @@ describe('connection-tls', () => {
8586
expect(conn).toBeDefined()
8687
})
8788

89+
it('should connect with insecure connection string', done => {
90+
if (INSECURE_DATABASE_URL) {
91+
expect(INSECURE_DATABASE_URL).toBeDefined()
92+
const conn = new SQLiteCloudConnection(INSECURE_DATABASE_URL, error => {
93+
expect(error).toBeNull()
94+
expect(conn.connected).toBe(true)
95+
96+
chinook.sendCommands('TEST STRING', (error, results) => {
97+
conn.close()
98+
expect(conn.connected).toBe(false)
99+
done()
100+
})
101+
})
102+
expect(conn).toBeDefined()
103+
} else {
104+
console.warn(`INSECURE_DATABASE_URL is not defined, ?insecure= connection will not be tested`)
105+
done()
106+
}
107+
})
108+
88109
it('should throw when connection string lacks credentials', done => {
89110
// use valid connection string but without credentials
90111
const testingConfig = getTestingConfig()

test/shared.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ expect(CHINOOK_DATABASE_URL).toBeDefined()
3333
expect(TESTING_DATABASE_URL).toBeDefined()
3434
expect(GATEWAY_URL).toBeDefined()
3535

36+
/** Url to insecure database used for testing */
37+
export const INSECURE_DATABASE_URL = process.env.INSECURE_DATABASE_URL as string
38+
3639
export const SELF_SIGNED_CERTIFICATE = `-----BEGIN CERTIFICATE-----
3740
MIID6zCCAtOgAwIBAgIUI0lTm5CfVf3mVP8606CkophcyB4wDQYJKoZIhvcNAQEL
3841
BQAwgYQxCzAJBgNVBAYTAklUMQswCQYDVQQIDAJNTjEQMA4GA1UEBwwHVmlhZGFu

webpack.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ const productionConfig = {
2121
minimize: true
2222
},
2323

24+
// add mock 'net' module
2425
// add mock 'tls' module
2526
resolve: {
2627
fallback: {
28+
net: false, // tell Webpack to ignore "net"
2729
tls: false // tell Webpack to ignore "tls"
2830
}
2931
}

0 commit comments

Comments
 (0)