@transitive-sdk/clickhouse 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -5,6 +5,17 @@ const { createClient } = require('@clickhouse/client');
5
5
 
6
6
  const { topicToPath, topicMatch } = require('@transitive-sdk/datacache');
7
7
 
8
+ /** Generate a random id (base36) */
9
+ const getRandomId = (bytes = 6) => {
10
+ const buffer = new Uint8Array(bytes);
11
+ crypto.getRandomValues(buffer);
12
+ return buffer.reduce((memo, i) => memo + i.toString(36), '');
13
+ };
14
+
15
+ const log = {
16
+ debug: console.log
17
+ }
18
+
8
19
  // Default TTL in days for mqtt_history table
9
20
  const DEFAULT_TTL_DAYS = 30;
10
21
 
@@ -466,7 +477,67 @@ class ClickHouse {
466
477
  return row;
467
478
  });
468
479
  }
480
+
481
+ /* Creates ClickHouse user for an organisation/user with SELECT access for all
482
+ dbs and tables. `accountsCollection` is the mongo accounts collection and
483
+ needs to be provided, only optional for testing. */
484
+ async ensureClickHouseOrgUser(orgId, accountsCollection = undefined) {
485
+ const orgUser = `org_${orgId}_user`;
486
+
487
+ // Check if user exists
488
+ const userExists = await ClickHouse.client.query({
489
+ query: `SELECT name FROM system.users WHERE name = '${orgUser}'`,
490
+ format: 'JSONEachRow'
491
+ });
492
+
493
+ const users = await userExists.json();
494
+
495
+ if (users.length > 0) {
496
+ log.debug(`ClickHouse user for organization ${orgId} already exists`);
497
+ if (!accountsCollection) return;
498
+ const orgDoc = await accountsCollection.findOne({ _id: orgId });
499
+ const { user: orgUser, password } = orgDoc.clickhouseCredentials;
500
+ if (!password) {
501
+ throw new Error(`ClickHouse user for organization ${orgId} exists but no password found in mongo`);
502
+ } else {
503
+ log.debug(`retrieved ClickHouse credentials for organization ${orgId} from mongo: ${orgUser} / ${password}`);
504
+ return {
505
+ user: orgUser,
506
+ password: password
507
+ };
508
+ }
509
+ }
510
+
511
+ const orgPassword = getRandomId(15);
512
+
513
+ log.debug(`creating ClickHouse user for organization ${orgId} : ${orgUser} / ${orgPassword}`);
514
+
515
+ for (let query of [
516
+ // Create user:
517
+ `CREATE USER IF NOT EXISTS ${orgUser} IDENTIFIED WITH plaintext_password BY '${orgPassword}'`,
518
+ // Grant read only access to all databases and tables - row level security
519
+ // will limit access to own org data:
520
+ `GRANT SELECT ON *.* TO ${orgUser}`,
521
+ // Immediately revoke select access to system tables again (see
522
+ // https://clickhouse.com/docs/sql-reference/statements/revoke#examples):
523
+ `REVOKE SELECT ON system.* FROM ${orgUser}`,
524
+ ]) await ClickHouse.client.command({ query });
525
+
526
+ // store user and password in mongo
527
+ if (accountsCollection) {
528
+ await accountsCollection.updateOne(
529
+ { _id: orgId },
530
+ { $set: { clickhouseCredentials: { user: orgUser, password: orgPassword } } }
531
+ );
532
+ }
533
+
534
+ return {
535
+ user: orgUser,
536
+ password: orgPassword
537
+ };
538
+ }
469
539
  }
470
540
 
541
+
471
542
  const instance = new ClickHouse();
472
543
  module.exports = instance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@transitive-sdk/clickhouse",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "A tiny ClickHouse utility class for use in the Transitive framework.",
5
5
  "homepage": "https://transitiverobotics.com",
6
6
  "repository": {
@@ -476,4 +476,15 @@ describe('ClickHouse', function() {
476
476
  assertTimelimit(ROWS / 1000);
477
477
  });
478
478
  });
479
+
480
+
481
+ describe('users', () => {
482
+ it('create a user', async () => {
483
+ const result = await clickhouse.ensureClickHouseOrgUser('test');
484
+ console.log({result});
485
+
486
+ const result2 = await clickhouse.ensureClickHouseOrgUser('test');
487
+ assert.equal(result2, undefined);
488
+ });
489
+ });
479
490
  });