├── .gitignore
├── .jshintrc
├── .travis.yml
├── README.md
├── TODO.md
├── bin
└── rethinkdb-proxy.js
├── index.js
├── lib-test
├── changefeeds.js
├── components
│ ├── buffer-parser.js
│ └── rethinkdb-proxy.js
├── connection.js
├── database-and-table-access.js
├── edge-cases.js
├── index.js
├── parallel.js
├── parser.js
├── queries.js
├── read-only-queries.js
└── utils.js
├── lib
├── buffer-parser.js
├── index.js
├── logger.js
├── options-parser.js
└── query-parser.js
├── package.json
├── src
├── buffer-parser.js
├── index.js
├── logger.js
├── options-parser.js
└── query-parser.js
└── test
├── changefeeds.js
├── components
├── buffer-parser.js
└── rethinkdb-proxy.js
├── connection.js
├── database-and-table-access.js
├── edge-cases.js
├── index.js
├── parallel.js
├── parser.js
├── queries.js
├── read-only-queries.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | !**/*
2 |
3 | node_modules
4 | driver
5 | fabfile.py
6 | **/*.pyc
7 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "curly": false,
4 | "eqeqeq": true,
5 | "forin": true,
6 | "freeze": true,
7 | "immed": true,
8 | "indent": 2,
9 | "latedef": true,
10 | "newcap": true,
11 | "noarg": true,
12 | "noempty": true,
13 | "nonbsp": true,
14 | "nonew": false,
15 | "quotmark": "single",
16 | "undef": true,
17 |
18 | "maxcomplexity": 10,
19 | "maxdepth": 4,
20 | "maxlen": 120,
21 | "maxparams": 5,
22 | "maxstatements": 25,
23 |
24 | "eqnull": true,
25 | "esnext": true,
26 |
27 | "node": true,
28 | "globals": {
29 | "describe": false,
30 | "xdescribe": false,
31 | "ddescribe": false,
32 | "it": false,
33 | "xit": false,
34 | "beforeEach": false,
35 | "afterEach": false,
36 | "before": false,
37 | "after": false
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "0.11"
5 | - "0.12"
6 | - "iojs"
7 |
8 | matrix:
9 | fast_finish: true
10 |
11 | before_install:
12 | - source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list
13 | - wget -qO- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -
14 | - sudo apt-get update -q
15 | - sudo apt-get -y --force-yes install rethinkdb
16 |
17 | notifications:
18 | email: false
19 |
20 | before_script:
21 | - rethinkdb --daemon
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RethinkDB Proxy
2 |
3 | [](https://travis-ci.org/thejsj/rethinkdb-proxy)
4 | [](http://badge.fury.io/js/rethinkdb-proxy)
5 |
6 | *Reverse proxy for RethinkDB*
7 |
8 | Make your RethinkDB publicly accessible through limiting what kind of queries
9 | can be executed on your RethinkDB database.
10 |
11 | Currently, RethinkDB has no access control (although they're currently
12 | [working on it](https://github.com/rethinkdb/rethinkdb/issues/4519)). Anyone
13 | with access to a running instance has access to everything, including system tables.
14 | This is a simple solution to that problem that allows for limited access to
15 | RethinkDB.
16 |
17 | - [Introduction by Example](#introduction-by-example-)
18 | - [Try it!](#try-it-)
19 | - [Installation](#installation)
20 | - [Running `rethinkdb-proxy`](#running-rethinkdb-proxy-)
21 | - [Options](#options-)
22 | - [The Future](#the-future-)
23 | - [License](#license-)
24 |
25 | ## Introduction by Example
26 |
27 | First, start the proxy.
28 |
29 | ```bash
30 | $ rethinkdb-proxy --port 8125
31 | ```
32 | Using the proxy, getting all users in the `users` table is allowed.
33 |
34 | ```javascript
35 | r.connect({ port: 8125 }).then((conn) => {
36 | r.table('users').coerceTo('array').run(conn)
37 | .then((results) => {
38 | // We have some results!
39 | console.log(results); // [{ name: 'jorge' }, ... ]
40 | });
41 | });
42 | ```
43 |
44 | But deleting the users **is not**:
45 |
46 | ```javascript
47 | import rethinkDBProxy from 'rethinkdb-proxy';
48 | rethinkDBProxy({ port: 8125 });
49 |
50 | r.connect({ port: 8125 }).then((conn) => {
51 | r.table('users').delete('array').run(conn)
52 | .catch((err) => {
53 | // We get an error!
54 | console.log(err); // RqlClientError: Cannot execute query. "DELETE" query not allowed
55 | });
56 | });
57 | ```
58 |
59 | ## Try it!
60 |
61 | You can try out rethinkdb-proxy by connecting to a publicly available proxy at `rethinkdb-proxy.thejsj.com:8125`.
62 | This database (named `test`) has two tables: `countries` and `cities`. You can
63 | run queries against it to see how `rethindkb-proxy` works.
64 |
65 | **JavasScript:**
66 |
67 | ```javascript
68 | import r from 'rethinkdb';
69 | r.connect({ host: 'rethinkdb-proxy.thejsj.com', port: 8125 })
70 | .then(function (conn) {
71 | r.table('countries').coerceTo('array').run(conn);
72 | });
73 | ```
74 |
75 | **Python:**
76 |
77 | ```python
78 | import rethinkdb as r
79 | conn = r.connect(host="rethinkdb-proxy.thejsj.com", port=8125)
80 | r.table('countries').coerce_to('array').run(conn)
81 | ```
82 |
83 | ## Installation
84 |
85 | Install rethinkdb-proxy through [npm](https://www.npmjs.com/package/rethinkdb-proxy).
86 |
87 | ```
88 | npm install -g rehtinkdb-proxy
89 | ```
90 |
91 | ## Running rethinkdb-proxy
92 |
93 | #### CLI
94 |
95 | rethinkdb-proxy comes with a CLI out-of-the box:
96 |
97 | ```javascript
98 | rethinkdb-proxy --port 8125
99 | ```
100 |
101 | #### Module
102 |
103 | You can also import rethinkdb-proxy into Node.js:
104 |
105 | ```javascript
106 | import rethinkDBProxy from 'rethinkdb-proxy';
107 | rethinkDBProxy({ port: 8125, allowInsert: true });
108 | ```
109 |
110 | ## Options
111 |
112 | - [`port`](#port)
113 | - [`rdbHost`](#rdbHost)
114 | - [`rdbPort`](#rdbPort)
115 | - [`dbs`](#dbs)
116 | - [`allowSysDbAccess`](#allowSysDbAccess)
117 | - [`tables`](#tables)
118 | - [`allowWrites`](#allowWrites)
119 | - [`allowInsert`](#allowInsert)
120 | - [`allowUpdate`](#allowUpdate)
121 | - [`allowDelete`](#allowDelete)
122 | - [`allowReplace`](#allowReplace)
123 | - [`allowDbCreate`](#allowDbCreate)
124 | - [`allowDbDrop`](#allowDbDrop)
125 | - [`allowTableCreate`](#allowTableCreate)
126 | - [`allowTableDrop`](#allowTableDrop)
127 | - [`allowIndexes`](#allowIndexes)
128 | - [`allowIndexCreate`](#allowIndexCreate)
129 | - [`allowIndexDrop`](#allowIndexDrop)
130 | - [`allowIndexRename`](#allowIndexRename)
131 | - [`allowReconfigure`](#allowReconfigure)
132 | - [`allowRebalance`](#allowRebalance)
133 | - [`allowHttp`](#allowHttp)
134 | - [`allowJavascript`](#allowJavascript)
135 |
136 | ### Port
137 |
138 | | Module Parameter | CLI Parameter | Default | API Reference |
139 | |--------------------|-------------------------|-------------|---------------|
140 | | `port` | `--port` | `8125` | |
141 |
142 | Port in which to listen for driver connections. You should point your driver to this port.
143 |
144 | ### RethinkDB Host
145 |
146 | | Module Parameter | CLI Parameter | Default | API Reference |
147 | |--------------------|-------------------------|-------------|-------------------------------------------------------------|
148 | | `rdbHost` | `--rdb-host` | `localhost` | [connect](http://www.rethinkdb.com/api/javascript/connect/) |
149 |
150 | Host in which RethinkDB is running.
151 |
152 | ### RethinkDB Port
153 |
154 | | Module Parameter | CLI Parameter | Default | API Reference |
155 | |--------------------|-------------------------|-------------|--------------------------------------------------------------|
156 | | `rdbPort` | `--rdb-host` | `localhost` | [connect](http://www.rethinkdb.com/api/javascript/connect/) |
157 |
158 | Host in which RethinkDB is running.
159 |
160 | ### Databases
161 |
162 | | Module Parameter | CLI Parameter | Default | API Reference |
163 | |--------------------|-------------------------|-------------|---------------|
164 | | `dbs` | `--dbs` | `[ ]` | |
165 |
166 | Database to allow access to. By default, all database are allowed except `rethinkdb`.
167 |
168 | ### Allow System Database Access
169 |
170 | | Module Parameter | CLI Parameter | Default | API Reference |
171 | |--------------------|-------------------------|-------------|---------------|
172 | | `allowSysDbAccess` | `--allow-sys-db-access` | `false` | |
173 |
174 | Allow access to the `rethinkdb` database. This is not allowed by default because
175 | access to this database allows the user to delete all other data, cancel jobs,
176 | mess with the cluster, etc.
177 |
178 | ### Tables
179 |
180 | | Module Parameter | CLI Parameter | Default | API Reference |
181 | |--------------------|-------------------------|-------------|---------------|
182 | | `tables` | `--tables` | `[ ]` | |
183 |
184 | Tables to allow access to. Tables must include their database `db.table`.
185 |
186 | ### Allow Writes
187 |
188 | | Module Parameter | CLI Parameter | Default | API Reference |
189 | |--------------------|-------------------------|-------------|---------------|
190 | | `allowWrites` | `--allow-writes` | `false` | |
191 |
192 | Allow all operations that write to the database (`insert`, `update`, `delete`).
193 |
194 | ### Allow `insert`
195 |
196 | | Module Parameter | CLI Parameter | Default | API Reference |
197 | |--------------------|-------------------------|-------------|-----------------------------------------------------------|
198 | | `allowInsert` | `--allow-insert` | `false` | [insert](http://www.rethinkdb.com/api/javascript/insert/) |
199 |
200 | Allow `insert` queries.
201 |
202 | ### Allow `update`
203 |
204 | | Module Parameter | CLI Parameter | Default | API Reference |
205 | |--------------------|-------------------------|-------------|-----------------------------------------------------------|
206 | | `allowUpdate` | `--allow-update` | `false` | [update](http://www.rethinkdb.com/api/javascript/update/) |
207 |
208 | Allow `update` queries.
209 |
210 | ### Allow `delete`
211 |
212 | | Module Parameter | CLI Parameter | Default | API Reference |
213 | |--------------------|-------------------------|-------------|-----------------------------------------------------------|
214 | | `allowDelete` | `--allow-delete` | `false` | [delete](http://www.rethinkdb.com/api/javascript/delete/) |
215 |
216 | Allow `delete` queries.
217 |
218 | ### Allow `replace`
219 |
220 | | Module Parameter | CLI Parameter | Default | API Reference |
221 | |--------------------|-------------------------|-------------|------------------------------------------------------------|
222 | | `allowReplace` | `--allow-replace` | `false` | [replace](http://www.rethinkdb.com/api/javascript/delete/) |
223 |
224 | Allow `replace` queries.
225 |
226 | ### Allow `dbCreate`
227 |
228 | | Module Parameter | CLI Parameter | Default | API Reference |
229 | |--------------------|-------------------------|-------------|----------------------------------------------------------------|
230 | | `allowDbCreate` | `--allow-db-create` | `false` | [dbCreate](http://www.rethinkdb.com/api/javascript/db_create/) |
231 |
232 | Allow `dbCreate` queries
233 |
234 | ### Allow `dbDrop`
235 |
236 | | Module Parameter | CLI Parameter | Default | API Reference |
237 | |--------------------|-------------------------|-------------|------------------------------------------------------------|
238 | | `allowDbDrop` | `--allow-db-drop` | `false` | [dbDrop](http://www.rethinkdb.com/api/javascript/db_drop/) |
239 |
240 | Allow `dbDrop` queries
241 |
242 | ### Allow `tableCreate`
243 |
244 | | Module Parameter | CLI Parameter | Default | API Reference |
245 | |--------------------|-------------------------|-------------|----------------------------------------------------------------------|
246 | | `allowTableCreate` | `--allow-table-create` | `false` | [tableCreate](http://www.rethinkdb.com/api/javascript/table_create/) |
247 |
248 | Allow `tableCreate` queries.
249 |
250 | ### Allow `tableDrop`
251 |
252 | | Module Parameter | CLI Parameter | Default | API Reference |
253 | |--------------------|-------------------------|-------------|-------------------------------------------------------------------|
254 | | `allowTableDrop` | `--allow-table-drop` | `false` | [tableDrop](http://www.rethinkdb.com/api/javascript/table_drop/) |
255 |
256 | Allow `tableDrop` queries.
257 |
258 | ### Allow Indexes
259 |
260 | | Module Parameter | CLI Parameter | Default | API Reference |
261 | |--------------------|-------------------------|-------------|---------------|
262 | | `allowIndexes` | `--allow-indexes` | `false` | |
263 |
264 | Allow all operations on indexes (`indexCreate`, `indexDrop`, `indexRename`).
265 |
266 | ### Allow `indexCreate`
267 |
268 | | Module Parameter | CLI Parameter | Default | API Reference |
269 | |--------------------|-------------------------|-------------|----------------------------------------------------------------------|
270 | | `allowIndexCreate` | `--allow-index-create` | `false` | [indexCreate](http://www.rethinkdb.com/api/javascript/index_create/) |
271 |
272 | Allow `indexCreate` queries.
273 |
274 | ### Allow `indexDrop`
275 |
276 | | Module Parameter | CLI Parameter | Default | API Reference |
277 | |--------------------|-------------------------|-------------|------------------------------------------------------------------|
278 | | `allowIndexDrop` | `--allow-index-drop` | `false` | [indexDrop](http://www.rethinkdb.com/api/javascript/index_drop/) |
279 |
280 | Allow `indexDrop` queries.
281 |
282 | ### Allow `indexRename`
283 |
284 | | Module Parameter | CLI Parameter | Default | API Reference |
285 | |--------------------|-------------------------|-------------|----------------------------------------------------------------------|
286 | | `allowIndexRename` | `--allow-index-rename` | `false` | [indexRename](http://www.rethinkdb.com/api/javascript/index_rename/) |
287 |
288 | Allow `indexRename` queries.
289 |
290 | ### Allow `reconfigure`
291 |
292 | | Module Parameter | CLI Parameter | Default | API Reference |
293 | |--------------------|-------------------------|-------------|---------------------------------------------------------------------|
294 | | `allowReconfigure` | `--allow-reconfigure` | `false` | [reconfigure](http://www.rethinkdb.com/api/javascript/reconfigure/) |
295 |
296 | Allow `reconfigure` queries.
297 |
298 | ### Allow `rebalance`
299 |
300 | | Module Parameter | CLI Parameter | Default | API Reference |
301 | |--------------------|-------------------------|-------------|----------------------------------------------------------------------|
302 | | `allowRebalance` | `--allow-rebalance` | `false` | [rebalance](http://www.rethinkdb.com/api/javascript/rebalance/) |
303 |
304 | Allow `rebalance` queries.
305 |
306 | ### Allow `http`
307 |
308 | | Module Parameter | CLI Parameter | Default | API Reference |
309 | |--------------------|-------------------------|-------------|-------------------------------------------------------|
310 | | `allowHttp` | `--allow-http` | `false` | [http](http://www.rethinkdb.com/api/javascript/http/) |
311 |
312 | Allow queries with the `http` term.
313 |
314 | ### Allow `js`
315 |
316 | | Module Parameter | CLI Parameter | Default | API Reference |
317 | |--------------------|-------------------------|-------------|----------------------------------------------------|
318 | | `allowJavascript` | `--allow-javascript` | `false` | [js](http://www.rethinkdb.com/api/javascript/js/) |
319 |
320 | Allow queries with the `js` term.
321 |
322 | ### The Future
323 |
324 | As of right now, there are many features that could be added to rethinkdb-proxy.
325 | If you have any suggestions, please [submit an issue](https://github.com/thejsj/rethinkdb-proxy/issues).
326 | If enough people use this, I'd be happy to implement them. Features for the
327 | future might include:
328 |
329 | - Access from the front-end, Firebase style (through http and/or websockets)
330 | - Authentication/User accounts (perhaps integration with Github/OAuth)
331 | - More robust access control (permissions per database, per table)
332 | - Options stored in the database
333 |
334 | ### License
335 |
336 | Copyright (c) 2015, [Jorge Silva](mailto:jorge.silva@thejsj.com).
337 |
338 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
339 |
340 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
341 |
342 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
343 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | - [x] README
4 | - [x] Wrap allow around code
5 | - [x] Add api links to docs
6 | - [x] Errors
7 | - [x] Add error handling for non-rethinkdb driver connections
8 | - [x] Keep server running on error
9 | - [x] It should handle queries that throw errors
10 | - [x] Fix `ECONNRESET` bugs
11 |
12 | ## Future
13 |
14 | - [ ] Use from browser
15 | - [ ] Replace `net` library with websockets
16 | - [ ] Authentication
17 | - [ ] Convert queries back to strings. Re-build AST.
18 | - [ ] Save options in RethinkDB instead of passing them as flags
19 | - [ ] Add logging
20 |
21 | ### Done
22 |
23 | - [x] Test cursors
24 | - [x] Test multiple concurrent queries from the same connection
25 | - [x] Test changefeeds
26 | - [x] API
27 | - [x] Better name for allowSysDbAccess
28 | - [x] Allow only a single database/table
29 | - [x] database `--db test,hello`
30 | - [x] Prevent connection if database is passed that is not allowed (Throw error)
31 | - [x] tables `--table people, trains`, `--table test.people, hello.trains`
32 | - [x] Prevent `r.args`
33 | - [x] Prevent getting the dbName, tableName dynamically
34 | - [x] Refactor
35 | - [x] Refactor code so that it uses classes
36 | - [x] Break up codes into more modular structure
37 | - [x] Opt-in Options:
38 | - [x] Allow Insert `--allow-insert`
39 | - [x] Allow Delete`--allow-delete`
40 | - [x] Allow Update`--allow-update`
41 | - [x] Allow Database Create `--allow-db-create`
42 | - [x] Database Drop `--allow-db-drop`
43 | - [x] Table Creation `--allow-table-create`
44 | - [x] Table Drop `--allow-table-drop`
45 | - [x] HTTP `--allow-http`
46 | - [x] JavaScript `--allow-javascript`
47 | - [x] Reconfigure `--allow-reconfigure`
48 | - [x] Rebalance `--allow-rebalance`
49 | - [x] Add token/connection parser
50 | - [x] Prevent `insert`, `update`, `replace`, `delete`
51 | - [x] Fix corner cases
52 | - [x] `update` with `null`: does nothing.
53 | - [x] `update` with `r.literal()`: throws Error.
54 | - [x] `insert` with `conflict: replace`. `allowReplace` must be allowed.
55 | - [x] `insert` with `conflict: replace` with `null` will deleted the document. `allowDelete` must be allowed. (Throw error)
56 | - [x] `insert` with `conflict: update`. `allowUpdate` must be allowed.
57 | - [x] `replace` with `null` deletes the document.
58 | - [x] `replace` with `r.literal`: Throws error.
59 |
60 |
--------------------------------------------------------------------------------
/bin/rethinkdb-proxy.js:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | var cli = require('cli').enable('status');
3 | var taser = require('taser');
4 | var RethinkDBProxy = require('../lib/');
5 |
6 | cli.parse({
7 | 'port': [false, 'Port in which to listen for driver connections', 'number', null],
8 | 'rdbHost': [false, 'RethinkDB host to connect to', 'string', null],
9 | 'rdbPort': [false, 'RethinkDB port to connect to', 'number', null],
10 | 'log-level': [false, 'Log level (Default for `in`, `out`, and `sys`)', 'string', 'fatal'],
11 | 'log-level-in': [false, 'Log level for incoming messages', 'string', 'fatal'],
12 | 'log-level-out': [false, 'Log level for outgoing messages', 'string', 'fatal'],
13 | 'log-level-sys': [false, 'Log level for system logs', 'string', 'fatal'],
14 | 'dbs': [false, 'Databases allowed', 'string', null],
15 | 'allow-sys-db-acces': [false, 'Allow access to the `rethinkdb` system database', 'string', null],
16 | 'tables': [false,
17 | 'Tables allowed. Must include dot (`db.table`) if multiple database allowed.', 'string', null],
18 | 'allow-writes': [false, 'Allow all operations that write to the database (`insert`, `update`, `delete`)'],
19 | 'allow-insert': [false, 'Allow `insert` queries'],
20 | 'allow-update': [false, 'Allow `update` queries'],
21 | 'allow-delete': [false, 'Allow `delete` queries'],
22 | 'allow-replace': [false, 'Allow `replace` queries'],
23 | 'allow-db-create': [false, 'Allow `dbCreate` queries'],
24 | 'allow-db-drop': [false, 'Allow `dbDrop` queries'],
25 | 'allow-table-create': [false, 'Allow `tableCreate` queries'],
26 | 'allow-table-drop': [false, 'Allow `tableDrop` queries'],
27 | 'allow-indexes': [false, 'Allow all operations on indexes (`indexCreate`, `indexDrop`, `indexRename`)'],
28 | 'allow-index-create': [false, 'Allow `indexCreate` queries'],
29 | 'allow-index-drop': [false, 'Allow `indexDrop` queries'],
30 | 'allow-index-rename': [false, 'Allow `indexRename` queries'],
31 | 'allow-reconfigure': [false, 'Allow `reconfigure` queries'],
32 | 'allow-rebalance': [false, 'Allow `rebalance` queries'],
33 | 'allow-http': [false, 'Allow queries with the `http` term'],
34 | 'allow-javascript': [false, 'Allow queries with the `js` term']
35 | });
36 |
37 | cli.main(function (args, _opts) {
38 | var opts = {};
39 | for (var key in _opts) {
40 | if (_opts.hasOwnProperty(key) && _opts[key] !== null) {
41 | opts[key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); })] = _opts[key];
42 | }
43 | }
44 | var server = new RethinkDBProxy(opts);
45 | server.listen(opts.port);
46 | });
47 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | var RethinkDBProxy = require('./lib/');
3 | var server = new RethinkDBProxy({});
4 | server.listen();
5 |
--------------------------------------------------------------------------------
/lib-test/changefeeds.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
25 |
26 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
27 |
28 | var proxyPort = 8125;
29 | var dbName = 'rethinkdb_proxy_test';
30 | var secondDbName = 'rethinkdb_proxy_test_2';
31 | var tableName = 'entries';
32 | var server = undefined;
33 |
34 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
35 | var executeProxyQuery = (0, _utils.makeExecuteProxyQuery)(dbName, proxyPort);
36 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
37 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
38 | var createSecondDatabase = (0, _utils.makeCreateDatabase)(secondDbName, tableName);
39 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
40 | var throwError = function throwError(res) {
41 | throw new Error();
42 | };
43 | var expectError = function expectError(errorName, errorMessageMatch, err) {
44 | if (errorName !== null) errorName.should.equal(err.name);
45 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
46 | (err instanceof Error).should.equal(true);
47 | };
48 | var closeCursorAndConn = function closeCursorAndConn(cursor, conn) {
49 | return cursor.close().then(function () {
50 | return conn.close();
51 | });
52 | };
53 | var ce = function ce(err) {
54 | console.log('Error');
55 | console.log(err);
56 | throw err;
57 | };
58 |
59 | describe('Changefeeds', function () {
60 |
61 | before(function (done) {
62 | this.timeout(5000);
63 | createDatabase().then(function () {
64 | return new _bluebird2['default'](function (resolve, reject) {
65 | server = new _componentsRethinkdbProxy2['default']({
66 | port: proxyPort,
67 | allowInsert: true
68 | });
69 | server.listen(resolve);
70 | });
71 | }).nodeify(done);
72 | });
73 |
74 | after(function (done) {
75 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
76 | });
77 |
78 | it('should listen for changes', function (done) {
79 | this.timeout(10000);
80 | var count = 0,
81 | results = [],
82 | cursor;
83 | // HINT: You'll need to pass an anonymous function to `filter`
84 | _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
85 | return _rethinkdb2['default'].table(tableName).changes().run(conn).then(function (_cursor) {
86 | cursor = _cursor;
87 | cursor.each(function (err, row) {
88 | count += 1;
89 | results.push(row);
90 | });
91 | }).then(function () {
92 | return _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
93 | return _bluebird2['default'].all([_rethinkdb2['default'].table(tableName).insert({ name: 'SomeCountryName', population: 1 }).run(conn)['catch'](ce), _rethinkdb2['default'].table(tableName).insert({ name: 'Transylvania', population: 2000 }).run(conn)['catch'](ce), _rethinkdb2['default'].table(tableName).insert({ name: 'Hong Kong', population: 1500 }).run(conn), _rethinkdb2['default'].table(tableName).insert({ name: 'Bavaira', population: 98 }).run(conn)]).then(conn.close.bind(conn));
94 | });
95 | }).then(function () {
96 | return new _bluebird2['default'](function (resolve, reject) {
97 | setTimeout(function () {
98 | count.should.equal(4);
99 | results.should.be.instanceOf(Array);
100 | results = results.sort(function (a, b) {
101 | return a.new_val.population - b.new_val.population;
102 | });
103 | results[0].new_val.population.should.equal(1);
104 | results[1].new_val.population.should.equal(98);
105 | results[0].new_val.should.have.property('name');
106 | results[1].new_val.should.have.property('name');
107 | resolve();
108 | }, 50); // Hopefully, this is enough
109 | });
110 | }).then(function () {
111 | return closeCursorAndConn(cursor, conn);
112 | }).nodeify(done);
113 | });
114 | });
115 |
116 | it('should listen for multiple changesfeeds', function (done) {
117 | this.timeout(10000);
118 | var first_count = 0,
119 | second_count = 0,
120 | results = [],
121 | cursor;
122 | return _bluebird2['default'].all([_rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
123 | return _rethinkdb2['default'].table(tableName).filter(_rethinkdb2['default'].row('population').lt(500)).changes().run(conn).then(function (cursor) {
124 | cursor.each(function (err, row) {
125 | first_count += 1;
126 | results.push(row);
127 | }, conn.close.bind(conn));
128 | });
129 | }), _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
130 | return _rethinkdb2['default'].table(tableName).filter(_rethinkdb2['default'].row('population').gt(500)).changes().run(conn).then(function (cursor) {
131 | cursor.each(function (err, row) {
132 | second_count += 1;
133 | results.push(row);
134 | }, conn.close.bind(conn));
135 | });
136 | })]).then(function () {
137 | return _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
138 | return _bluebird2['default'].all([_rethinkdb2['default'].table(tableName).insert({ name: 'SomeCountryName', population: 1 }).run(conn)['catch'](ce), _rethinkdb2['default'].table(tableName).insert({ name: 'Transylvania', population: 2000 }).run(conn)['catch'](ce), _rethinkdb2['default'].table(tableName).insert({ name: 'Hong Kong', population: 1500 }).run(conn), _rethinkdb2['default'].table(tableName).insert({ name: 'Bavaira', population: 98 }).run(conn)]).then(conn.close.bind(conn));
139 | });
140 | }).then(function () {
141 | return new _bluebird2['default'](function (resolve, reject) {
142 | setTimeout(function () {
143 | first_count.should.equal(2);
144 | second_count.should.equal(2);
145 | results.should.be.instanceOf(Array);
146 | results = results.sort(function (a, b) {
147 | return a.new_val.population - b.new_val.population;
148 | });
149 | results[0].new_val.population.should.equal(1);
150 | results[1].new_val.population.should.equal(98);
151 | results[0].new_val.should.have.property('name');
152 | results[1].new_val.should.have.property('name');
153 | resolve();
154 | }, 50); // Hopefully, this is enough
155 | });
156 | }).nodeify(done);
157 | });
158 | });
--------------------------------------------------------------------------------
/lib-test/components/buffer-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 | var BufferParser;
7 | if (process.env.NODE_ENV === 'test_es6') {
8 | // Called from /test
9 | var BufferParserES6 = require('../../src/buffer-parser');
10 | BufferParser = BufferParserES6;
11 | } else {
12 | // Called from /lib/test
13 | var BufferParserES5 = require('../../lib/buffer-parser');
14 | BufferParser = BufferParserES5;
15 | }
16 | exports['default'] = BufferParser;
17 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib-test/components/rethinkdb-proxy.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', {
4 | value: true
5 | });
6 | var RethinkDBProxy;
7 | if (process.env.NODE_ENV === 'test_es6') {
8 | // Called from /test
9 | var RethinkDBProxyES6 = require('../../src');
10 | RethinkDBProxy = RethinkDBProxyES6;
11 | } else {
12 | // Called from /lib/test
13 | var RethinkDBProxyES5 = require('../../lib');
14 | RethinkDBProxy = RethinkDBProxyES5;
15 | }
16 | exports['default'] = RethinkDBProxy;
17 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib-test/connection.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var proxyPort = 8125;
23 | var server = undefined;
24 |
25 | describe('Connection', function () {
26 |
27 | before(function (done) {
28 | server = new _componentsRethinkdbProxy2['default']({
29 | port: proxyPort
30 | });
31 | server.listen(done);
32 | });
33 |
34 | after(function (done) {
35 | server.close().then(done.bind(null, null));
36 | });
37 |
38 | it('should create a connection successfully', function (done) {
39 | _bluebird2['default'].resolve().then(function () {
40 | return [_rethinkdb2['default'].connect(), _rethinkdb2['default'].connect({ port: proxyPort })];
41 | }).spread(function (connA, connB) {
42 | connA.port.should.equal(28015);
43 | connB.port.should.equal(proxyPort);
44 | connA.constructor.should.equal(connB.constructor);
45 | done();
46 | });
47 | });
48 | });
--------------------------------------------------------------------------------
/lib-test/database-and-table-access.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
25 |
26 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
27 |
28 | var proxyPort = 8125;
29 | var dbName = 'rethinkdb_proxy_test';
30 | var secondDbName = 'rethinkdb_proxy_test_2';
31 | var tableName = 'entries';
32 | var server = undefined;
33 |
34 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
35 | var executeProxyQuery = (0, _utils.makeExecuteProxyQuery)(dbName, proxyPort);
36 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
37 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
38 | var createSecondDatabase = (0, _utils.makeCreateDatabase)(secondDbName, tableName);
39 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
40 | var throwError = function throwError(res) {
41 | throw new Error();
42 | };
43 | var expectError = function expectError(errorName, errorMessageMatch, err) {
44 | if (errorName !== null) errorName.should.equal(err.name);
45 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
46 | (err instanceof Error).should.equal(true);
47 | };
48 |
49 | describe('Database and Table Access', function () {
50 |
51 | var get = _rethinkdb2['default'].db(dbName).table(tableName);
52 |
53 | describe('allowSysDbAccess', function () {
54 |
55 | describe('Disallow RethinkDB Database Access', function () {
56 |
57 | before(function (done) {
58 | this.timeout(10000);
59 | createDatabase().then(function () {
60 | return new _bluebird2['default'](function (resolve, reject) {
61 | server = new _componentsRethinkdbProxy2['default']({
62 | port: proxyPort
63 | });
64 | server.listen(resolve);
65 | });
66 | }).nodeify(done);
67 | });
68 |
69 | after(function (done) {
70 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
71 | });
72 |
73 | it('should not allow a query that uses the `rethinkdb` database', function (done) {
74 | executeQuery(_rethinkdb2['default'].db('rethinkdb').tableList()).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
75 | });
76 |
77 | it('should not allow a query that uses the `rethinkdb` database inside a `do`', function (done) {
78 | executeQuery(_rethinkdb2['default'].expr(true)['do'](function () {
79 | return _rethinkdb2['default'].db('rethinkdb').tableList();
80 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
81 | });
82 |
83 | it('should not allow a query that uses the `rethinkdb` database inside a `do` inside a `do`', function (done) {
84 | executeQuery(_rethinkdb2['default'].expr([1, 2, 3])['do'](function (arr) {
85 | return _rethinkdb2['default'].expr(arr)['do'](function () {
86 | return _rethinkdb2['default'].db('rethinkdb').tableList();
87 | });
88 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
89 | });
90 |
91 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', function (done) {
92 | executeQuery(_rethinkdb2['default'].db('rethinkdb').table('server_config')).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
93 | });
94 | });
95 |
96 | describe('Allow RethinkDB Database Access', function () {
97 |
98 | before(function (done) {
99 | this.timeout(10000);
100 | createDatabase().then(function () {
101 | return new _bluebird2['default'](function (resolve, reject) {
102 | server = new _componentsRethinkdbProxy2['default']({
103 | port: proxyPort,
104 | allowSysDbAccess: true
105 | });
106 | server.listen(resolve);
107 | });
108 | }).nodeify(done);
109 | });
110 |
111 | after(function (done) {
112 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
113 | });
114 |
115 | it('should allow a query that uses the `rethinkdb` database, if explicitly set', function (done) {
116 | executeQuery(_rethinkdb2['default'].db('rethinkdb').tableList()).then(function (res) {
117 | Array.isArray(res).should.equal(true);
118 | res.length.should.not.be.equal(0);
119 | }).nodeify(done);
120 | });
121 |
122 | it('should allow a query queries the `rehtinkdb` database if explicitly set', function (done) {
123 | executeQuery(_rethinkdb2['default'].db('rethinkdb').table('server_config').coerceTo('array')).then(function (res) {
124 | Array.isArray(res).should.equal(true);
125 | res.length.should.not.be.equal(0);
126 | }).nodeify(done);
127 | });
128 | });
129 | });
130 |
131 | describe('Database Access', function () {
132 | before(function (done) {
133 | this.timeout(10000);
134 | createDatabase().then(createSecondDatabase).then(function () {
135 | return new _bluebird2['default'](function (resolve, reject) {
136 | server = new _componentsRethinkdbProxy2['default']({
137 | port: proxyPort,
138 | dbs: [dbName, 'someOtherDb']
139 | });
140 | server.listen(resolve);
141 | });
142 | }).nodeify(done);
143 | });
144 |
145 | after(function (done) {
146 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
147 | });
148 |
149 | it('should not allow access to a database that was not allowed', function (done) {
150 | executeProxyQuery(_rethinkdb2['default'].db(secondDbName).tableList()).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
151 | });
152 |
153 | it('should not allow access to a database that was not allowed inside a `do`', function (done) {
154 | executeProxyQuery(_rethinkdb2['default'].expr(1)['do'](function () {
155 | return _rethinkdb2['default'].db(secondDbName).tableList();
156 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
157 | });
158 |
159 | it('should not allow access to a database that was not allowed inside a `do`', function (done) {
160 | executeProxyQuery(_rethinkdb2['default'].expr(true)['do'](function () {
161 | return [_rethinkdb2['default'].db(secondDbName).tableList(), 4, 5, 9];
162 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
163 | });
164 |
165 | it('should allow access to a database that is allowed', function (done) {
166 | executeProxyQuery(_rethinkdb2['default'].db(dbName).tableList()).then(function (list) {
167 | list.should.be['instanceof'](Array);
168 | }).nodeify(done);
169 | });
170 |
171 | describe('Connection', function () {
172 | it('should throw an error when trying to connect with an unallowed database', function (done) {
173 | _rethinkdb2['default'].connect({ port: proxyPort, db: secondDbName }).then(function (conn) {
174 | return _rethinkdb2['default'].tableList().run(conn);
175 | }).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
176 | });
177 | });
178 |
179 | describe('Query arguments', function () {
180 |
181 | it('should not allow a database name to be passed through r.args', function (done) {
182 | // For some reason, using `executeProxyQuery` doesn't work...
183 | _rethinkdb2['default'].connect({ port: proxyPort }).then(function (conn) {
184 | return _rethinkdb2['default'].db(_rethinkdb2['default'].args([dbName])).tableList().run(conn).then(conn.close.bind(conn));
185 | }).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
186 | });
187 |
188 | it('should allow a database name to be passed through r.expr', function (done) {
189 | executeProxyQuery(_rethinkdb2['default'].db(_rethinkdb2['default'].expr(dbName)).tableList()).then(function (list) {
190 | list.should.be['instanceof'](Array);
191 | }).nodeify(done);
192 | });
193 | });
194 | });
195 |
196 | describe('Table Access', function () {
197 |
198 | before(function (done) {
199 | this.timeout(10000);
200 | createDatabase().then(createSecondDatabase).then(function () {
201 | return new _bluebird2['default'](function (resolve, reject) {
202 | server = new _componentsRethinkdbProxy2['default']({
203 | port: proxyPort,
204 | dbs: [dbName, 'someOtherDb'],
205 | tables: dbName + '.' + tableName
206 | });
207 | server.listen(resolve);
208 | });
209 | }).nodeify(done);
210 | });
211 |
212 | after(function (done) {
213 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
214 | });
215 |
216 | it('should not allow access to a table if not allowed', function (done) {
217 | this.timeout(5000);
218 | _rethinkdb2['default'].connect({ db: dbName }).then(function (conn) {
219 | return _rethinkdb2['default'].tableCreate('someOtherTable').run(conn).then(function () {
220 | return conn.close();
221 | });
222 | }).then(function () {
223 | return executeProxyQuery(_rethinkdb2['default'].db(dbName).table('someOtherTable')).then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i)).nodeify(done);
224 | });
225 | });
226 |
227 | it('should allow access to a table if allowed', function (done) {
228 | executeProxyQuery(_rethinkdb2['default'].db(dbName).table(tableName)).then(function (list) {
229 | list.should.be['instanceof'](Array);
230 | }).nodeify(done);
231 | });
232 |
233 | describe('Connection', function () {
234 | it('should not throw an error when trying query a table with an unallowed default database', function (done) {
235 | _rethinkdb2['default'].connect({ port: proxyPort, db: 'someOtherDbNotAllowed' }).then(function (conn) {
236 | return _rethinkdb2['default'].table(tableName).run(conn);
237 | }).then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i)).nodeify(done);
238 | });
239 |
240 | it('should not throw an error when trying query a table with an unallowed table', function (done) {
241 | _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
242 | return _rethinkdb2['default'].table(tableName).coerceTo('array').run(conn);
243 | }).then(function (list) {
244 | return list.should.be['instanceof'](Array);
245 | }).nodeify(done);
246 | });
247 |
248 | it('should throw an error when trying query a table with an unallowed default database', function (done) {
249 | _rethinkdb2['default'].connect({ port: proxyPort, db: dbName }).then(function (conn) {
250 | return _rethinkdb2['default'].table('someOtherTable').run(conn);
251 | }).then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i)).nodeify(done);
252 | });
253 | });
254 |
255 | describe('Query arguments', function () {
256 |
257 | it('should not allow a table name to be passed through r.args', function (done) {
258 | executeProxyQuery(_rethinkdb2['default'].db(dbName).table(_rethinkdb2['default'].args([tableName]))).then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i)).nodeify(done);
259 | });
260 |
261 | it('should allow a table name to be passed through r.expr', function (done) {
262 | executeProxyQuery(_rethinkdb2['default'].db(dbName).table(_rethinkdb2['default'].expr(tableName))).then(function (list) {
263 | list.should.be['instanceof'](Array);
264 | }).nodeify(done);
265 | });
266 |
267 | it('should not allow a table name to be passed through a ReQL expression', function (done) {
268 | executeProxyQuery(_rethinkdb2['default'].db(dbName).table(_rethinkdb2['default'].db(dbName).tableList()(0))).then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i)).nodeify(done);
269 | });
270 | });
271 | });
272 | });
--------------------------------------------------------------------------------
/lib-test/edge-cases.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
25 |
26 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
27 |
28 | var proxyPort = 8125;
29 | var dbName = 'rethinkdb_proxy_test';
30 | var tableName = 'entries';
31 | var server = undefined;
32 |
33 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
34 | var executeProxyQuery = (0, _utils.makeExecuteProxyQuery)(dbName, proxyPort);
35 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
36 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
37 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
38 | var throwError = function throwError(res) {
39 | throw new Error();
40 | };
41 | var expectError = function expectError(errorName, errorMessageMatch, err) {
42 | if (errorName !== null) errorName.should.equal(err.name);
43 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
44 | (err instanceof Error).should.equal(true);
45 | };
46 |
47 | describe('Edge Cases', function () {
48 |
49 | var get = _rethinkdb2['default'].db(dbName).table(tableName);
50 |
51 | describe('Replace', function () {
52 |
53 | before(function (done) {
54 | this.timeout(5000);
55 | createDatabase().then(function () {
56 | return new _bluebird2['default'](function (resolve, reject) {
57 | server = new _componentsRethinkdbProxy2['default']({
58 | port: proxyPort,
59 | allowReplace: true
60 | });
61 | server.listen(resolve);
62 | });
63 | }).then(function () {
64 | return _rethinkdb2['default'].connect().then(function (conn) {
65 | return get.insert([{
66 | id: 1, name: 'Hello'
67 | }, {
68 | id: 2, name: 'Jorge'
69 | }]).run(conn).then(function () {
70 | return conn.close();
71 | });
72 | });
73 | }).nodeify(done);
74 | });
75 |
76 | after(function (done) {
77 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
78 | });
79 |
80 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', function (done) {
81 | executeQuery(get.get(1).replace(null)).then(throwError, expectError.bind(null, 'ReqlDriverError', /REPLACE/i)).nodeify(done);
82 | });
83 |
84 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', function (done) {
85 | executeQuery(get.replace(null)).then(throwError, expectError.bind(null, 'ReqlDriverError', /REPLACE/i)).nodeify(done);
86 | });
87 | });
88 |
89 | describe('Insert', function () {
90 |
91 | before(function (done) {
92 | this.timeout(5000);
93 | createDatabase().then(function () {
94 | return new _bluebird2['default'](function (resolve, reject) {
95 | server = new _componentsRethinkdbProxy2['default']({
96 | port: proxyPort,
97 | allowInsert: true
98 | });
99 | server.listen(resolve);
100 | });
101 | }).then(function () {
102 | return _rethinkdb2['default'].connect().then(function (conn) {
103 | return get.insert([{
104 | id: 1, name: 'Hello'
105 | }, {
106 | id: 2, name: 'Jorge'
107 | }]).run(conn).then(function () {
108 | return conn.close();
109 | });
110 | });
111 | }).nodeify(done);
112 | });
113 |
114 | after(function (done) {
115 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
116 | });
117 |
118 | it('should not allow a query that passes `conflict: replace` if `replace` is not allowed', function (done) {
119 | executeProxyQuery(get.insert({ id: 1, name: 'Hugo' }, { conflict: 'replace' })).then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i)).nodeify(done);
120 | });
121 |
122 | it('should not allow a query that passes `conflict: update` if `update` is not allowed', function (done) {
123 | executeProxyQuery(get.insert({ id: 1, name: 'Hugo' }, { conflict: 'update' })).then(throwError, expectError.bind(null, 'ReqlDriverError', /UPDATE/i)).nodeify(done);
124 | });
125 | });
126 | });
--------------------------------------------------------------------------------
/lib-test/index.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 |
3 | 'use strict';
4 |
5 | require('./parser.js');
6 |
7 | require('./connection.js');
8 |
9 | require('./queries.js');
10 |
11 | require('./read-only-queries.js');
12 |
13 | require('./edge-cases.js');
14 |
15 | require('./database-and-table-access.js');
16 |
17 | require('./changefeeds.js');
18 |
19 | require('./parallel.js');
--------------------------------------------------------------------------------
/lib-test/parallel.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
25 |
26 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
27 |
28 | var proxyPort = 8125;
29 | var dbName = 'rethinkdb_proxy_test';
30 | var secondDbName = 'rethinkdb_proxy_test_2';
31 | var tableName = 'entries';
32 | var server = undefined;
33 |
34 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
35 | var executeProxyQuery = (0, _utils.makeExecuteProxyQuery)(dbName, proxyPort);
36 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
37 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
38 | var createSecondDatabase = (0, _utils.makeCreateDatabase)(secondDbName, tableName);
39 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
40 | var throwError = function throwError(res) {
41 | throw new Error();
42 | };
43 | var expectError = function expectError(errorName, errorMessageMatch, err) {
44 | if (errorName !== null) errorName.should.equal(err.name);
45 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
46 | (err instanceof Error).should.equal(true);
47 | };
48 |
49 | describe('Parallel Queries', function () {
50 |
51 | var testData = [{ name: 'Germany' }, { name: 'Bhutan' }, { name: 'Maldives' }];
52 |
53 | before(function (done) {
54 | this.timeout(10000);
55 | createDatabase().then(function () {
56 | return _rethinkdb2['default'].connect().then(function (conn) {
57 | return _rethinkdb2['default'].db(dbName).table(tableName).insert(testData).run(conn);
58 | });
59 | }).then(function () {
60 | return new _bluebird2['default'](function (resolve, reject) {
61 | server = new _componentsRethinkdbProxy2['default']({
62 | port: proxyPort
63 | });
64 | server.listen(resolve);
65 | });
66 | }).nodeify(done);
67 | });
68 |
69 | after(function (done) {
70 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
71 | });
72 |
73 | it('should handle parallel connections', function (done) {
74 | //this.timeout(15000);
75 | var query = function query(countryName) {
76 | return _rethinkdb2['default'].db(dbName).table(tableName).filter({ 'name': countryName }).coerceTo('array');
77 | };
78 | var query1 = query('Germany');
79 | var query2 = query('Maldives');
80 | var arr = (function () {
81 | var arr = [];
82 | for (var i = 0; i < 5; i += 1) {
83 | arr.push(i);
84 | }return arr;
85 | })();
86 | _rethinkdb2['default'].connect({ port: proxyPort }).then(function (conn1) {
87 | return _bluebird2['default'].all([].concat(arr.map(function () {
88 | return query1.run(conn1);
89 | })).concat(arr.map(function () {
90 | return query2.run(conn1);
91 | }))).then(function (res) {
92 | res.slice(0, 5).forEach(function (row) {
93 | return row[0].name.should.equal('Germany');
94 | });
95 | res.slice(5, 10).forEach(function (row) {
96 | return row[0].name.should.equal('Maldives');
97 | });
98 | });
99 | }).nodeify(done);
100 | });
101 |
102 | it('should handle parallel cursors', function (done) {
103 | _rethinkdb2['default'].connect({ port: proxyPort }).then(function (conn1) {
104 | return _rethinkdb2['default'].db(dbName).table(tableName).run(conn1).then(function (cursor) {
105 | var processRow = function processRow(err, row) {
106 | if (err === null) return cursor.next(processRow);
107 | if (err.msg === 'No more rows in the cursor.') return done();
108 | return done(err);
109 | };
110 | cursor.next(processRow);
111 | });
112 | });
113 | });
114 | });
--------------------------------------------------------------------------------
/lib-test/parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
4 |
5 | var _componentsBufferParser = require('./components/buffer-parser');
6 |
7 | var _componentsBufferParser2 = _interopRequireDefault(_componentsBufferParser);
8 |
9 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
10 |
11 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
12 |
13 | require('should');
14 |
15 | describe('Buffer Parser', function () {
16 |
17 | var parser = undefined;
18 | var parseQuery = function parseQuery() {
19 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
20 | args[_key] = arguments[_key];
21 | }
22 |
23 | var result = [];
24 | var cb = args.pop();
25 | parser.on('query', function (query) {
26 | result.push(query);
27 | });
28 | var count = 0;
29 | for (var key in args) {
30 | if (args.hasOwnProperty(key)) {
31 | var value = args[key];
32 | var token = count++;
33 | var tokenBuffer = new Buffer(8);
34 | tokenBuffer.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
35 | var byteLengthBuffer = new Buffer(4);
36 | var queryBuffer = new Buffer(JSON.stringify(value));
37 | byteLengthBuffer.writeUInt32LE(queryBuffer.length, 0);
38 | parser.append(Buffer.concat([tokenBuffer, byteLengthBuffer, queryBuffer]));
39 | }
40 | }
41 | cb(result);
42 | };
43 |
44 | beforeEach(function () {
45 | parser = new _componentsBufferParser2['default']();
46 | var version = new Buffer(4);
47 | version.writeUInt32LE(_node_modulesRethinkdbProtoDef2['default'].VersionDummy.Version.VO_4, 0);
48 | var auth_buffer = new Buffer('', 'ascii');
49 | var auth_length = new Buffer(4);
50 | auth_length.writeUInt32LE(auth_buffer.length, 0);
51 | var protocol = new Buffer(4);
52 | protocol.writeUInt32LE(_node_modulesRethinkdbProtoDef2['default'].VersionDummy.Protocol.JSON, 0);
53 | var token = Buffer.concat([version, auth_length, auth_buffer, protocol]);
54 | parser.append(token);
55 | });
56 |
57 | it('should correctly parse a single buffer', function () {
58 | var value = [[1]];
59 | parseQuery(value, function (result) {
60 | result[0].should.eql(value);
61 | });
62 | });
63 |
64 | it('should correctly parse a multiples buffers', function () {
65 | var value0 = [[1]];
66 | var value1 = [1, [3, [4, [3]]]];
67 | var value2 = [1, [3, [4, [3]]]];
68 | parseQuery(value0, value1, value2, function (result) {
69 | result[0].should.eql(value0);
70 | result[1].should.eql(value1);
71 | result[2].should.eql(value2);
72 | });
73 | });
74 |
75 | it('should correctly parse a parser with `]` inside the bytelength/token`', function () {
76 | var value = [1, [51, [[39, [[15, [[14, ['rethinkdb_proxy_test']], 'entries']], { 'name': 'Germany' }]], 'array']]];
77 | parseQuery(value, value, value, function (result) {
78 | result[0].should.eql(value);
79 | result[1].should.eql(value);
80 | result[2].should.eql(value);
81 | });
82 | });
83 |
84 | it('should correctly handle strings with `[]`', function () {
85 | var value = [1, [51, [[39, [[15, [[14, ['rethinkdb_proxy_test']], 'entries']], { 'name': '[[[[[[[' }]], 'array']]];
86 | parseQuery(value, value, value, function (result) {
87 | result[0].should.eql(value);
88 | result[1].should.eql(value);
89 | result[2].should.eql(value);
90 | });
91 | });
92 | });
--------------------------------------------------------------------------------
/lib-test/queries.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var proxyPort = 8125;
25 | var dbName = 'rethinkdb_proxy_test';
26 | var tableName = 'entries';
27 | var server = undefined;
28 |
29 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
30 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
31 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
32 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
33 |
34 | describe('Normal Queries', function () {
35 |
36 | before(function (done) {
37 | this.timeout(5000);
38 | createDatabase().then(function () {
39 | server = new _componentsRethinkdbProxy2['default']({
40 | port: proxyPort,
41 | allowWrites: true
42 | });
43 | server.listen(done);
44 | });
45 | });
46 |
47 | after(function (done) {
48 | server.close().then(dropDatabase).then(done.bind(null, null));
49 | });
50 |
51 | describe('Read Queries', function () {
52 |
53 | it('should return an `r.expr` successfully', function (done) {
54 | assertQuery(_rethinkdb2['default'].expr([1, 2, 3])).nodeify(done);
55 | });
56 |
57 | it('should return the same list of databases', function (done) {
58 | assertQuery(_rethinkdb2['default'].dbList()).nodeify(done);
59 | });
60 |
61 | it('should handle group queries', function (done) {
62 | assertQuery(_rethinkdb2['default'].expr([{ 'v': 1 }, { 'v': 2 }, { 'v': 2 }, { 'v': 4 }]).group('v').count().ungroup()).nodeify(done);
63 | });
64 | });
65 |
66 | describe('Write Queries', function () {
67 |
68 | it('should return the same result after a write', function (done) {
69 | var get = _rethinkdb2['default'].db(dbName).table(tableName);
70 | executeQuery(get.insert({ hello: 'world' })).then(function () {
71 | return assertQuery(get.orderBy('id'));
72 | }).then(function () {
73 | return _rethinkdb2['default'].connect().then(function (conn) {
74 | return get.count().run(conn).then(function (count) {});
75 | });
76 | }).nodeify(done);
77 | });
78 | });
79 | });
80 |
81 | //count.should.eql(2);
--------------------------------------------------------------------------------
/lib-test/read-only-queries.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | var _rethinkdb = require('rethinkdb');
7 |
8 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
9 |
10 | var _bluebird = require('bluebird');
11 |
12 | var _bluebird2 = _interopRequireDefault(_bluebird);
13 |
14 | var _componentsRethinkdbProxy = require('./components/rethinkdb-proxy');
15 |
16 | var _componentsRethinkdbProxy2 = _interopRequireDefault(_componentsRethinkdbProxy);
17 |
18 | var _should = require('should');
19 |
20 | var _should2 = _interopRequireDefault(_should);
21 |
22 | var _utils = require('./utils');
23 |
24 | var _node_modulesRethinkdbProtoDef = require('../node_modules/rethinkdb/proto-def');
25 |
26 | var _node_modulesRethinkdbProtoDef2 = _interopRequireDefault(_node_modulesRethinkdbProtoDef);
27 |
28 | var proxyPort = 8125;
29 | var dbName = 'rethinkdb_proxy_test';
30 | var tableName = 'entries';
31 | var server = undefined;
32 |
33 | var executeQuery = (0, _utils.makeExecuteQuery)(dbName, proxyPort);
34 | var executeProxyQuery = (0, _utils.makeExecuteProxyQuery)(dbName, proxyPort);
35 | var assertQuery = (0, _utils.makeAssertQuery)(executeQuery);
36 | var createDatabase = (0, _utils.makeCreateDatabase)(dbName, tableName);
37 | var dropDatabase = (0, _utils.makeDropDatabase)(dbName);
38 | var throwError = function throwError(res) {
39 | throw new Error();
40 | };
41 | var expectError = function expectError(errorName, errorMessageMatch, err) {
42 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
43 | if (errorName !== null) errorName.should.equal(err.name);
44 | (err instanceof Error).should.equal(true);
45 | };
46 |
47 | describe('Unallowed Queries', function () {
48 | var get = _rethinkdb2['default'].db(dbName).table(tableName);
49 |
50 | before(function (done) {
51 | this.timeout(5000);
52 | createDatabase().then(function () {
53 | server = new _componentsRethinkdbProxy2['default']({
54 | port: proxyPort
55 | });
56 | return server.listen().then(done);
57 | });
58 | });
59 |
60 | after(function (done) {
61 | dropDatabase().then(server.close.bind(server)).then(done.bind(null, null));
62 | });
63 |
64 | describe('Read Queries', function () {
65 |
66 | it('should return an `r.expr` successfully', function (done) {
67 | assertQuery(_rethinkdb2['default'].expr([1, 2, 3])).nodeify(done);
68 | });
69 |
70 | it('should return the same list of databases', function (done) {
71 | assertQuery(_rethinkdb2['default'].dbList()).nodeify(done);
72 | });
73 |
74 | it('should allow for queries inside arrays', function (done) {
75 | assertQuery(_rethinkdb2['default'].expr([get.coerceTo('array'), get.coerceTo('array')])).nodeify(done);
76 | });
77 | });
78 |
79 | describe('Insert', function () {
80 |
81 | it('should throw an error after attempting to write to the database', function (done) {
82 | executeProxyQuery(get.insert({ hello: 'world' })).then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i)).nodeify(done);
83 | });
84 |
85 | it('should not throw an error when using the same number as the `write` proto buff definition', function (done) {
86 | executeProxyQuery(_rethinkdb2['default'].expr([_node_modulesRethinkdbProtoDef2['default'].Term.TermType.INSERT, 1, 2, 3])).then(function (res) {
87 | res.should.eql([_node_modulesRethinkdbProtoDef2['default'].Term.TermType.INSERT, 1, 2, 3]);
88 | }).nodeify(done);
89 | });
90 |
91 | it('should not allow insert queries inside `do`', function (done) {
92 | executeProxyQuery(_rethinkdb2['default'].expr([1, 2, 3])['do'](function (row) {
93 | return get.insert({ name: row });
94 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i)).nodeify(done);
95 | });
96 |
97 | it('should not allow for write queries inside arrays', function (done) {
98 | executeProxyQuery(_rethinkdb2['default'].expr([get['delete'](), get.coerceTo('array')])).then(throwError, expectError.bind(null, 'ReqlDriverError', /DELETE/i)).nodeify(done);
99 | });
100 | });
101 |
102 | describe('Delete', function () {
103 | it('should not allow delete queries inside `forEach` inside a `do`', function (done) {
104 | executeProxyQuery(get.coerceTo('array')['do'](function (rows) {
105 | return rows.forEach(function (row) {
106 | return get.get(row('id'))['delete']();
107 | });
108 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /DELETE/i)).nodeify(done);
109 | });
110 | });
111 |
112 | describe('HTTP', function () {
113 |
114 | it('should not allow http queries inside `map` inside a `do`', function (done) {
115 | executeProxyQuery(get.coerceTo('array').slice(0, 3)['do'](function (rows) {
116 | return rows.map(function (row) {
117 | return _rethinkdb2['default'].http('http://www.reddit.com/r/javascript.json');
118 | });
119 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /HTTP/i)).nodeify(done);
120 | });
121 |
122 | it('should not allow http queries inside `do`', function (done) {
123 | executeProxyQuery(_rethinkdb2['default'].expr('hello')['do'](function (rows) {
124 | return _rethinkdb2['default'].http('http://www.reddit.com/r/javascript.json');
125 | })).then(throwError, expectError.bind(null, 'ReqlDriverError', /HTTP/i)).nodeify(done);
126 | });
127 | });
128 | });
--------------------------------------------------------------------------------
/lib-test/utils.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
5 |
6 | Object.defineProperty(exports, '__esModule', {
7 | value: true
8 | });
9 | exports.makeExecuteQuery = makeExecuteQuery;
10 | exports.makeExecuteProxyQuery = makeExecuteProxyQuery;
11 | exports.makeAssertQuery = makeAssertQuery;
12 | exports.makeCreateDatabase = makeCreateDatabase;
13 | exports.makeDropDatabase = makeDropDatabase;
14 |
15 | var _bluebird = require('bluebird');
16 |
17 | var _bluebird2 = _interopRequireDefault(_bluebird);
18 |
19 | var _rethinkdb = require('rethinkdb');
20 |
21 | var _rethinkdb2 = _interopRequireDefault(_rethinkdb);
22 |
23 | function makeExecuteQuery(dbName, proxyPort) {
24 | return function (query) {
25 | return _bluebird2['default'].resolve().then(function () {
26 | return [_rethinkdb2['default'].connect({ db: dbName }), _rethinkdb2['default'].connect({ port: proxyPort, db: dbName })];
27 | }).spread(function (connA, connB) {
28 | return _bluebird2['default'].all([query.run(connA), query.run(connB)]).spread(function (resultA, resultB) {
29 | if (typeof resultA.toArray === 'function' && typeof resultA.each === 'function') {
30 | return _bluebird2['default'].all([resultA.toArray(), resultB.toArray()]);
31 | }
32 | return [resultA, resultB];
33 | })['finally'](function () {
34 | return _bluebird2['default'].all([connA.close(), connB.close()]);
35 | });
36 | });
37 | };
38 | }
39 |
40 | function makeExecuteProxyQuery(dbName, proxyPort) {
41 | return function (query) {
42 | return _bluebird2['default'].resolve().then(function () {
43 | return _rethinkdb2['default'].connect({ port: proxyPort, db: dbName });
44 | }).then(function (conn) {
45 | return query.run(conn).then(function (result) {
46 | if (typeof result.toArray === 'function' && typeof result.each === 'function') {
47 | return result.toArray();
48 | }
49 | return result;
50 | })['finally'](function () {
51 | return conn.close();
52 | });
53 | });
54 | };
55 | }
56 |
57 | function makeAssertQuery(executeQuery) {
58 | return function (query) {
59 | return executeQuery(query).spread(function (resultA, resultB) {
60 | return resultA.should.eql(resultB);
61 | });
62 | };
63 | }
64 |
65 | function makeCreateDatabase(dbName, tableName) {
66 | return function (done) {
67 | return _rethinkdb2['default'].connect().then(function (conn) {
68 | return _bluebird2['default'].resolve().then(function () {
69 | return _rethinkdb2['default'].dbCreate(dbName).run(conn)['catch'](function (err) {});
70 | }).then(function () {
71 | return _rethinkdb2['default'].db(dbName).tableCreate(tableName).run(conn)['catch'](function (err) {});
72 | });
73 | });
74 | };
75 | }
76 |
77 | function makeDropDatabase(dbName) {
78 | return function () {
79 | return _rethinkdb2['default'].connect().then(function (conn) {
80 | return _rethinkdb2['default'].dbDrop(dbName).run(conn)['finally'](function () {
81 | return conn.close();
82 | });
83 | });
84 | };
85 | }
--------------------------------------------------------------------------------
/lib/buffer-parser.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | 'use strict';
3 |
4 | var _createClass = require('babel-runtime/helpers/create-class')['default'];
5 |
6 | var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
7 |
8 | var _Symbol = require('babel-runtime/core-js/symbol')['default'];
9 |
10 | Object.defineProperty(exports, '__esModule', {
11 | value: true
12 | });
13 | var _listeners_ = _Symbol('listeners');
14 | var _queue_ = _Symbol('queue');
15 |
16 | var BufferParser = (function () {
17 | function BufferParser() {
18 | _classCallCheck(this, BufferParser);
19 |
20 | this[_queue_] = new Buffer(0);
21 | this[_listeners_] = {};
22 | this.protoVersion = null;
23 | this.authKeyLength = null;
24 | this.authKey = null;
25 | this.protoProtocol = null;
26 | }
27 |
28 | _createClass(BufferParser, [{
29 | key: 'append',
30 | value: function append(buff) {
31 | if (buff !== undefined) {
32 | this[_queue_] = Buffer.concat([this[_queue_], buff]);
33 | }
34 | if (this.protoVersion === null) {
35 | if (Buffer.byteLength(this[_queue_]) >= 4) {
36 | this.protoVersion = this[_queue_].slice(0, 4).readUInt32LE();
37 | this[_queue_] = this[_queue_].slice(4);
38 | // Call code again to ensure we don't have the authKey
39 | this.append();
40 | }
41 | } else if (this.authKeyLength === null) {
42 | if (Buffer.byteLength(this[_queue_]) >= 4) {
43 | this.authKeyLength = this[_queue_].slice(0, 4).readUInt32LE();
44 | this[_queue_] = this[_queue_].slice(4);
45 | // Call code again to ensure we don't have the authKey
46 | this.append();
47 | }
48 | } else if (this.authKey === null) {
49 | if (Buffer.byteLength(this[_queue_] >= this.authKeyLength)) {
50 | this.authKey = this[_queue_].slice(0, this.authKeyLength).toString('ascii');
51 | this[_queue_] = this[_queue_].slice(this.authKeyLength);
52 | // Call code again to ensure we don't have the authKey
53 | this.append();
54 | }
55 | } else if (this.protoProtocol === null) {
56 | if (Buffer.byteLength(this[_queue_]) >= 4) {
57 | this.protoProtocol = this[_queue_].slice(0, 4).readUInt32LE();
58 | this[_queue_] = this[_queue_].slice(4);
59 | // Call code again to ensure we don't have the authKey
60 | this.append();
61 | this.fireEvent('connect', this.protoVersion, this.authKey, this.protoProtocol);
62 | }
63 | } else {
64 | this.parseQuery();
65 | }
66 | }
67 | }, {
68 | key: 'fireEvent',
69 | value: function fireEvent(eventName) {
70 | for (var _len = arguments.length, data = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
71 | data[_key - 1] = arguments[_key];
72 | }
73 |
74 | if (this[_listeners_][eventName] !== undefined) {
75 | this[_listeners_][eventName].forEach(function (func) {
76 | func.apply(null, data);
77 | });
78 | }
79 | }
80 | }, {
81 | key: 'parseQuery',
82 | value: function parseQuery() {
83 | /*!
84 | * Assumptions
85 | * 1. All clients have their own instance of BufferParser
86 | * 2. TCP connections send messages in order
87 | * 3. Because messages are sent in order by the client through a TCP connection,
88 | * we can assume that all packets are tokens followed by query bytelenghts
89 | * followed by queries
90 | */
91 | var newQueue = null;
92 | var _prevQuery = null;
93 | /*!
94 | * Make sure we have all three components necessary to parse the query:
95 | * token, byteLength, and query
96 | */
97 | if (Buffer.byteLength(this[_queue_]) <= 12) return;
98 | var token = this[_queue_].slice(0, 8).readUInt32LE();
99 | var byteLength = this[_queue_].slice(8, 12).readUInt32LE();
100 | var query = this[_queue_].slice(12, byteLength + 12);
101 | newQueue = this[_queue_].slice(byteLength + 12);
102 | try {
103 | // Simplest way to handle check if input is valid JSON
104 | var json = JSON.parse(query.toString());
105 | this.fireEvent('query', json, token);
106 | } catch (err) {
107 | // I think the problem has something to do with the fact that I'm adding a
108 | // comma somehwere ....
109 | this.fireEvent('error', token);
110 | }
111 | this[_queue_] = new Buffer(0);
112 | if (newQueue !== null && newQueue.length > 0 && Buffer.byteLength(newQueue) >= 8) {
113 | var _token = newQueue.slice(0, 8).readUInt32LE();
114 | if (parseInt(_token) === +_token) {
115 | this.append(newQueue);
116 | }
117 | }
118 | }
119 | }, {
120 | key: 'on',
121 | value: function on(eventName, func) {
122 | if (this[_listeners_][eventName] === undefined) {
123 | this[_listeners_][eventName] = [];
124 | }
125 | this[_listeners_][eventName].push(func);
126 | }
127 | }]);
128 |
129 | return BufferParser;
130 | })();
131 |
132 | exports['default'] = BufferParser;
133 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true, bitwise:false */
2 | 'use strict';
3 |
4 | var _createClass = require('babel-runtime/helpers/create-class')['default'];
5 |
6 | var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
7 |
8 | var _Promise = require('babel-runtime/core-js/promise')['default'];
9 |
10 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
11 |
12 | Object.defineProperty(exports, '__esModule', {
13 | value: true
14 | });
15 |
16 | require('babel/polyfill');
17 |
18 | var _net = require('net');
19 |
20 | var _net2 = _interopRequireDefault(_net);
21 |
22 | var _rethinkdbProtoDef = require('rethinkdb/proto-def');
23 |
24 | var _rethinkdbProtoDef2 = _interopRequireDefault(_rethinkdbProtoDef);
25 |
26 | var _bufferParser = require('./buffer-parser');
27 |
28 | var _bufferParser2 = _interopRequireDefault(_bufferParser);
29 |
30 | var _queryParser = require('./query-parser');
31 |
32 | var _optionsParser = require('./options-parser');
33 |
34 | var _optionsParser2 = _interopRequireDefault(_optionsParser);
35 |
36 | var _loggerJs = require('./logger.js');
37 |
38 | var _loggerJs2 = _interopRequireDefault(_loggerJs);
39 |
40 | var RethinkDBProxy = (function () {
41 | function RethinkDBProxy(opts) {
42 | _classCallCheck(this, RethinkDBProxy);
43 |
44 | // Set defaults and unallowedTerms
45 | this.opts = (0, _optionsParser2['default'])(opts);
46 | this.logger = new _loggerJs2['default'](opts.inLevel, opts.outLevel, opts.sysLevel);
47 | this.__connections = [];
48 | this.server = _net2['default'].createServer(this.connectionHandler.bind(this));
49 | return this;
50 | }
51 |
52 | _createClass(RethinkDBProxy, [{
53 | key: 'listen',
54 | value: function listen(cb) {
55 | var _this = this;
56 |
57 | return new _Promise(function (resolve, reject) {
58 | _this.server.listen(_this.opts.port, function (err) {
59 | if (err) {
60 | _this.logger.sys.info({ err: err, port: _this.opts.port }, 'Error tryingg to listen for traffic');
61 | if (typeof cb === 'function') cb(err);
62 | reject(err);
63 | }
64 | _this.logger.sys.info({ port: _this.opts.port }, 'server.listen');
65 | if (typeof cb === 'function') cb();
66 | resolve();
67 | });
68 | });
69 | }
70 | }, {
71 | key: 'close',
72 | value: function close(cb) {
73 | var _this2 = this;
74 |
75 | return new _Promise(function (resolve, reject) {
76 | _this2.server.close(function () {
77 | _this2.logger.sys.info('server.close');
78 | if (typeof cb === 'function') cb();
79 | resolve();
80 | });
81 | setTimeout(function () {
82 | _this2.logger.sys.info('Server closing all connections');
83 | _this2.__connections.forEach(function (conn) {
84 | _this2.logger.sys.trace({ conn: conn }, 'conn.destroy');
85 | conn.destroy();
86 | });
87 | }, 100);
88 | });
89 | }
90 | }, {
91 | key: 'makeSendResponse',
92 | value: function makeSendResponse(socket) {
93 | var _this3 = this;
94 |
95 | return function (response, token) {
96 | _this3.logger.out.info({
97 | token: token,
98 | response: response
99 | }, 'Send Response');
100 | var tokenBuffer = new Buffer(8);
101 | tokenBuffer.writeUInt32LE(token & 0xFFFFFFFF, 0);
102 | tokenBuffer.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
103 | var responseBuffer = new Buffer(JSON.stringify(response));
104 | var lengthBuffer = new Buffer(4);
105 | lengthBuffer.writeUInt32LE(responseBuffer.length, 0);
106 | if (socket.destroyed) return;
107 | socket.write(tokenBuffer);
108 | socket.write(lengthBuffer);
109 | socket.write(responseBuffer);
110 | };
111 | }
112 | }, {
113 | key: 'makeSendError',
114 | value: function makeSendError(clientSocket) {
115 | var _this4 = this;
116 |
117 | var sendResponseToClient = this.makeSendResponse(clientSocket);
118 | return function (token, message) {
119 | var response = {
120 | t: _rethinkdbProtoDef2['default'].Response.ResponseType.CLIENT_ERROR,
121 | b: [],
122 | n: [],
123 | r: [message]
124 | };
125 | _this4.logger.out.info({
126 | message: message,
127 | response: response,
128 | token: token
129 | }, 'Send Error');
130 | sendResponseToClient(response, token);
131 | };
132 | }
133 | }, {
134 | key: 'connectionHandler',
135 | value: function connectionHandler(clientSocket) {
136 | var _this5 = this;
137 |
138 | this.__connections.push(clientSocket);
139 |
140 | clientSocket.connected = false;
141 | var parser = new _bufferParser2['default']();
142 | var serverSocket = _net2['default'].connect(this.opts.rdbPort, this.opts.rdbHost);
143 | var sendResponseToServer = this.makeSendResponse(serverSocket);
144 | var sendResponseToClient = this.makeSendResponse(clientSocket);
145 | var sendError = this.makeSendError(clientSocket);
146 |
147 | /*!
148 | * Listeners
149 | */
150 | serverSocket.on('data', function (buff) {
151 | _this5.logger.out.info({
152 | buffer: buff.toString()
153 | }, 'Data received');
154 | if (buff.toString() === 'SUCCESS') clientSocket.connected = true;
155 | // NOTE: The socket might try to write something even if the connection
156 | // is closed
157 | if (clientSocket.destroyed) return;
158 | clientSocket.write(buff);
159 | });
160 |
161 | parser.on('connect', function (version, authKey, protoProtocol) {
162 | _this5.logger['in'].info({
163 | version: version,
164 | authKey: authKey,
165 | protoProtocol: protoProtocol
166 | }, 'parser.on.connect');
167 | var versionBuffer = new Buffer(4);
168 | versionBuffer.writeUInt32LE(version, 0);
169 | var authBuffer = new Buffer(authKey, 'ascii');
170 | var authLengthBuffer = new Buffer(4);
171 | authLengthBuffer.writeUInt32LE(authBuffer.length, 0);
172 | var protocolBuffer = new Buffer(4);
173 | protocolBuffer.writeUInt32LE(protoProtocol, 0);
174 | var token = Buffer.concat([versionBuffer, authLengthBuffer, authBuffer, protocolBuffer]);
175 | if (protoProtocol !== _rethinkdbProtoDef2['default'].VersionDummy.Protocol.JSON) {
176 | sendError(token, 'Proxy Error: Only JSON protocol allowed.');
177 | }
178 | serverSocket.write(token);
179 | });
180 |
181 | parser.on('error', function (token) {
182 | _this5.logger['in'].error({
183 | token: token
184 | }, 'parser.on.error');
185 | sendError('Proxy Error: Could not parse query correctly.');
186 | });
187 |
188 | parser.on('query', function (query, token) {
189 | _this5.logger['in'].info({
190 | query: query,
191 | token: token
192 | }, 'parser.on.query');
193 | var termsFound = (0, _queryParser.findTermsOrErrors)(_this5.opts, _this5.opts.unallowedTerms, query);
194 | if (termsFound.length > 0) {
195 | // This shouldn't throw an error. It should
196 | // send the error through the TCP connection
197 | var errorMessage = undefined;
198 | if (typeof termsFound[0] === 'object' && typeof termsFound[0].error === 'string') {
199 | errorMessage = termsFound[0].error;
200 | } else {
201 | errorMessage = 'Cannot execute query. "' + termsFound + '" query not allowed.';
202 | }
203 | _this5.logger['in'].warn({
204 | termsFound: termsFound,
205 | errorMessage: errorMessage
206 | }, 'parser.on.query Error');
207 | sendError(token, errorMessage);
208 | }
209 | return sendResponseToServer(query, token);
210 | });
211 |
212 | clientSocket.on('data', function (data) {
213 | _this5.logger['in'].trace({
214 | data: data
215 | }, 'clientSocket.on.data');
216 | return parser.append(data);
217 | });
218 | }
219 | }]);
220 |
221 | return RethinkDBProxy;
222 | })();
223 |
224 | exports['default'] = RethinkDBProxy;
225 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _createClass = require('babel-runtime/helpers/create-class')['default'];
4 |
5 | var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default'];
6 |
7 | var _Symbol = require('babel-runtime/core-js/symbol')['default'];
8 |
9 | var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];
10 |
11 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
12 |
13 | Object.defineProperty(exports, '__esModule', {
14 | value: true
15 | });
16 |
17 | var _bunyan = require('bunyan');
18 |
19 | var _bunyan2 = _interopRequireDefault(_bunyan);
20 |
21 | var _log_ = _Symbol('log');
22 |
23 | var levels = {
24 | 10: 'trace',
25 | 20: 'debug',
26 | 30: 'info',
27 | 40: 'warn',
28 | 50: 'error',
29 | 60: 'fatal'
30 | };
31 |
32 | exports.levels = levels;
33 |
34 | var LevelLogger = (function () {
35 | function LevelLogger(globalLevel, logger) {
36 | var _this = this;
37 |
38 | _classCallCheck(this, LevelLogger);
39 |
40 | this[_log_] = logger;
41 | // "fatal" (60): The service/app is going to stop or become unusable now.
42 | // An operator should definitely look into this soon.
43 | // "error" (50): Fatal for a particular request, but the service/app continues servicing other requests.
44 | // An operator should look at this soon(ish).
45 | // "warn" (40): A note on something that should probably be looked at by an operator eventually.
46 | // "info" (30): Detail on regular operation.
47 | // "debug" (20): Anything else, i.e. too verbose to be included in "info" level.
48 | // "trace" (10): Logging from external libraries used by your app or very detailed application logging.
49 | _Object$keys(levels).forEach(function (levelKey) {
50 | _this[levelKey] = _this.log(globalLevel, levelKey, levels[levelKey]);
51 | _this[levels[levelKey]] = _this.log(globalLevel, levelKey, levels[levelKey]);
52 | });
53 | }
54 |
55 | _createClass(LevelLogger, [{
56 | key: 'log',
57 | value: function log(globalLevel, level, levelName) {
58 | var _this2 = this;
59 |
60 | return function () {
61 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
62 | args[_key] = arguments[_key];
63 | }
64 |
65 | _this2[_log_][levelName].apply(_this2[_log_], args);
66 | };
67 | }
68 | }]);
69 |
70 | return LevelLogger;
71 | })();
72 |
73 | var Logger = function Logger(inLevel, outLevel, sysLevel) {
74 | _classCallCheck(this, Logger);
75 |
76 | var logger = _bunyan2['default'].createLogger({ name: 'rethinkdb-proxy' });
77 | this['in'] = new LevelLogger(inLevel, logger);
78 | this.out = new LevelLogger(outLevel, logger);
79 | this.sys = new LevelLogger(sysLevel, logger);
80 | };
81 |
82 | exports['default'] = Logger;
--------------------------------------------------------------------------------
/lib/options-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _Object$keys = require('babel-runtime/core-js/object/keys')['default'];
4 |
5 | var _Object$assign = require('babel-runtime/core-js/object/assign')['default'];
6 |
7 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
8 |
9 | Object.defineProperty(exports, '__esModule', {
10 | value: true
11 | });
12 |
13 | var _taser = require('taser');
14 |
15 | var _taser2 = _interopRequireDefault(_taser);
16 |
17 | var _loggerJs = require('./logger.js');
18 |
19 | var assertStringOrArray = (0, _taser2['default'])(['string', 'array']);
20 | var assertBoolean = (0, _taser2['default'])(['boolean']);
21 | var assertNumber = (0, _taser2['default'])(['number']);
22 | var assertString = (0, _taser2['default'])(['string']);
23 | var assertLevelString = (0, _taser2['default'])({
24 | type: 'string',
25 | values: (function () {
26 | return _Object$keys(_loggerJs.levels).map(function (levelKey) {
27 | return _loggerJs.levels[levelKey];
28 | });
29 | })()
30 | });
31 |
32 | var getAllowedTermsOpts = function getAllowedTermsOpts(opts) {
33 | // By default, don't allow any of these terms
34 | opts.unallowedTerms = ['INSERT', 'UPDATE', 'REPLACE', 'DELETE', 'DB_CREATE', 'DB_DROP', 'TABLE_CREATE', 'TABLE_DROP', 'INDEX_CREATE', 'INDEX_DROP', 'INDEX_RENAME', 'RECONFIGURE', 'REBALANCE', 'HTTP', 'JAVASCRIPT'];
35 |
36 | var toUpperCaseSnakeCase = function toUpperCaseSnakeCase(str) {
37 | return str.replace(/(^[A-Z])/g, function ($1) {
38 | return $1.toLowerCase();
39 | }).replace(/([A-Z])/g, function ($1) {
40 | return '_' + $1.toUpperCase();
41 | }).toUpperCase();
42 | };
43 | var allowTerm = function allowTerm(termName) {
44 | if (opts.unallowedTerms.includes(termName)) {
45 | opts.unallowedTerms.splice(opts.unallowedTerms.indexOf(termName), 1);
46 | }
47 | };
48 | // Allow all terms specified by user (single terms)
49 | for (var key in opts) {
50 | if (opts.hasOwnProperty(key)) {
51 | if (opts[key] === true && key.substring(0, 5) === 'allow') {
52 | var termName = toUpperCaseSnakeCase(key.substring(5));
53 | allowTerm(termName);
54 | }
55 | }
56 | }
57 | // Allow all terms specified (multiple terms)
58 | if (opts.allowWrites) {
59 | allowTerm('INSERT');
60 | allowTerm('UPDATE');
61 | allowTerm('DELETE');
62 | }
63 | if (opts.allowIndexes) {
64 | allowTerm('INDEX_CREATE');
65 | allowTerm('INDEX_DROP');
66 | allowTerm('INDEX_RENAME');
67 | }
68 | return opts;
69 | };
70 |
71 | var getAllowedDatabaseOpts = function getAllowedDatabaseOpts(opts) {
72 | // Clean options
73 | if (typeof opts.dbs === 'string') opts.dbs = [opts.dbs];
74 | if (typeof opts.tables === 'string') opts.tables = [opts.tables];
75 |
76 | // Create object for dbs
77 | var databases = opts.dbs;
78 | opts.dbs = {
79 | $$count: 0 // `$` not allowed in RethinkDB database names
80 | };
81 | databases.forEach(function (database) {
82 | opts.dbs[database] = { allowed: true, tables: {}, $$count: 0 };
83 | // `$` not allowed in RethinkDB database names
84 | opts.dbs.$$count += 1;
85 | });
86 | opts.tables.forEach(function (tableName) {
87 | if (opts.dbs.$$count > 1) {
88 | var split = tableName.split('.');
89 | if (split.length !== 2) {
90 | var message = 'If more than 1 database is passed, ';
91 | message += 'all table names must be dot (`.`) separated with the table names.';
92 | message += ' `' + tableName + '` is not valid.';
93 | throw new Error(message);
94 | }
95 | if (opts.dbs[split[0]] === undefined) {
96 | var message = 'Database ' + split[0] + ' in ' + tableName + ' was not declared';
97 | throw new Error(message);
98 | }
99 | opts.dbs[split[0]].tables[split[1]] = { allowed: true };
100 | opts.dbs[split[0]].$$count += 1;
101 | }
102 | });
103 | return opts;
104 | };
105 |
106 | var parseLoggingOpts = function parseLoggingOpts(opts) {
107 | // Set default levels
108 | if (opts.logLevel === undefined) opts.logLevel = 'fatal';
109 | if (opts.logLevelIn === undefined) opts.logLevelIn = opts.logLevel;
110 | if (opts.logLevelOut === undefined) opts.logLevelOut = opts.logLevel;
111 | if (opts.logLevelSys === undefined) opts.logLevelSys = opts.logLevel;
112 | var levelsStringToInts = _Object$keys(_loggerJs.levels).reduce(function (keysObj, key) {
113 | var obj = {};
114 | obj[_loggerJs.levels[key]] = key;
115 | return _Object$assign(keysObj, obj);
116 | }, {});
117 | opts.logLevel = levelsStringToInts[opts.logLevel];
118 | opts.logLevelIn = levelsStringToInts[opts.logLevelIn];
119 | opts.logLevelOut = levelsStringToInts[opts.logLevelOut];
120 | opts.logLevelSys = levelsStringToInts[opts.logLevelSys];
121 | return opts;
122 | };
123 |
124 | exports['default'] = function (opts) {
125 | // Define Options and defaults
126 | opts = _Object$assign({
127 | port: 8125,
128 | rdbHost: 'localhost',
129 | rdbPort: 28015,
130 | logLevel: 'fatal',
131 | logLevelIn: 'fatal',
132 | logLevelOut: 'fatal',
133 | logLevelSys: 'fatal',
134 | dbs: [],
135 | tables: [],
136 | allowSysDbAccess: false,
137 | allowWrites: false, // Allow insert, update, delete
138 | allowInsert: false,
139 | allowUpdate: false,
140 | allowReplace: false,
141 | allowDelete: false,
142 | allowDbCreate: false,
143 | allowDbDrop: false,
144 | allowTableCreate: false,
145 | allowTableDrop: false,
146 | allowIndexes: false, // Allow indexCreate, indexDrop, indexRename
147 | allowIndexCreate: false,
148 | allowIndexDrop: false,
149 | allowIndexRename: false,
150 | allowReconfigure: false,
151 | allowRebalance: false,
152 | allowHttp: false,
153 | allowJavascript: false
154 | }, opts);
155 |
156 | // Ensure validity of inputs
157 | assertNumber(opts.port);
158 | assertNumber(opts.rdbPort);
159 | assertString(opts.rdbHost);
160 | assertLevelString(opts.logLevel);
161 | assertLevelString(opts.logLevelIn);
162 | assertLevelString(opts.logLevelOut);
163 | assertLevelString(opts.logLevelSys);
164 | assertStringOrArray(opts.dbs);
165 | assertStringOrArray(opts.tables);
166 |
167 | // Assert booleans
168 | for (var key in opts) {
169 | if (opts.hasOwnProperty(key) && key.substring(0, 5) === 'allow') {
170 | assertBoolean(opts[key]);
171 | }
172 | }
173 |
174 | // Parse logging options
175 | opts = parseLoggingOpts(opts);
176 |
177 | // Get allowed databases
178 | opts = getAllowedDatabaseOpts(opts);
179 |
180 | // Get allowed terms
181 | opts = getAllowedTermsOpts(opts);
182 |
183 | return opts;
184 | };
185 |
186 | module.exports = exports['default'];
--------------------------------------------------------------------------------
/lib/query-parser.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _Number$isInteger = require('babel-runtime/core-js/number/is-integer')['default'];
4 |
5 | var _interopRequireDefault = require('babel-runtime/helpers/interop-require-default')['default'];
6 |
7 | Object.defineProperty(exports, '__esModule', {
8 | value: true
9 | });
10 |
11 | var _lodash = require('lodash');
12 |
13 | var _lodash2 = _interopRequireDefault(_lodash);
14 |
15 | var _rethinkdbProtoDef = require('rethinkdb/proto-def');
16 |
17 | var _rethinkdbProtoDef2 = _interopRequireDefault(_rethinkdbProtoDef);
18 |
19 | var isRQLQuery = function isRQLQuery(query) {
20 | // Duck typing a query...
21 | if (!Array.isArray(query)) return false;
22 | if (query.length < 2 || query.length > 3) return false;
23 | if (!_Number$isInteger(query[0])) return false;
24 | if (query[2] !== undefined && typeof query[2] !== 'object' && !Array.isArray(query[2])) {
25 | return false;
26 | }
27 | return true;
28 | };
29 |
30 | var checkForDeleteInReplace = function checkForDeleteInReplace(terms, command, args, query_opts) {
31 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.REPLACE && !terms.includes('REPLACE') && terms.includes('DELETE')) {
32 | var argPassedToReplace = args[args.length - 1];
33 | if (argPassedToReplace === null) {
34 | return [{
35 | 'error': 'Using the `REPLACE` term with `null` is not allowed if `DELETE` is not also allowed.'
36 | }];
37 | }
38 | }
39 | return [];
40 | };
41 |
42 | var checkForUpdateInInsert = function checkForUpdateInInsert(terms, command, args, query_opts) {
43 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.INSERT && !terms.includes('INSERT') && terms.includes('UPDATE')) {
44 | if (typeof query_opts === 'object' && typeof query_opts.conflict === 'string' && query_opts.conflict.toLowerCase() === 'update') {
45 | return [{
46 | 'error': 'Using the `INSERT` term with `conflict: update` is not allowed if `UPDATE` is not also allowed.'
47 | }];
48 | }
49 | }
50 | return [];
51 | };
52 |
53 | var checkForDeleteInInsert = function checkForDeleteInInsert(terms, command, args, query_opts) {
54 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.INSERT && !terms.includes('INSERT') && terms.includes('REPLACE')) {
55 | if (typeof query_opts === 'object' && typeof query_opts.conflict === 'string' && query_opts.conflict.toLowerCase() === 'replace') {
56 | return [{
57 | 'error': 'Using the `INSERT` term with `conflict: replace` is not allowed if `REPLACE` is not also allowed.'
58 | }];
59 | }
60 | }
61 | return [];
62 | };
63 |
64 | var checkForTableAccess = function checkForTableAccess(opts, connectionDbName, command, args, query_opts) {
65 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.TABLE && typeof opts === 'object') {
66 | var tableName = args[args.length - 1];
67 | var dbName = connectionDbName;
68 | /*!
69 | * Since the name of a table can be passed dynamically and this introducues
70 | * a lot of complexity to determining whether a tables is allowed, only
71 | * allow strings, which cover most cases
72 | */
73 | if (typeof tableName !== 'string') {
74 | return [{
75 | 'error': 'Only strings are allowed for table names while using the `table` command'
76 | }];
77 | }
78 | if (opts.dbs.$$count > 0 && (typeof opts.dbs[dbName] === 'object' && opts.dbs[dbName].allowed)) {
79 | if (opts.dbs[dbName].$$count > 0 && (typeof opts.dbs[dbName].tables[tableName] !== 'object' || !opts.dbs[dbName].tables[tableName].allowed)) {
80 | return [{ 'error': 'Access to the `{$tableName}` table is not allowed.' + ' Table must be declared in the `tables` and' + ' database must be inlcluded in `dbs` parameter'
81 | }];
82 | }
83 | }
84 | }
85 | return [];
86 | };
87 |
88 | var checkForDatabaseAccess = function checkForDatabaseAccess(opts, connectionDbName, command, args, query_opts) {
89 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.DB && typeof opts === 'object') {
90 | var dbName = args[args.length - 1];
91 | /*!
92 | * Since the name of a database can be passed dynamically and this introduces
93 | * a lot of complexity to determining whether a tables is allowed, only
94 | * allow strings, which cover most cases
95 | */
96 | if (typeof dbName !== 'string') {
97 | return [{
98 | 'error': 'Only strings are allowed for database names while using the `db` command'
99 | }];
100 | }
101 | if (!opts.allowSysDbAccess && dbName === 'rethinkdb') {
102 | return [{
103 | 'error': 'Access to the `rethinkdb` database is not allowed unless explicitly stated with `allowSysDbAccess`'
104 | }];
105 | }
106 | if (opts.dbs.$$count > 0 && (typeof opts.dbs[dbName] !== 'object' || !opts.dbs[dbName].allowed)) {
107 | return [{ 'error': 'Access to the `' + dbName + '` database is not allowed. ' + 'Database must be inlcluded in `db` parameter' }];
108 | }
109 | }
110 | return [];
111 | };
112 |
113 | var findTermsOrErrors = function findTermsOrErrors(opts, terms, query) {
114 | var termsFound = [],
115 | connectionDbName = undefined;
116 |
117 | var __findTermsOrErrors = function __findTermsOrErrors(query) {
118 | if (!isRQLQuery(query)) {
119 | if (Array.isArray(query)) {
120 | return _lodash2['default'].flatten(query.map(__findTermsOrErrors)).filter(function (x) {
121 | return x;
122 | });
123 | }
124 | return [];
125 | }
126 | var command = query[0],
127 | args = query[1],
128 | query_opts = query[2];
129 |
130 | /*!
131 | * Edge cases
132 | */
133 | var errorsFound = []
134 | // #1 If `replace` is allowed but `delete` is not
135 | .concat(checkForDeleteInReplace(terms, command, args, query_opts))
136 | // #2 If `insert` is allowed but `update` is not
137 | .concat(checkForUpdateInInsert(terms, command, args, query_opts))
138 | // #3 If `insert` is allowed but `delete` is not
139 | .concat(checkForDeleteInInsert(terms, command, args, query_opts));
140 | if (errorsFound.length > 0) return errorsFound;
141 |
142 | /*!
143 | * Database and table access
144 | */
145 | errorsFound = [].concat(checkForDatabaseAccess(opts, connectionDbName, command, args, query_opts)).concat(checkForTableAccess(opts, connectionDbName, command, args, query_opts));
146 | if (errorsFound.length > 0) return errorsFound;
147 |
148 | /*!
149 | * Check for unallowedTerms
150 | */
151 | for (var key in terms) {
152 | if (terms.hasOwnProperty(key)) {
153 | var termName = terms[key];
154 | if (_rethinkdbProtoDef2['default'].Term.TermType[termName] === command) return termName;
155 | }
156 | }
157 | if (command === _rethinkdbProtoDef2['default'].Term.TermType.MAKE_ARRAY) {
158 | return _lodash2['default'].flatten(query[1].map(__findTermsOrErrors)).filter(function (x) {
159 | return x;
160 | });
161 | }
162 | return _lodash2['default'].flatten(query.map(__findTermsOrErrors)).filter(function (x) {
163 | return x;
164 | });
165 | };
166 |
167 | if (typeof query[2] === 'object' && query[2].db !== undefined) {
168 | connectionDbName = query[2].db[1][0];
169 | termsFound = termsFound.concat(__findTermsOrErrors(query[2].db));
170 | }
171 | return termsFound.concat(__findTermsOrErrors(query));
172 | };
173 | exports.findTermsOrErrors = findTermsOrErrors;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rethinkdb-proxy",
3 | "version": "0.0.8",
4 | "description": "Reverse Proxy for RethinkDB. Make your RethinkDB publicly accessible through limiting what kind of queries can be executed on your RethinkDB database.",
5 | "main": "lib/index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "bin": {
10 | "rethinkdb-proxy": "bin/rethinkdb-proxy.js"
11 | },
12 | "scripts": {
13 | "test:es6": "export NODE_ENV='test_es6' && ./node_modules/mocha/bin/mocha --compilers js:babel/register ./test/index.js",
14 | "test": "export NODE_ENV='test' && npm run compile && ./node_modules/mocha/bin/mocha lib-test/index.js",
15 | "dev": "./node_modules/nodemon/bin/nodemon.js --exec babel-node -- ./src/index.js",
16 | "start": "node index.js",
17 | "compile": "npm run compile:src && npm run compile:test",
18 | "compile:src": "./node_modules/babel/bin/babel.js --optional runtime -d lib src",
19 | "compile:test": "./node_modules/babel/bin/babel.js --optional runtime -d lib-test test",
20 | "prepublish": "npm run compile"
21 | },
22 | "author": "thejsj",
23 | "license": "MIT",
24 | "devDependencies": {
25 | "bluebird": "^2.9.34",
26 | "mocha": "^2.2.5",
27 | "nodemon": "^1.3.7",
28 | "should": "^7.0.2"
29 | },
30 | "dependencies": {
31 | "babel": "^5.6.23",
32 | "babel-runtime": "^5.8.20",
33 | "bunyan": "^1.5.1",
34 | "cli": "^0.8.0",
35 | "lodash": "^3.10.0",
36 | "rethinkdb": "^2.1.0",
37 | "taser": "^1.0.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/buffer-parser.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | const _listeners_ = Symbol('listeners');
3 | const _queue_ = Symbol('queue');
4 |
5 | const BufferParser = class BufferParser {
6 |
7 | constructor () {
8 | this[_queue_] = new Buffer(0);
9 | this[_listeners_] = {};
10 | this.protoVersion = null;
11 | this.authKeyLength = null;
12 | this.authKey = null;
13 | this.protoProtocol = null;
14 | }
15 |
16 | append (buff) {
17 | if (buff !== undefined) {
18 | this[_queue_] = Buffer.concat([this[_queue_], buff]);
19 | }
20 | if (this.protoVersion === null) {
21 | if (Buffer.byteLength(this[_queue_]) >= 4) {
22 | this.protoVersion = this[_queue_].slice(0, 4).readUInt32LE();
23 | this[_queue_] = this[_queue_].slice(4);
24 | // Call code again to ensure we don't have the authKey
25 | this.append();
26 | }
27 | } else if (this.authKeyLength === null) {
28 | if (Buffer.byteLength(this[_queue_]) >= 4) {
29 | this.authKeyLength = this[_queue_].slice(0, 4).readUInt32LE();
30 | this[_queue_] = this[_queue_].slice(4);
31 | // Call code again to ensure we don't have the authKey
32 | this.append();
33 | }
34 | } else if (this.authKey === null) {
35 | if (Buffer.byteLength(this[_queue_] >= this.authKeyLength)) {
36 | this.authKey = this[_queue_].slice(0, this.authKeyLength).toString('ascii');
37 | this[_queue_] = this[_queue_].slice(this.authKeyLength);
38 | // Call code again to ensure we don't have the authKey
39 | this.append();
40 | }
41 | } else if (this.protoProtocol === null) {
42 | if (Buffer.byteLength(this[_queue_]) >= 4) {
43 | this.protoProtocol = this[_queue_].slice(0, 4).readUInt32LE();
44 | this[_queue_] = this[_queue_].slice(4);
45 | // Call code again to ensure we don't have the authKey
46 | this.append();
47 | this.fireEvent('connect', this.protoVersion, this.authKey, this.protoProtocol);
48 | }
49 | } else {
50 | this.parseQuery();
51 | }
52 | }
53 |
54 | fireEvent (eventName, ...data) {
55 | if (this[_listeners_][eventName] !== undefined) {
56 | this[_listeners_][eventName].forEach((func) => {
57 | func.apply(null, data);
58 | });
59 | }
60 | }
61 |
62 | parseQuery () {
63 | /*!
64 | * Assumptions
65 | * 1. All clients have their own instance of BufferParser
66 | * 2. TCP connections send messages in order
67 | * 3. Because messages are sent in order by the client through a TCP connection,
68 | * we can assume that all packets are tokens followed by query bytelenghts
69 | * followed by queries
70 | */
71 | let newQueue = null;
72 | let _prevQuery = null;
73 | /*!
74 | * Make sure we have all three components necessary to parse the query:
75 | * token, byteLength, and query
76 | */
77 | if (Buffer.byteLength(this[_queue_]) <= 12) return;
78 | let token = this[_queue_].slice(0, 8).readUInt32LE();
79 | let byteLength = this[_queue_].slice(8, 12).readUInt32LE();
80 | let query = this[_queue_].slice(12, byteLength + 12);
81 | newQueue = this[_queue_].slice(byteLength + 12);
82 | try {
83 | // Simplest way to handle check if input is valid JSON
84 | let json = JSON.parse(query.toString());
85 | this.fireEvent('query', json, token);
86 | } catch (err) {
87 | // I think the problem has something to do with the fact that I'm adding a
88 | // comma somehwere ....
89 | this.fireEvent('error', token);
90 | }
91 | this[_queue_] = new Buffer(0);
92 | if (newQueue !== null && newQueue.length > 0 && Buffer.byteLength(newQueue) >= 8){
93 | let token = newQueue.slice(0, 8).readUInt32LE();
94 | if (parseInt(token) === +token) {
95 | this.append(newQueue);
96 | }
97 | }
98 | }
99 |
100 | on (eventName, func) {
101 | if (this[_listeners_][eventName] === undefined) {
102 | this[_listeners_][eventName] = [];
103 | }
104 | this[_listeners_][eventName].push(func);
105 | }
106 |
107 | };
108 |
109 | export default BufferParser;
110 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true, bitwise:false */
2 | import 'babel/polyfill';
3 | import net from 'net';
4 | import protoDef from 'rethinkdb/proto-def';
5 | import BufferParser from './buffer-parser';
6 | import { findTermsOrErrors } from './query-parser';
7 | import optionsParser from './options-parser';
8 | import Logger from './logger.js';
9 |
10 | export default class RethinkDBProxy {
11 |
12 | constructor (opts) {
13 | // Set defaults and unallowedTerms
14 | this.opts = optionsParser(opts);
15 | this.logger = new Logger(opts.inLevel, opts.outLevel, opts.sysLevel);
16 | this.__connections = [];
17 | this.server = net.createServer(this.connectionHandler.bind(this));
18 | return this;
19 | }
20 |
21 | listen (cb) {
22 | return new Promise((resolve, reject) => {
23 | this.server.listen(this.opts.port, (err) => {
24 | if (err) {
25 | this.logger.sys.info({ err: err, port: this.opts.port }, 'Error tryingg to listen for traffic');
26 | if (typeof cb === 'function') cb(err);
27 | reject(err);
28 | }
29 | this.logger.sys.info({ port: this.opts.port }, 'server.listen');
30 | if (typeof cb === 'function') cb();
31 | resolve();
32 | });
33 | });
34 | }
35 |
36 | close (cb) {
37 | return new Promise((resolve, reject) => {
38 | this.server.close(() => {
39 | this.logger.sys.info('server.close');
40 | if (typeof cb === 'function') cb();
41 | resolve();
42 | });
43 | setTimeout(() => {
44 | this.logger.sys.info('Server closing all connections');
45 | this.__connections.forEach((conn) => {
46 | this.logger.sys.trace({ conn: conn }, 'conn.destroy');
47 | conn.destroy();
48 | });
49 | }, 100);
50 | });
51 | }
52 |
53 | makeSendResponse (socket) {
54 | return (response, token) => {
55 | this.logger.out.info({
56 | token: token,
57 | response: response
58 | }, 'Send Response');
59 | let tokenBuffer = new Buffer(8);
60 | tokenBuffer.writeUInt32LE(token & 0xFFFFFFFF, 0);
61 | tokenBuffer.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
62 | let responseBuffer = new Buffer(JSON.stringify(response));
63 | let lengthBuffer = new Buffer(4);
64 | lengthBuffer.writeUInt32LE(responseBuffer.length, 0);
65 | if (socket.destroyed) return;
66 | socket.write(tokenBuffer);
67 | socket.write(lengthBuffer);
68 | socket.write(responseBuffer);
69 | };
70 | }
71 |
72 | makeSendError (clientSocket) {
73 | let sendResponseToClient = this.makeSendResponse(clientSocket);
74 | return (token, message) => {
75 | let response = {
76 | t: protoDef.Response.ResponseType.CLIENT_ERROR,
77 | b: [],
78 | n: [],
79 | r: [message]
80 | };
81 | this.logger.out.info({
82 | message: message,
83 | response: response,
84 | token: token
85 | }, 'Send Error');
86 | sendResponseToClient(response, token);
87 | };
88 | }
89 |
90 | connectionHandler (clientSocket) {
91 |
92 | this.__connections.push(clientSocket);
93 |
94 | clientSocket.connected = false;
95 | let parser = new BufferParser();
96 | let serverSocket = net.connect(this.opts.rdbPort, this.opts.rdbHost);
97 | let sendResponseToServer = this.makeSendResponse(serverSocket);
98 | let sendResponseToClient = this.makeSendResponse(clientSocket);
99 | let sendError = this.makeSendError(clientSocket);
100 |
101 | /*!
102 | * Listeners
103 | */
104 | serverSocket.on('data', (buff) => {
105 | this.logger.out.info({
106 | buffer: buff.toString(),
107 | }, 'Data received');
108 | if (buff.toString() === 'SUCCESS') clientSocket.connected = true;
109 | // NOTE: The socket might try to write something even if the connection
110 | // is closed
111 | if (clientSocket.destroyed) return;
112 | clientSocket.write(buff);
113 | });
114 |
115 | parser.on('connect', (version, authKey, protoProtocol) => {
116 | this.logger.in.info({
117 | version: version,
118 | authKey: authKey,
119 | protoProtocol: protoProtocol
120 | }, 'parser.on.connect');
121 | let versionBuffer = new Buffer(4);
122 | versionBuffer.writeUInt32LE(version, 0);
123 | let authBuffer = new Buffer(authKey, 'ascii');
124 | let authLengthBuffer = new Buffer(4);
125 | authLengthBuffer.writeUInt32LE(authBuffer.length, 0);
126 | let protocolBuffer = new Buffer(4);
127 | protocolBuffer.writeUInt32LE(protoProtocol, 0);
128 | let token = Buffer.concat([versionBuffer, authLengthBuffer, authBuffer, protocolBuffer]);
129 | if (protoProtocol !== protoDef.VersionDummy.Protocol.JSON) {
130 | sendError(token, 'Proxy Error: Only JSON protocol allowed.');
131 | }
132 | serverSocket.write(token);
133 | });
134 |
135 | parser.on('error', (token) => {
136 | this.logger.in.error({
137 | token: token,
138 | }, 'parser.on.error');
139 | sendError('Proxy Error: Could not parse query correctly.');
140 | });
141 |
142 | parser.on('query', (query, token) => {
143 | this.logger.in.info({
144 | query: query,
145 | token: token,
146 | }, 'parser.on.query');
147 | let termsFound = findTermsOrErrors(this.opts, this.opts.unallowedTerms, query);
148 | if (termsFound.length > 0) {
149 | // This shouldn't throw an error. It should
150 | // send the error through the TCP connection
151 | let errorMessage;
152 | if (typeof termsFound[0] === 'object' && typeof termsFound[0].error === 'string') {
153 | errorMessage = termsFound[0].error;
154 | } else {
155 | errorMessage = 'Cannot execute query. \"' + termsFound + '\" query not allowed.';
156 | }
157 | this.logger.in.warn({
158 | termsFound: termsFound,
159 | errorMessage: errorMessage,
160 | }, 'parser.on.query Error');
161 | sendError(token, errorMessage);
162 | }
163 | return sendResponseToServer(query, token);
164 | });
165 |
166 | clientSocket.on('data', (data) => {
167 | this.logger.in.trace({
168 | data: data,
169 | }, 'clientSocket.on.data');
170 | return parser.append(data);
171 | });
172 | }
173 | }
174 |
175 |
--------------------------------------------------------------------------------
/src/logger.js:
--------------------------------------------------------------------------------
1 | import bunyan from 'bunyan';
2 |
3 | const _log_ = Symbol('log');
4 |
5 | export const levels = {
6 | 10: 'trace',
7 | 20: 'debug',
8 | 30: 'info',
9 | 40: 'warn',
10 | 50: 'error',
11 | 60: 'fatal'
12 | };
13 |
14 | class LevelLogger {
15 |
16 | constructor (globalLevel, logger) {
17 | this[_log_] = logger;
18 | // "fatal" (60): The service/app is going to stop or become unusable now.
19 | // An operator should definitely look into this soon.
20 | // "error" (50): Fatal for a particular request, but the service/app continues servicing other requests.
21 | // An operator should look at this soon(ish).
22 | // "warn" (40): A note on something that should probably be looked at by an operator eventually.
23 | // "info" (30): Detail on regular operation.
24 | // "debug" (20): Anything else, i.e. too verbose to be included in "info" level.
25 | // "trace" (10): Logging from external libraries used by your app or very detailed application logging.
26 | Object.keys(levels).forEach(levelKey => {
27 | this[levelKey] = this.log(globalLevel, levelKey, levels[levelKey]);
28 | this[levels[levelKey]] = this.log(globalLevel, levelKey, levels[levelKey]);
29 | });
30 | }
31 |
32 | log (globalLevel, level, levelName) {
33 | return (...args) => {
34 | this[_log_][levelName].apply(this[_log_], args);
35 | };
36 | }
37 | }
38 |
39 | export default class Logger {
40 |
41 | constructor (inLevel, outLevel, sysLevel) {
42 | let logger = bunyan.createLogger({ name: 'rethinkdb-proxy'});
43 | this.in = new LevelLogger(inLevel, logger);
44 | this.out = new LevelLogger(outLevel, logger);
45 | this.sys = new LevelLogger(sysLevel, logger);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/options-parser.js:
--------------------------------------------------------------------------------
1 | import taser from 'taser';
2 | import { levels } from './logger.js';
3 |
4 | const assertStringOrArray = taser(['string', 'array']);
5 | const assertBoolean = taser(['boolean']);
6 | const assertNumber = taser(['number']);
7 | const assertString = taser(['string']);
8 | const assertLevelString = taser({
9 | type: 'string',
10 | values: (() => {
11 | return Object.keys(levels).map(levelKey => levels[levelKey]);
12 | })()
13 | });
14 |
15 | let getAllowedTermsOpts = (opts) => {
16 | // By default, don't allow any of these terms
17 | opts.unallowedTerms = [
18 | 'INSERT',
19 | 'UPDATE',
20 | 'REPLACE',
21 | 'DELETE',
22 | 'DB_CREATE',
23 | 'DB_DROP',
24 | 'TABLE_CREATE',
25 | 'TABLE_DROP',
26 | 'INDEX_CREATE',
27 | 'INDEX_DROP',
28 | 'INDEX_RENAME',
29 | 'RECONFIGURE',
30 | 'REBALANCE',
31 | 'HTTP',
32 | 'JAVASCRIPT'
33 | ];
34 |
35 | let toUpperCaseSnakeCase = (str) => {
36 | return str
37 | .replace(/(^[A-Z])/g, ($1) => { return $1.toLowerCase(); })
38 | .replace(/([A-Z])/g, ($1) => { return '_'+$1.toUpperCase(); })
39 | .toUpperCase();
40 | };
41 | let allowTerm = (termName) => {
42 | if (opts.unallowedTerms.includes(termName)) {
43 | opts.unallowedTerms.splice(opts.unallowedTerms.indexOf(termName), 1);
44 | }
45 | };
46 | // Allow all terms specified by user (single terms)
47 | for(let key in opts) {
48 | if (opts.hasOwnProperty(key)) {
49 | if (opts[key] === true && key.substring(0, 5) === 'allow') {
50 | let termName = toUpperCaseSnakeCase(key.substring(5));
51 | allowTerm(termName);
52 | }
53 | }
54 | }
55 | // Allow all terms specified (multiple terms)
56 | if (opts.allowWrites) {
57 | allowTerm('INSERT');
58 | allowTerm('UPDATE');
59 | allowTerm('DELETE');
60 | }
61 | if (opts.allowIndexes) {
62 | allowTerm('INDEX_CREATE');
63 | allowTerm('INDEX_DROP');
64 | allowTerm('INDEX_RENAME');
65 | }
66 | return opts;
67 | };
68 |
69 | let getAllowedDatabaseOpts = (opts) => {
70 | // Clean options
71 | if (typeof opts.dbs === 'string') opts.dbs = [opts.dbs];
72 | if (typeof opts.tables === 'string') opts.tables = [opts.tables];
73 |
74 | // Create object for dbs
75 | let databases = opts.dbs;
76 | opts.dbs = {
77 | $$count: 0 // `$` not allowed in RethinkDB database names
78 | };
79 | databases.forEach(function (database) {
80 | opts.dbs[database] = { allowed: true, tables: { }, $$count: 0 };
81 | // `$` not allowed in RethinkDB database names
82 | opts.dbs.$$count += 1;
83 | });
84 | opts.tables.forEach(function (tableName) {
85 | if (opts.dbs.$$count > 1) {
86 | let split = tableName.split('.');
87 | if (split.length !== 2) {
88 | let message = `If more than 1 database is passed, `;
89 | message += `all table names must be dot (\`.\`) separated with the table names.`;
90 | message += ` \`${tableName}\` is not valid.`;
91 | throw new Error(message);
92 | }
93 | if (opts.dbs[split[0]] === undefined) {
94 | let message = `Database ${split[0]} in ${tableName} was not declared`;
95 | throw new Error(message);
96 | }
97 | opts.dbs[split[0]].tables[split[1]] = { allowed: true };
98 | opts.dbs[split[0]].$$count += 1;
99 | }
100 | });
101 | return opts;
102 | };
103 |
104 | let parseLoggingOpts = (opts) => {
105 | // Set default levels
106 | if (opts.logLevel === undefined) opts.logLevel = 'fatal';
107 | if (opts.logLevelIn === undefined) opts.logLevelIn = opts.logLevel;
108 | if (opts.logLevelOut === undefined) opts.logLevelOut = opts.logLevel;
109 | if (opts.logLevelSys === undefined) opts.logLevelSys = opts.logLevel;
110 | let levelsStringToInts = Object.keys(levels).reduce((keysObj, key) => {
111 | let obj = {};
112 | obj[levels[key]] = key;
113 | return Object.assign(keysObj, obj);
114 | }, {});
115 | opts.logLevel = levelsStringToInts[opts.logLevel];
116 | opts.logLevelIn = levelsStringToInts[opts.logLevelIn];
117 | opts.logLevelOut = levelsStringToInts[opts.logLevelOut];
118 | opts.logLevelSys = levelsStringToInts[opts.logLevelSys];
119 | return opts;
120 | };
121 |
122 | export default (opts) => {
123 | // Define Options and defaults
124 | opts = Object.assign({
125 | port: 8125,
126 | rdbHost: 'localhost',
127 | rdbPort: 28015,
128 | logLevel: 'fatal',
129 | logLevelIn: 'fatal',
130 | logLevelOut: 'fatal',
131 | logLevelSys: 'fatal',
132 | dbs: [],
133 | tables: [],
134 | allowSysDbAccess: false,
135 | allowWrites: false, // Allow insert, update, delete
136 | allowInsert: false,
137 | allowUpdate: false,
138 | allowReplace: false,
139 | allowDelete: false,
140 | allowDbCreate: false,
141 | allowDbDrop: false,
142 | allowTableCreate: false,
143 | allowTableDrop: false,
144 | allowIndexes: false, // Allow indexCreate, indexDrop, indexRename
145 | allowIndexCreate: false,
146 | allowIndexDrop: false,
147 | allowIndexRename: false,
148 | allowReconfigure: false,
149 | allowRebalance: false,
150 | allowHttp: false,
151 | allowJavascript: false
152 | }, opts);
153 |
154 | // Ensure validity of inputs
155 | assertNumber(opts.port);
156 | assertNumber(opts.rdbPort);
157 | assertString(opts.rdbHost);
158 | assertLevelString(opts.logLevel);
159 | assertLevelString(opts.logLevelIn);
160 | assertLevelString(opts.logLevelOut);
161 | assertLevelString(opts.logLevelSys);
162 | assertStringOrArray(opts.dbs);
163 | assertStringOrArray(opts.tables);
164 |
165 | // Assert booleans
166 | for (let key in opts) {
167 | if (opts.hasOwnProperty(key) && key.substring(0, 5) === 'allow') {
168 | assertBoolean(opts[key]);
169 | }
170 | }
171 |
172 | // Parse logging options
173 | opts = parseLoggingOpts(opts);
174 |
175 | // Get allowed databases
176 | opts = getAllowedDatabaseOpts(opts);
177 |
178 | // Get allowed terms
179 | opts = getAllowedTermsOpts(opts);
180 |
181 | return opts;
182 | };
183 |
--------------------------------------------------------------------------------
/src/query-parser.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import protoDef from 'rethinkdb/proto-def';
3 |
4 | let isRQLQuery = (query) => {
5 | // Duck typing a query...
6 | if (!Array.isArray(query)) return false;
7 | if (query.length < 2 || query.length > 3) return false;
8 | if (!Number.isInteger(query[0])) return false;
9 | if (query[2] !== undefined && typeof query[2] !== 'object' && !Array.isArray(query[2])) {
10 | return false;
11 | }
12 | return true;
13 | };
14 |
15 | let checkForDeleteInReplace = function (terms, command, args, query_opts) {
16 | if (
17 | command === protoDef.Term.TermType.REPLACE &&
18 | !terms.includes('REPLACE') &&
19 | terms.includes('DELETE')
20 | ){
21 | let argPassedToReplace = args[args.length - 1];
22 | if (argPassedToReplace === null) {
23 | return [{
24 | 'error': 'Using the `REPLACE` term with `null` is not allowed if `DELETE` is not also allowed.'
25 | }];
26 | }
27 | }
28 | return [];
29 | };
30 |
31 | let checkForUpdateInInsert = function (terms, command, args, query_opts) {
32 | if (
33 | command === protoDef.Term.TermType.INSERT &&
34 | !terms.includes('INSERT') &&
35 | terms.includes('UPDATE')
36 | ){
37 | if (
38 | typeof query_opts === 'object' &&
39 | typeof query_opts.conflict === 'string' &&
40 | query_opts.conflict.toLowerCase() === 'update'
41 | ) {
42 | return [{
43 | 'error': 'Using the `INSERT` term with `conflict: update` is not allowed if `UPDATE` is not also allowed.'
44 | }];
45 | }
46 | }
47 | return [];
48 | };
49 |
50 | let checkForDeleteInInsert = function (terms, command, args, query_opts) {
51 | if (
52 | command === protoDef.Term.TermType.INSERT &&
53 | !terms.includes('INSERT') &&
54 | terms.includes('REPLACE')
55 | ){
56 | if (
57 | typeof query_opts === 'object' &&
58 | typeof query_opts.conflict === 'string' &&
59 | query_opts.conflict.toLowerCase() === 'replace'
60 | ) {
61 | return [{
62 | 'error': 'Using the `INSERT` term with `conflict: replace` is not allowed if `REPLACE` is not also allowed.'
63 | }];
64 | }
65 | }
66 | return [];
67 | };
68 |
69 | let checkForTableAccess = function (opts, connectionDbName, command, args, query_opts) {
70 | if (command === protoDef.Term.TermType.TABLE && typeof opts === 'object') {
71 | let tableName = args[args.length - 1];
72 | let dbName = connectionDbName;
73 | /*!
74 | * Since the name of a table can be passed dynamically and this introducues
75 | * a lot of complexity to determining whether a tables is allowed, only
76 | * allow strings, which cover most cases
77 | */
78 | if (typeof tableName !== 'string') {
79 | return [{
80 | 'error': `Only strings are allowed for table names while using the \`table\` command`
81 | }];
82 | }
83 | if (opts.dbs.$$count > 0 && (typeof opts.dbs[dbName] === 'object' && opts.dbs[dbName].allowed)) {
84 | if (
85 | opts.dbs[dbName].$$count > 0 &&
86 | (typeof opts.dbs[dbName].tables[tableName] !== 'object' ||
87 | !opts.dbs[dbName].tables[tableName].allowed)
88 | ) {
89 | return [{ 'error': `Access to the \`{$tableName}\` table is not allowed.` +
90 | ` Table must be declared in the \`tables\` and` +
91 | ` database must be inlcluded in \`dbs\` parameter`
92 | }];
93 | }
94 | }
95 | }
96 | return [];
97 | };
98 |
99 | let checkForDatabaseAccess = function (opts, connectionDbName, command, args, query_opts) {
100 | if (command === protoDef.Term.TermType.DB && typeof opts === 'object') {
101 | let dbName = args[args.length - 1];
102 | /*!
103 | * Since the name of a database can be passed dynamically and this introduces
104 | * a lot of complexity to determining whether a tables is allowed, only
105 | * allow strings, which cover most cases
106 | */
107 | if (typeof dbName !== 'string') {
108 | return [{
109 | 'error': `Only strings are allowed for database names while using the \`db\` command`
110 | }];
111 | }
112 | if (!opts.allowSysDbAccess && dbName === 'rethinkdb') {
113 | return [{
114 | 'error': 'Access to the `rethinkdb` database is not allowed unless explicitly stated with `allowSysDbAccess`'
115 | }];
116 | }
117 | if (opts.dbs.$$count > 0 && (typeof opts.dbs[dbName] !== 'object' || !opts.dbs[dbName].allowed)) {
118 | return [{ 'error': `Access to the \`${dbName}\` database is not allowed. ` +
119 | `Database must be inlcluded in \`db\` parameter` }];
120 | }
121 | }
122 | return [];
123 | };
124 |
125 | export const findTermsOrErrors = (opts, terms, query) => {
126 | let termsFound = [], connectionDbName;
127 |
128 | const __findTermsOrErrors = (query) => {
129 | if (!isRQLQuery(query)) {
130 | if (Array.isArray(query)) {
131 | return _.flatten(query.map(__findTermsOrErrors)).filter(x => x);
132 | }
133 | return [];
134 | }
135 | let command = query[0], args = query[1], query_opts = query[2];
136 |
137 | /*!
138 | * Edge cases
139 | */
140 | let errorsFound = []
141 | // #1 If `replace` is allowed but `delete` is not
142 | .concat(checkForDeleteInReplace(terms, command, args, query_opts))
143 | // #2 If `insert` is allowed but `update` is not
144 | .concat(checkForUpdateInInsert(terms, command, args, query_opts))
145 | // #3 If `insert` is allowed but `delete` is not
146 | .concat(checkForDeleteInInsert(terms, command, args, query_opts));
147 | if (errorsFound.length > 0) return errorsFound;
148 |
149 | /*!
150 | * Database and table access
151 | */
152 | errorsFound = []
153 | .concat(checkForDatabaseAccess(opts, connectionDbName, command, args, query_opts))
154 | .concat(checkForTableAccess(opts, connectionDbName, command, args, query_opts));
155 | if (errorsFound.length > 0) return errorsFound;
156 |
157 | /*!
158 | * Check for unallowedTerms
159 | */
160 | for (let key in terms) {
161 | if (terms.hasOwnProperty(key)) {
162 | let termName = terms[key];
163 | if(protoDef.Term.TermType[termName] === command) return termName;
164 | }
165 | }
166 | if (command === protoDef.Term.TermType.MAKE_ARRAY) {
167 | return _.flatten(query[1].map(__findTermsOrErrors)).filter(x => x);
168 | }
169 | return _.flatten(query.map(__findTermsOrErrors)).filter(x => x);
170 | };
171 |
172 | if (typeof query[2] === 'object' && query[2].db !== undefined) {
173 | connectionDbName = query[2].db[1][0];
174 | termsFound = termsFound.concat(__findTermsOrErrors(query[2].db));
175 | }
176 | return termsFound.concat(__findTermsOrErrors(query));
177 | };
178 |
179 |
180 |
--------------------------------------------------------------------------------
/test/changefeeds.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeExecuteProxyQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 | import protoDef from '../node_modules/rethinkdb/proto-def';
8 |
9 | let proxyPort = 8125;
10 | let dbName = 'rethinkdb_proxy_test';
11 | let secondDbName = 'rethinkdb_proxy_test_2';
12 | let tableName = 'entries';
13 | let server;
14 |
15 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
16 | let executeProxyQuery = makeExecuteProxyQuery(dbName, proxyPort);
17 | let assertQuery = makeAssertQuery(executeQuery);
18 | let createDatabase = makeCreateDatabase(dbName, tableName);
19 | let createSecondDatabase = makeCreateDatabase(secondDbName, tableName);
20 | let dropDatabase = makeDropDatabase(dbName);
21 | let throwError = function (res) { throw new Error(); };
22 | let expectError = function (errorName, errorMessageMatch, err) {
23 | if (errorName !== null) errorName.should.equal(err.name);
24 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
25 | (err instanceof Error).should.equal(true);
26 | };
27 | let closeCursorAndConn = function (cursor, conn) {
28 | return cursor.close()
29 | .then(function () {
30 | return conn.close();
31 | });
32 | };
33 | let ce = function (err) {
34 | console.log('Error');
35 | console.log(err);
36 | throw err;
37 | };
38 |
39 | describe('Changefeeds', () => {
40 |
41 | before(function (done) {
42 | this.timeout(5000);
43 | createDatabase()
44 | .then(() => {
45 | return new Promise(function (resolve, reject) {
46 | server = new RethinkDBProxy({
47 | port: proxyPort,
48 | allowInsert: true
49 | });
50 | server.listen(resolve);
51 | });
52 | })
53 | .nodeify(done);
54 | });
55 |
56 | after((done) => {
57 | dropDatabase()
58 | .then(server.close.bind(server))
59 | .then(done.bind(null, null));
60 | });
61 |
62 | it('should listen for changes', function (done) {
63 | this.timeout(10000);
64 | var count = 0, results = [], cursor;
65 | // HINT: You'll need to pass an anonymous function to `filter`
66 | r.connect({ port: proxyPort, db: dbName }).then((conn) => {
67 | return r.table(tableName).changes().run(conn)
68 | .then(function (_cursor) {
69 | cursor = _cursor;
70 | cursor.each(function (err, row) {
71 | count += 1;
72 | results.push(row);
73 | });
74 | })
75 | .then(function () {
76 | return r.connect({ port: proxyPort, db: dbName }).then((conn) => {
77 | return Promise.all([
78 | r.table(tableName).insert({ name: 'SomeCountryName', population: 1 }).run(conn).catch(ce),
79 | r.table(tableName).insert({ name: 'Transylvania', population: 2000 }).run(conn).catch(ce),
80 | r.table(tableName).insert({ name: 'Hong Kong', population: 1500 }).run(conn),
81 | r.table(tableName).insert({ name: 'Bavaira', population: 98 }).run(conn),
82 | ])
83 | .then(conn.close.bind(conn));
84 | });
85 | })
86 | .then(function () {
87 | return new Promise(function (resolve, reject) {
88 | setTimeout(function () {
89 | count.should.equal(4);
90 | results.should.be.instanceOf(Array);
91 | results = results.sort((a, b) => a.new_val.population - b.new_val.population);
92 | results[0].new_val.population.should.equal(1);
93 | results[1].new_val.population.should.equal(98);
94 | results[0].new_val.should.have.property('name');
95 | results[1].new_val.should.have.property('name');
96 | resolve();
97 | }, 50); // Hopefully, this is enough
98 | });
99 | })
100 | .then(function () {
101 | return closeCursorAndConn(cursor, conn);
102 | })
103 | .nodeify(done);
104 | });
105 | });
106 |
107 | it('should listen for multiple changesfeeds', function (done) {
108 | this.timeout(10000);
109 | var first_count = 0, second_count = 0, results = [], cursor;
110 | return Promise.all([
111 | r.connect({ port: proxyPort, db: dbName }).then((conn) => {
112 | return r.table(tableName).filter(r.row('population').lt(500)).changes().run(conn)
113 | .then(function (cursor) {
114 | cursor.each(function (err, row) {
115 | first_count += 1;
116 | results.push(row);
117 | }, conn.close.bind(conn));
118 | });
119 | }),
120 | r.connect({ port: proxyPort, db: dbName }).then((conn) => {
121 | return r.table(tableName).filter(r.row('population').gt(500)).changes().run(conn)
122 | .then(function (cursor) {
123 | cursor.each(function (err, row) {
124 | second_count += 1;
125 | results.push(row);
126 | }, conn.close.bind(conn));
127 | });
128 | })
129 | ])
130 | .then(function () {
131 | return r.connect({ port: proxyPort, db: dbName }).then((conn) => {
132 | return Promise.all([
133 | r.table(tableName).insert({ name: 'SomeCountryName', population: 1 }).run(conn).catch(ce),
134 | r.table(tableName).insert({ name: 'Transylvania', population: 2000 }).run(conn).catch(ce),
135 | r.table(tableName).insert({ name: 'Hong Kong', population: 1500 }).run(conn),
136 | r.table(tableName).insert({ name: 'Bavaira', population: 98 }).run(conn),
137 | ])
138 | .then(conn.close.bind(conn));
139 | });
140 | })
141 | .then(function () {
142 | return new Promise(function (resolve, reject) {
143 | setTimeout(function () {
144 | first_count.should.equal(2);
145 | second_count.should.equal(2);
146 | results.should.be.instanceOf(Array);
147 | results = results.sort((a, b) => a.new_val.population - b.new_val.population);
148 | results[0].new_val.population.should.equal(1);
149 | results[1].new_val.population.should.equal(98);
150 | results[0].new_val.should.have.property('name');
151 | results[1].new_val.should.have.property('name');
152 | resolve();
153 | }, 50); // Hopefully, this is enough
154 | });
155 | })
156 | .nodeify(done);
157 | });
158 |
159 | });
160 |
--------------------------------------------------------------------------------
/test/components/buffer-parser.js:
--------------------------------------------------------------------------------
1 | var BufferParser;
2 | if (process.env.NODE_ENV === 'test_es6') {
3 | // Called from /test
4 | var BufferParserES6 = require('../../src/buffer-parser');
5 | BufferParser = BufferParserES6;
6 | } else {
7 | // Called from /lib/test
8 | var BufferParserES5 = require('../../lib/buffer-parser');
9 | BufferParser = BufferParserES5;
10 | }
11 | export default BufferParser;
12 |
--------------------------------------------------------------------------------
/test/components/rethinkdb-proxy.js:
--------------------------------------------------------------------------------
1 | var RethinkDBProxy;
2 | if (process.env.NODE_ENV === 'test_es6') {
3 | // Called from /test
4 | var RethinkDBProxyES6 = require('../../src');
5 | RethinkDBProxy = RethinkDBProxyES6;
6 | } else {
7 | // Called from /lib/test
8 | var RethinkDBProxyES5 = require('../../lib');
9 | RethinkDBProxy = RethinkDBProxyES5;
10 | }
11 | export default RethinkDBProxy;
12 |
--------------------------------------------------------------------------------
/test/connection.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 |
7 | let proxyPort = 8125;
8 | let server;
9 |
10 | describe('Connection', () => {
11 |
12 | before((done) => {
13 | server = new RethinkDBProxy({
14 | port: proxyPort
15 | });
16 | server.listen(done);
17 | });
18 |
19 | after((done) => {
20 | server.close().then(done.bind(null, null));
21 | });
22 |
23 | it('should create a connection successfully', (done) => {
24 | Promise.resolve()
25 | .then(() => {
26 | return [r.connect(), r.connect({ port: proxyPort })];
27 | })
28 | .spread((connA, connB) => {
29 | connA.port.should.equal(28015);
30 | connB.port.should.equal(proxyPort);
31 | connA.constructor.should.equal(connB.constructor);
32 | done();
33 | });
34 | });
35 |
36 | });
37 |
--------------------------------------------------------------------------------
/test/database-and-table-access.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeExecuteProxyQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 | import protoDef from '../node_modules/rethinkdb/proto-def';
8 |
9 | let proxyPort = 8125;
10 | let dbName = 'rethinkdb_proxy_test';
11 | let secondDbName = 'rethinkdb_proxy_test_2';
12 | let tableName = 'entries';
13 | let server;
14 |
15 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
16 | let executeProxyQuery = makeExecuteProxyQuery(dbName, proxyPort);
17 | let assertQuery = makeAssertQuery(executeQuery);
18 | let createDatabase = makeCreateDatabase(dbName, tableName);
19 | let createSecondDatabase = makeCreateDatabase(secondDbName, tableName);
20 | let dropDatabase = makeDropDatabase(dbName);
21 | let throwError = function (res) { throw new Error(); };
22 | let expectError = function (errorName, errorMessageMatch, err) {
23 | if (errorName !== null) errorName.should.equal(err.name);
24 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
25 | (err instanceof Error).should.equal(true);
26 | };
27 |
28 | describe('Database and Table Access', () => {
29 |
30 | let get = r.db(dbName).table(tableName);
31 |
32 | describe('allowSysDbAccess', () => {
33 |
34 | describe('Disallow RethinkDB Database Access', () => {
35 |
36 | before(function (done) {
37 | this.timeout(10000);
38 | createDatabase()
39 | .then(() => {
40 | return new Promise(function (resolve, reject) {
41 | server = new RethinkDBProxy({
42 | port: proxyPort,
43 | });
44 | server.listen(resolve);
45 | });
46 | })
47 | .nodeify(done);
48 | });
49 |
50 | after((done) => {
51 | dropDatabase()
52 | .then(server.close.bind(server))
53 | .then(done.bind(null, null));
54 | });
55 |
56 | it('should not allow a query that uses the `rethinkdb` database', (done) => {
57 | executeQuery(r.db('rethinkdb').tableList())
58 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
59 | .nodeify(done);
60 | });
61 |
62 | it('should not allow a query that uses the `rethinkdb` database inside a `do`', (done) => {
63 | executeQuery(r.expr(true).do(function () {
64 | return r.db('rethinkdb').tableList();
65 | }))
66 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
67 | .nodeify(done);
68 | });
69 |
70 | it('should not allow a query that uses the `rethinkdb` database inside a `do` inside a `do`', (done) => {
71 | executeQuery(r.expr([1, 2, 3]).do(function (arr) {
72 | return r.expr(arr).do(function () {
73 | return r.db('rethinkdb').tableList();
74 | });
75 | }))
76 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
77 | .nodeify(done);
78 | });
79 |
80 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', (done) => {
81 | executeQuery(r.db('rethinkdb').table('server_config'))
82 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
83 | .nodeify(done);
84 | });
85 |
86 | });
87 |
88 | describe('Allow RethinkDB Database Access', () => {
89 |
90 | before(function (done) {
91 | this.timeout(10000);
92 | createDatabase()
93 | .then(() => {
94 | return new Promise(function (resolve, reject) {
95 | server = new RethinkDBProxy({
96 | port: proxyPort,
97 | allowSysDbAccess: true
98 | });
99 | server.listen(resolve);
100 | });
101 | })
102 | .nodeify(done);
103 | });
104 |
105 | after((done) => {
106 | dropDatabase()
107 | .then(server.close.bind(server))
108 | .then(done.bind(null, null));
109 | });
110 |
111 | it('should allow a query that uses the `rethinkdb` database, if explicitly set', (done) => {
112 | executeQuery(r.db('rethinkdb').tableList())
113 | .then(function (res) {
114 | Array.isArray(res).should.equal(true);
115 | res.length.should.not.be.equal(0);
116 | })
117 | .nodeify(done);
118 | });
119 |
120 | it('should allow a query queries the `rehtinkdb` database if explicitly set', (done) => {
121 | executeQuery(r.db('rethinkdb').table('server_config').coerceTo('array'))
122 | .then(function (res) {
123 | Array.isArray(res).should.equal(true);
124 | res.length.should.not.be.equal(0);
125 | })
126 | .nodeify(done);
127 | });
128 | });
129 |
130 | });
131 |
132 | describe('Database Access', () => {
133 | before(function (done) {
134 | this.timeout(10000);
135 | createDatabase()
136 | .then(createSecondDatabase)
137 | .then(() => {
138 | return new Promise(function (resolve, reject) {
139 | server = new RethinkDBProxy({
140 | port: proxyPort,
141 | dbs: [dbName, 'someOtherDb'],
142 | });
143 | server.listen(resolve);
144 | });
145 | })
146 | .nodeify(done);
147 | });
148 |
149 | after((done) => {
150 | dropDatabase()
151 | .then(server.close.bind(server))
152 | .then(done.bind(null, null));
153 | });
154 |
155 |
156 | it('should not allow access to a database that was not allowed', (done) => {
157 | executeProxyQuery(r.db(secondDbName).tableList())
158 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
159 | .nodeify(done);
160 | });
161 |
162 | it('should not allow access to a database that was not allowed inside a `do`', (done) => {
163 | executeProxyQuery(r.expr(1).do(function () {
164 | return r.db(secondDbName).tableList();
165 | }))
166 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
167 | .nodeify(done);
168 | });
169 |
170 | it('should not allow access to a database that was not allowed inside a `do`', (done) => {
171 | executeProxyQuery(r.expr(true).do(function () {
172 | return [r.db(secondDbName).tableList(), 4, 5, 9];
173 | }))
174 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
175 | .nodeify(done);
176 | });
177 |
178 | it('should allow access to a database that is allowed', (done) => {
179 | executeProxyQuery(r.db(dbName).tableList())
180 | .then(function (list) {
181 | list.should.be.instanceof(Array);
182 | })
183 | .nodeify(done);
184 | });
185 |
186 | describe('Connection', () => {
187 | it('should throw an error when trying to connect with an unallowed database', (done) => {
188 | r.connect({ port: proxyPort, db: secondDbName })
189 | .then(function (conn) {
190 | return r.tableList().run(conn);
191 | })
192 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
193 | .nodeify(done);
194 | });
195 | });
196 |
197 | describe('Query arguments', () => {
198 |
199 | it('should not allow a database name to be passed through r.args', function (done) {
200 | // For some reason, using `executeProxyQuery` doesn't work...
201 | r.connect({ port: proxyPort }).then(function (conn) {
202 | return r.db(r.args([dbName])).tableList().run(conn)
203 | .then(conn.close.bind(conn));
204 | })
205 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
206 | .nodeify(done);
207 | });
208 |
209 | it('should allow a database name to be passed through r.expr', (done) => {
210 | executeProxyQuery(r.db(r.expr(dbName)).tableList())
211 | .then(function (list) {
212 | list.should.be.instanceof(Array);
213 | })
214 | .nodeify(done);
215 | });
216 |
217 | });
218 |
219 | });
220 |
221 | describe('Table Access', () => {
222 |
223 | before(function (done) {
224 | this.timeout(10000);
225 | createDatabase()
226 | .then(createSecondDatabase)
227 | .then(() => {
228 | return new Promise(function (resolve, reject) {
229 | server = new RethinkDBProxy({
230 | port: proxyPort,
231 | dbs: [dbName, 'someOtherDb'],
232 | tables: dbName + '.' + tableName
233 | });
234 | server.listen(resolve);
235 | });
236 | })
237 | .nodeify(done);
238 | });
239 |
240 | after((done) => {
241 | dropDatabase()
242 | .then(server.close.bind(server))
243 | .then(done.bind(null, null));
244 | });
245 |
246 | it('should not allow access to a table if not allowed', function (done) {
247 | this.timeout(5000);
248 | r.connect({ db: dbName }).then(function (conn) {
249 | return r.tableCreate('someOtherTable').run(conn)
250 | .then(function () {
251 | return conn.close();
252 | });
253 | })
254 | .then(function () {
255 | return executeProxyQuery(r.db(dbName).table('someOtherTable'))
256 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i))
257 | .nodeify(done);
258 | });
259 | });
260 |
261 | it('should allow access to a table if allowed', (done) => {
262 | executeProxyQuery(r.db(dbName).table(tableName))
263 | .then(function (list) {
264 | list.should.be.instanceof(Array);
265 | })
266 | .nodeify(done);
267 | });
268 |
269 | describe('Connection', () => {
270 | it('should not throw an error when trying query a table with an unallowed default database', (done) => {
271 | r.connect({ port: proxyPort, db: 'someOtherDbNotAllowed' })
272 | .then(function (conn) {
273 | return r.table(tableName).run(conn);
274 | })
275 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DATABASE/i))
276 | .nodeify(done);
277 | });
278 |
279 | it('should not throw an error when trying query a table with an unallowed table', (done) => {
280 | r.connect({ port: proxyPort, db: dbName })
281 | .then(function (conn) {
282 | return r.table(tableName).coerceTo('array').run(conn);
283 | })
284 | .then(function (list) {
285 | return list.should.be.instanceof(Array);
286 | })
287 | .nodeify(done);
288 | });
289 |
290 | it('should throw an error when trying query a table with an unallowed default database', (done) => {
291 | r.connect({ port: proxyPort, db: dbName })
292 | .then(function (conn) {
293 | return r.table('someOtherTable').run(conn);
294 | })
295 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i))
296 | .nodeify(done);
297 | });
298 | });
299 |
300 | describe('Query arguments', () => {
301 |
302 | it('should not allow a table name to be passed through r.args', (done) => {
303 | executeProxyQuery(r.db(dbName).table(r.args([tableName])))
304 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i))
305 | .nodeify(done);
306 | });
307 |
308 | it('should allow a table name to be passed through r.expr', (done) => {
309 | executeProxyQuery(r.db(dbName).table(r.expr(tableName)))
310 | .then(function (list) {
311 | list.should.be.instanceof(Array);
312 | })
313 | .nodeify(done);
314 | });
315 |
316 | it('should not allow a table name to be passed through a ReQL expression', (done) => {
317 | executeProxyQuery(r.db(dbName).table(r.db(dbName).tableList()(0)))
318 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /TABLE/i))
319 | .nodeify(done);
320 | });
321 |
322 | });
323 |
324 | });
325 |
326 |
327 | });
328 |
--------------------------------------------------------------------------------
/test/edge-cases.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeExecuteProxyQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 | import protoDef from '../node_modules/rethinkdb/proto-def';
8 |
9 | let proxyPort = 8125;
10 | let dbName = 'rethinkdb_proxy_test';
11 | let tableName = 'entries';
12 | let server;
13 |
14 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
15 | let executeProxyQuery = makeExecuteProxyQuery(dbName, proxyPort);
16 | let assertQuery = makeAssertQuery(executeQuery);
17 | let createDatabase = makeCreateDatabase(dbName, tableName);
18 | let dropDatabase = makeDropDatabase(dbName);
19 | let throwError = function (res) { throw new Error(); };
20 | let expectError = function (errorName, errorMessageMatch, err) {
21 | if (errorName !== null) errorName.should.equal(err.name);
22 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
23 | (err instanceof Error).should.equal(true);
24 | };
25 |
26 | describe('Edge Cases', () => {
27 |
28 | let get = r.db(dbName).table(tableName);
29 |
30 | describe('Replace', () => {
31 |
32 | before(function (done) {
33 | this.timeout(5000);
34 | createDatabase()
35 | .then(() => {
36 | return new Promise(function (resolve, reject) {
37 | server = new RethinkDBProxy({
38 | port: proxyPort,
39 | allowReplace: true,
40 | });
41 | server.listen(resolve);
42 | });
43 | })
44 | .then(() => {
45 | return r.connect().then((conn) => {
46 | return get.insert([{
47 | id: 1, name: 'Hello'
48 | }, {
49 | id: 2, name: 'Jorge'
50 | }]).run(conn).then(() => {
51 | return conn.close();
52 | });
53 | });
54 | })
55 | .nodeify(done);
56 | });
57 |
58 | after((done) => {
59 | dropDatabase()
60 | .then(server.close.bind(server))
61 | .then(done.bind(null, null));
62 | });
63 |
64 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', (done) => {
65 | executeQuery(get.get(1).replace(null))
66 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /REPLACE/i))
67 | .nodeify(done);
68 | });
69 |
70 | it('should not allow a query that passes null to `replace` if `delete` is not allowed', (done) => {
71 | executeQuery(get.replace(null))
72 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /REPLACE/i))
73 | .nodeify(done);
74 | });
75 |
76 | });
77 |
78 | describe('Insert', () => {
79 |
80 | before(function (done) {
81 | this.timeout(5000);
82 | createDatabase()
83 | .then(() => {
84 | return new Promise(function (resolve, reject) {
85 | server = new RethinkDBProxy({
86 | port: proxyPort,
87 | allowInsert: true,
88 | });
89 | server.listen(resolve);
90 | });
91 | })
92 | .then(() => {
93 | return r.connect().then((conn) => {
94 | return get.insert([{
95 | id: 1, name: 'Hello'
96 | }, {
97 | id: 2, name: 'Jorge'
98 | }]).run(conn).then(() => {
99 | return conn.close();
100 | });
101 | });
102 | })
103 | .nodeify(done);
104 | });
105 |
106 | after((done) => {
107 | dropDatabase()
108 | .then(server.close.bind(server))
109 | .then(done.bind(null, null));
110 | });
111 |
112 | it('should not allow a query that passes `conflict: replace` if `replace` is not allowed', (done) => {
113 | executeProxyQuery(get.insert({ id: 1, name: 'Hugo'}, { conflict: 'replace' }))
114 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i))
115 | .nodeify(done);
116 | });
117 |
118 | it('should not allow a query that passes `conflict: update` if `update` is not allowed', (done) => {
119 | executeProxyQuery(get.insert({ id: 1, name: 'Hugo'}, { conflict: 'update' }))
120 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /UPDATE/i))
121 | .nodeify(done);
122 | });
123 |
124 | });
125 |
126 | });
127 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 |
3 | import './parser.js';
4 | import './connection.js';
5 | import './queries.js';
6 | import './read-only-queries.js';
7 | import './edge-cases.js';
8 | import './database-and-table-access.js';
9 | import './changefeeds.js';
10 | import './parallel.js';
11 |
12 |
13 |
--------------------------------------------------------------------------------
/test/parallel.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeExecuteProxyQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 | import protoDef from '../node_modules/rethinkdb/proto-def';
8 |
9 | let proxyPort = 8125;
10 | let dbName = 'rethinkdb_proxy_test';
11 | let secondDbName = 'rethinkdb_proxy_test_2';
12 | let tableName = 'entries';
13 | let server;
14 |
15 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
16 | let executeProxyQuery = makeExecuteProxyQuery(dbName, proxyPort);
17 | let assertQuery = makeAssertQuery(executeQuery);
18 | let createDatabase = makeCreateDatabase(dbName, tableName);
19 | let createSecondDatabase = makeCreateDatabase(secondDbName, tableName);
20 | let dropDatabase = makeDropDatabase(dbName);
21 | let throwError = function (res) { throw new Error(); };
22 | let expectError = function (errorName, errorMessageMatch, err) {
23 | if (errorName !== null) errorName.should.equal(err.name);
24 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
25 | (err instanceof Error).should.equal(true);
26 | };
27 |
28 |
29 | describe('Parallel Queries', () => {
30 |
31 | let testData = [
32 | { name: 'Germany' },
33 | { name: 'Bhutan' },
34 | { name: 'Maldives' },
35 | ];
36 |
37 | before(function (done) {
38 | this.timeout(10000);
39 | createDatabase()
40 | .then(() => {
41 | return r.connect().then((conn) => {
42 | return r.db(dbName).table(tableName)
43 | .insert(testData)
44 | .run(conn);
45 | });
46 | })
47 | .then(() => {
48 | return new Promise(function (resolve, reject) {
49 | server = new RethinkDBProxy({
50 | port: proxyPort,
51 | });
52 | server.listen(resolve);
53 | });
54 | })
55 | .nodeify(done);
56 | });
57 |
58 | after((done) => {
59 | dropDatabase()
60 | .then(server.close.bind(server))
61 | .then(done.bind(null, null));
62 | });
63 |
64 | it('should handle parallel connections', (done) => {
65 | //this.timeout(15000);
66 | let query = function (countryName) {
67 | return r.db(dbName).table(tableName)
68 | .filter({ 'name': countryName })
69 | .coerceTo('array');
70 | };
71 | let query1 = query('Germany');
72 | let query2 = query('Maldives');
73 | let arr = (() => {
74 | let arr = [];
75 | for (let i = 0; i < 5; i += 1) arr.push(i);
76 | return arr;
77 | }());
78 | r.connect({ port: proxyPort }).then((conn1) => {
79 | return Promise.all(
80 | [].concat(arr.map(() => query1.run(conn1) ))
81 | .concat(arr.map(() => query2.run(conn1) ))
82 | )
83 | .then((res) => {
84 | res.slice(0, 5).forEach(row => row[0].name.should.equal('Germany'));
85 | res.slice(5, 10).forEach(row => row[0].name.should.equal('Maldives'));
86 | });
87 | })
88 | .nodeify(done);
89 | });
90 |
91 | it('should handle parallel cursors', (done) => {
92 | r.connect({ port: proxyPort }).then((conn1) => {
93 | return r.db(dbName).table(tableName).run(conn1)
94 | .then((cursor) => {
95 | let processRow = (err, row) => {
96 | if (err === null) return cursor.next(processRow);
97 | if (err.msg === 'No more rows in the cursor.') return done();
98 | return done(err);
99 | };
100 | cursor.next(processRow);
101 | });
102 | });
103 | });
104 |
105 | });
106 |
107 |
108 |
--------------------------------------------------------------------------------
/test/parser.js:
--------------------------------------------------------------------------------
1 | import BufferParser from './components/buffer-parser';
2 | import protoDef from '../node_modules/rethinkdb/proto-def';
3 | import 'should';
4 |
5 | describe('Buffer Parser', () => {
6 |
7 | let parser;
8 | let parseQuery = (...args) => {
9 | let result = [];
10 | let cb = args.pop();
11 | parser.on('query', (query) => {
12 | result.push(query);
13 | });
14 | let count = 0;
15 | for(let key in args) {
16 | if (args.hasOwnProperty(key)) {
17 | let value = args[key];
18 | let token = count++;
19 | let tokenBuffer = new Buffer(8);
20 | tokenBuffer.writeUInt32LE(Math.floor(token / 0xFFFFFFFF), 4);
21 | let byteLengthBuffer = new Buffer(4);
22 | let queryBuffer = new Buffer(JSON.stringify(value));
23 | byteLengthBuffer.writeUInt32LE(queryBuffer.length, 0);
24 | parser.append(Buffer.concat([tokenBuffer, byteLengthBuffer, queryBuffer]));
25 | }
26 | }
27 | cb(result);
28 | };
29 |
30 | beforeEach(() => {
31 | parser = new BufferParser();
32 | let version = new Buffer(4);
33 | version.writeUInt32LE(protoDef.VersionDummy.Version.VO_4, 0);
34 | let auth_buffer = new Buffer('', 'ascii');
35 | let auth_length = new Buffer(4);
36 | auth_length.writeUInt32LE(auth_buffer.length, 0);
37 | let protocol = new Buffer(4);
38 | protocol.writeUInt32LE(protoDef.VersionDummy.Protocol.JSON, 0);
39 | var token = Buffer.concat([version, auth_length, auth_buffer, protocol]);
40 | parser.append(token);
41 | });
42 |
43 |
44 | it('should correctly parse a single buffer', () => {
45 | let value = [[1]];
46 | parseQuery(value, (result) => {
47 | result[0].should.eql(value);
48 | });
49 | });
50 |
51 | it('should correctly parse a multiples buffers', () => {
52 | let value0 = [[1]];
53 | let value1 = [1, [3, [4, [3]]]];
54 | let value2 = [1, [3, [4, [3]]]];
55 | parseQuery(value0, value1, value2, (result) => {
56 | result[0].should.eql(value0);
57 | result[1].should.eql(value1);
58 | result[2].should.eql(value2);
59 | });
60 |
61 | });
62 |
63 | it('should correctly parse a parser with `]` inside the bytelength/token`', () => {
64 | let value = [1,[51,[[39,[[15,[[14,['rethinkdb_proxy_test']],'entries']],{'name':'Germany'}]],'array']]];
65 | parseQuery(value, value, value, (result) => {
66 | result[0].should.eql(value);
67 | result[1].should.eql(value);
68 | result[2].should.eql(value);
69 | });
70 |
71 | });
72 |
73 | it('should correctly handle strings with `[]`', () => {
74 | let value = [1,[51,[[39,[[15,[[14,['rethinkdb_proxy_test']],'entries']],{'name':'[[[[[[['}]],'array']]];
75 | parseQuery(value, value, value, (result) => {
76 | result[0].should.eql(value);
77 | result[1].should.eql(value);
78 | result[2].should.eql(value);
79 | });
80 | });
81 |
82 | });
83 |
--------------------------------------------------------------------------------
/test/queries.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 |
8 | let proxyPort = 8125;
9 | let dbName = 'rethinkdb_proxy_test';
10 | let tableName = 'entries';
11 | let server;
12 |
13 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
14 | let assertQuery = makeAssertQuery(executeQuery);
15 | let createDatabase = makeCreateDatabase(dbName, tableName);
16 | let dropDatabase = makeDropDatabase(dbName);
17 |
18 | describe('Normal Queries', () => {
19 |
20 | before(function (done) {
21 | this.timeout(5000);
22 | createDatabase()
23 | .then(() => {
24 | server = new RethinkDBProxy({
25 | port: proxyPort,
26 | allowWrites: true
27 | });
28 | server.listen(done);
29 | });
30 | });
31 |
32 | after((done) => {
33 | server.close().then(dropDatabase)
34 | .then(done.bind(null, null));
35 | });
36 |
37 | describe('Read Queries', () => {
38 |
39 | it('should return an `r.expr` successfully', (done) => {
40 | assertQuery(r.expr([1, 2, 3]))
41 | .nodeify(done);
42 | });
43 |
44 | it('should return the same list of databases', (done) => {
45 | assertQuery(r.dbList())
46 | .nodeify(done);
47 | });
48 |
49 | it('should handle group queries', (done) => {
50 | assertQuery(
51 | r.expr([{'v': 1}, {'v': 2}, {'v' : 2}, {'v' : 4}])
52 | .group('v').count().ungroup()
53 | )
54 | .nodeify(done);
55 | });
56 |
57 | });
58 |
59 | describe('Write Queries', () => {
60 |
61 | it('should return the same result after a write', (done) => {
62 | let get = r.db(dbName).table(tableName);
63 | executeQuery(get.insert({ hello: 'world'}))
64 | .then(() => {
65 | return assertQuery(get.orderBy('id'));
66 | })
67 | .then(() => {
68 | return r.connect().then((conn) => {
69 | return get.count().run(conn)
70 | .then((count) => {
71 | //count.should.eql(2);
72 | });
73 | });
74 | })
75 | .nodeify(done);
76 | });
77 |
78 | });
79 |
80 | });
81 |
--------------------------------------------------------------------------------
/test/read-only-queries.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import r from 'rethinkdb';
3 | import Promise from 'bluebird';
4 | import RethinkDBProxy from './components/rethinkdb-proxy';
5 | import should from 'should';
6 | import { makeExecuteQuery, makeExecuteProxyQuery, makeAssertQuery, makeCreateDatabase, makeDropDatabase } from './utils';
7 | import protoDef from '../node_modules/rethinkdb/proto-def';
8 |
9 | let proxyPort = 8125;
10 | let dbName = 'rethinkdb_proxy_test';
11 | let tableName = 'entries';
12 | let server;
13 |
14 | let executeQuery = makeExecuteQuery(dbName, proxyPort);
15 | let executeProxyQuery = makeExecuteProxyQuery(dbName, proxyPort);
16 | let assertQuery = makeAssertQuery(executeQuery);
17 | let createDatabase = makeCreateDatabase(dbName, tableName);
18 | let dropDatabase = makeDropDatabase(dbName);
19 | let throwError = function (res) { throw new Error(); };
20 | let expectError = function (errorName, errorMessageMatch, err) {
21 | if (errorMessageMatch !== null) err.msg.should.match(errorMessageMatch);
22 | if (errorName !== null) errorName.should.equal(err.name);
23 | (err instanceof Error).should.equal(true);
24 | };
25 |
26 | describe('Unallowed Queries', () => {
27 | let get = r.db(dbName).table(tableName);
28 |
29 | before(function (done) {
30 | this.timeout(5000);
31 | createDatabase()
32 | .then(() => {
33 | server = new RethinkDBProxy({
34 | port: proxyPort,
35 | });
36 | return server.listen().then(done);
37 | });
38 | });
39 |
40 | after((done) => {
41 | dropDatabase()
42 | .then(server.close.bind(server))
43 | .then(done.bind(null, null));
44 | });
45 |
46 | describe('Read Queries', () => {
47 |
48 | it('should return an `r.expr` successfully', (done) => {
49 | assertQuery(r.expr([1, 2, 3]))
50 | .nodeify(done);
51 | });
52 |
53 | it('should return the same list of databases', (done) => {
54 | assertQuery(r.dbList())
55 | .nodeify(done);
56 | });
57 |
58 | it('should allow for queries inside arrays', (done) => {
59 | assertQuery(r.expr([ get.coerceTo('array'), get.coerceTo('array') ]))
60 | .nodeify(done);
61 | });
62 |
63 | });
64 |
65 | describe('Insert', function () {
66 |
67 | it('should throw an error after attempting to write to the database', (done) => {
68 | executeProxyQuery(get.insert({ hello: 'world' }))
69 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i))
70 | .nodeify(done);
71 | });
72 |
73 | it('should not throw an error when using the same number as the `write` proto buff definition', (done) => {
74 | executeProxyQuery(r.expr([protoDef.Term.TermType.INSERT, 1, 2, 3]))
75 | .then(function (res) {
76 | res.should.eql([protoDef.Term.TermType.INSERT, 1, 2, 3]);
77 | })
78 | .nodeify(done);
79 | });
80 |
81 | it('should not allow insert queries inside `do`', (done) => {
82 | executeProxyQuery(r.expr([1, 2, 3]).do(function (row) { return get.insert({ name: row }); }))
83 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /INSERT/i))
84 | .nodeify(done);
85 | });
86 |
87 |
88 | it('should not allow for write queries inside arrays', (done) => {
89 | executeProxyQuery(r.expr([ get.delete(), get.coerceTo('array') ]))
90 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DELETE/i))
91 | .nodeify(done);
92 | });
93 | });
94 |
95 | describe('Delete', () => {
96 | it('should not allow delete queries inside `forEach` inside a `do`', (done) => {
97 | executeProxyQuery(get.coerceTo('array').do((rows) => {
98 | return rows.forEach((row) => {
99 | return get.get(row('id')).delete();
100 | });
101 | }))
102 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /DELETE/i))
103 | .nodeify(done);
104 | });
105 | });
106 |
107 | describe('HTTP', function () {
108 |
109 | it('should not allow http queries inside `map` inside a `do`', (done) => {
110 | executeProxyQuery(get.coerceTo('array').slice(0, 3).do((rows) => {
111 | return rows.map((row) => {
112 | return r.http('http://www.reddit.com/r/javascript.json');
113 | });
114 | }))
115 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /HTTP/i))
116 | .nodeify(done);
117 | });
118 |
119 | it('should not allow http queries inside `do`', (done) => {
120 | executeProxyQuery(r.expr('hello').do((rows) => {
121 | return r.http('http://www.reddit.com/r/javascript.json');
122 | }))
123 | .then(throwError, expectError.bind(null, 'ReqlDriverError', /HTTP/i))
124 | .nodeify(done);
125 | });
126 |
127 | });
128 |
129 | });
130 |
--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true */
2 | import Promise from 'bluebird';
3 | import r from 'rethinkdb';
4 |
5 | export function makeExecuteQuery (dbName, proxyPort) {
6 | return function (query) {
7 | return Promise.resolve()
8 | .then(() => {
9 | return [r.connect({ db: dbName }), r.connect({ port: proxyPort, db: dbName })];
10 | })
11 | .spread((connA, connB) => {
12 | return Promise.all([query.run(connA), query.run(connB)])
13 | .spread((resultA, resultB) => {
14 | if (typeof resultA.toArray === 'function' && typeof resultA.each === 'function') {
15 | return Promise.all([
16 | resultA.toArray(),
17 | resultB.toArray()
18 | ]);
19 | }
20 | return [resultA, resultB];
21 | })
22 | .finally(function () {
23 | return Promise.all([connA.close(), connB.close()]);
24 | });
25 | });
26 | };
27 | }
28 |
29 | export function makeExecuteProxyQuery (dbName, proxyPort) {
30 | return function (query) {
31 | return Promise.resolve()
32 | .then(() => {
33 | return r.connect({ port: proxyPort, db: dbName });
34 | })
35 | .then((conn) => {
36 | return query.run(conn)
37 | .then((result) => {
38 | if (typeof result.toArray === 'function' && typeof result.each === 'function') {
39 | return result.toArray();
40 | }
41 | return result;
42 | })
43 | .finally(function () {
44 | return conn.close();
45 | });
46 | });
47 | };
48 | }
49 |
50 | export function makeAssertQuery (executeQuery) {
51 | return function (query) {
52 | return executeQuery(query)
53 | .spread((resultA, resultB) => {
54 | return resultA.should.eql(resultB);
55 | });
56 | };
57 | }
58 |
59 | export function makeCreateDatabase (dbName, tableName) {
60 | return function (done) {
61 | return r.connect().then((conn) => {
62 | return Promise.resolve()
63 | .then(() => {
64 | return r.dbCreate(dbName).run(conn)
65 | .catch((err) => { });
66 | })
67 | .then(() => {
68 | return r.db(dbName).tableCreate(tableName).run(conn)
69 | .catch((err) => { });
70 | });
71 | });
72 | };
73 | }
74 |
75 | export function makeDropDatabase (dbName) {
76 | return function () {
77 | return r.connect().then((conn) => {
78 | return r.dbDrop(dbName).run(conn)
79 | .finally(() => conn.close());
80 | });
81 | };
82 | }
83 |
84 |
--------------------------------------------------------------------------------