diff --git a/.gitignore b/.gitignore index b07ddf4..6f1a91b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules build +/.idea \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index a336e48..740b35d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,5 +1,6 @@ import { Client, Connection, type ClientConfig } from 'pg'; import { Socket } from './shims/net'; +import { warnIfBrowser } from './utils'; export declare interface NeonClient { connection: Connection & { @@ -95,6 +96,10 @@ export class NeonClient extends Client { const connectEvent = this.ssl ? 'sslconnect' : 'connect'; con.on(connectEvent, () => { + if (!this.neonConfig.disableWarningInBrowsers) { + warnIfBrowser(); + } + this._handleAuthCleartextPassword(); this._handleReadyForQuery(); }); diff --git a/src/httpQuery.ts b/src/httpQuery.ts index e2f00a6..335e951 100644 --- a/src/httpQuery.ts +++ b/src/httpQuery.ts @@ -27,6 +27,9 @@ import type { ParameterizedQuery, } from './httpTypes'; import { SqlTemplate, UnsafeRawSql } from './sqlTemplate'; +import { warnIfBrowser } from './utils'; + +import { Socket as neonConfig } from './shims/net'; // @ts-ignore -- this isn't officially exported by pg import TypeOverrides from 'pg/lib/type-overrides'; @@ -192,6 +195,7 @@ export function neon< readOnly: neonOptReadOnly, deferrable: neonOptDeferrable, authToken, + disableWarningInBrowsers, }: HTTPTransactionOptions = {}, ): NeonQueryFunction { // check the connection string @@ -365,6 +369,10 @@ export function neon< headers['Neon-Batch-Deferrable'] = String(resolvedDeferrable); } + if (!(disableWarningInBrowsers || neonConfig.disableWarningInBrowsers)) { + warnIfBrowser(); + } + // --- run query --- let response; diff --git a/src/httpTypes.ts b/src/httpTypes.ts index 205229c..b186f29 100644 --- a/src/httpTypes.ts +++ b/src/httpTypes.ts @@ -64,6 +64,14 @@ export interface HTTPQueryOptions< * Custom type parsers. See https://github.com/brianc/node-pg-types. */ types?: typeof PgTypes; + + /** + * When `disableWarningInBrowsers` is set to `true`, it disables the warning about + * running this driver in the browser. + * + * Default: `false` + */ + disableWarningInBrowsers?: boolean; } export interface HTTPTransactionOptions< diff --git a/src/index.ts b/src/index.ts index 6f9722c..8bb0aec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,9 +19,20 @@ circumstances are right. import type { ClientBase as PgClientBase } from 'pg'; import { type SocketDefaults } from './shims/net'; +// Ensure we are very explicit while using these apis. This ensures more type safety +// specially since this library is made to run both in the browser and node.js environments. +declare global { + const window: Window | undefined; + const document: Document | undefined; + + interface Window {} + interface Document {} +} + export * from './httpQuery'; export * from './sqlTemplate'; export type * from './httpTypes'; +export * from './utils'; export { NeonPool as Pool, type NeonPoolClient as PoolClient } from './pool'; export { NeonClient as Client } from './client'; diff --git a/src/shims/net/index.ts b/src/shims/net/index.ts index e61f417..7a454eb 100644 --- a/src/shims/net/index.ts +++ b/src/shims/net/index.ts @@ -102,6 +102,7 @@ export interface SocketDefaults { rootCerts: string; pipelineTLS: boolean; disableSNI: boolean; + disableWarningInBrowsers: boolean; } type GlobalOnlyDefaults = | 'poolQueryViaFetch' @@ -142,6 +143,7 @@ export class Socket extends EventEmitter { rootCerts: '', pipelineTLS: false, disableSNI: false, + disableWarningInBrowsers: false, }; static opts: Partial = {}; @@ -345,6 +347,34 @@ export class Socket extends EventEmitter { this.opts.disableSNI = newValue; } + /** + * When `disableWarningInBrowsers` is set to `true`, it disables the warning about + * running this driver in the browser. + * + * Default: `false`. + */ + static get disableWarningInBrowsers() { + return ( + Socket.opts.disableWarningInBrowsers ?? + Socket.defaults.disableWarningInBrowsers + ); + } + static set disableWarningInBrowsers( + newValue: SocketDefaults['disableWarningInBrowsers'], + ) { + Socket.opts.disableWarningInBrowsers = newValue; + } + get disableWarningInBrowsers() { + return ( + this.opts.disableWarningInBrowsers ?? Socket.disableWarningInBrowsers + ); + } + set disableWarningInBrowsers( + newValue: SocketDefaults['disableWarningInBrowsers'], + ) { + this.opts.disableWarningInBrowsers = newValue; + } + /** * Pipelines the startup message, cleartext password message and first query * when set to `"password"`. This works only for cleartext password auth. diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..3ea27f2 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,30 @@ +/** + * Detects if the code is running in a browser environment and displays a warning + * about the security implications of running SQL directly from the browser. + */ +export function warnIfBrowser(): void { + const isBrowser = + typeof window !== 'undefined' && typeof document !== 'undefined'; + if ( + isBrowser && + typeof console !== 'undefined' && + typeof console.warn === 'function' + ) { + console.warn(` + ************************************************************ + * * + * WARNING: Running SQL directly from the browser can have * + * security implications. Even if your database is * + * protected by Row-Level Security (RLS), use it at your * + * own risk. This approach is great for fast prototyping, * + * but ensure proper safeguards are in place to prevent * + * misuse or execution of expensive SQL queries by your * + * end users. * + * * + * If you've assessed the risks, suppress this message * + * using the disableWarningInBrowsers configuration * + * parameter. * + * * + ************************************************************`); + } +}