Skip to content

Commit 6accbf5

Browse files
slvrtrnpulpdrew
andauthored
Role query parameters (#351)
Co-authored-by: Andrew Davis <[email protected]>
1 parent 00af5c2 commit 6accbf5

File tree

13 files changed

+507
-6
lines changed

13 files changed

+507
-6
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 1.8.0 (Common, Node.js, Web)
2+
3+
## New features
4+
5+
- Added support for specifying roles via request query parameters. See [this example](examples/role.ts) for more details. ([@pulpdrew](https://github.com/pulpdrew), [#328](https://github.com/ClickHouse/clickhouse-js/pull/328))
6+
17
# 1.7.0 (Common, Node.js, Web)
28

39
## Bug fixes

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ If something is missing, or you found a mistake in one of these examples, please
6868
- [default_format_setting.ts](default_format_setting.ts) - sending queries using `exec` method without a `FORMAT` clause; the default format will be set from the client settings.
6969
- [session_id_and_temporary_tables.ts](session_id_and_temporary_tables.ts) - creating a temporary table, which requires a session_id to be passed to the server.
7070
- [session_level_commands.ts](session_level_commands.ts) - using SET commands, memorized for the specific session_id.
71+
- [role.ts](role.ts) - using one or more roles without explicit `USE` commands or session IDs
7172

7273
## How to run
7374

examples/role.ts

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import type { ClickHouseError } from '@clickhouse/client'
2+
import { createClient } from '@clickhouse/client' // or '@clickhouse/client-web'
3+
4+
/**
5+
* An example of specifying a role using query parameters
6+
* See https://clickhouse.com/docs/en/interfaces/http#setting-role-with-query-parameters
7+
*/
8+
void (async () => {
9+
const format = 'JSONEachRow'
10+
const username = 'clickhouse_js_role_user'
11+
const password = 'role_user_password'
12+
const table1 = 'clickhouse_js_role_table_1'
13+
const table2 = 'clickhouse_js_role_table_2'
14+
15+
// Create 2 tables, a role for each table allowing SELECT, and a user with access to those roles
16+
const defaultClient = createClient()
17+
await createOrReplaceUser(username, password)
18+
const table1Role = await createTableAndGrantAccess(table1, username)
19+
const table2Role = await createTableAndGrantAccess(table2, username)
20+
await defaultClient.close()
21+
22+
// Create a client using a role that only has permission to query table1
23+
const client = createClient({
24+
username,
25+
password,
26+
// This role will be applied to all the queries by default,
27+
// unless it is overridden in a specific method call
28+
role: table1Role,
29+
})
30+
31+
// Selecting from table1 is allowed using table1Role
32+
const resultSet1 = await client.query({
33+
query: `select count(*) from ${table1}`,
34+
format,
35+
})
36+
console.log(
37+
`Successfully queried from ${table1} using ${table1Role}. Result: `,
38+
await resultSet1.json(),
39+
)
40+
41+
// Selecting from table2 is not allowed using table1Role,
42+
// which is set by default in the client instance
43+
await client
44+
.query({ query: `select count(*) from ${table2}`, format })
45+
.catch((e: ClickHouseError) => {
46+
console.error(
47+
`Failed to query from ${table2}, as ${table1Role} does not have sufficient privileges. Expected error:`,
48+
e,
49+
)
50+
})
51+
52+
// Override the client's role to table2Role, allowing a query to table2
53+
const resultSet2 = await client.query({
54+
query: `select count(*) from ${table2}`,
55+
role: table2Role,
56+
format,
57+
})
58+
console.log(
59+
`Successfully queried from ${table2} using ${table2Role}. Result:`,
60+
await resultSet2.json(),
61+
)
62+
63+
// Selecting from table1 is no longer allowed, since table2Role is being used
64+
await client
65+
.query({
66+
query: `select count(*) from ${table1}`,
67+
role: table2Role,
68+
format,
69+
})
70+
.catch((e: ClickHouseError) => {
71+
console.error(
72+
`Failed to query from ${table1}, as ${table2Role} does not have sufficient privileges. Expected error:`,
73+
e,
74+
)
75+
})
76+
77+
// Multiple roles can be specified to allowed querying from either table
78+
const resultSet3 = await client.query({
79+
query: `select count(*) from ${table1}`,
80+
role: [table1Role, table2Role],
81+
format,
82+
})
83+
console.log(
84+
`Successfully queried from ${table1} using roles: [${table1Role}, ${table2Role}]. Result:`,
85+
await resultSet3.json(),
86+
)
87+
88+
const resultSet4 = await client.query({
89+
query: `select count(*) from ${table2}`,
90+
role: [table1Role, table2Role],
91+
format,
92+
})
93+
console.log(
94+
`Successfully queried from ${table2} using roles: [${table1Role}, ${table2Role}]. Result: `,
95+
await resultSet4.json(),
96+
)
97+
98+
await client.close()
99+
100+
async function createOrReplaceUser(username: string, password: string) {
101+
await defaultClient.command({
102+
query: `CREATE USER OR REPLACE ${username} IDENTIFIED WITH plaintext_password BY '${password}'`,
103+
})
104+
}
105+
106+
async function createTableAndGrantAccess(
107+
tableName: string,
108+
username: string,
109+
) {
110+
const role = `${tableName}_role`
111+
112+
await defaultClient.command({
113+
query: `
114+
CREATE OR REPLACE TABLE ${tableName}
115+
(id UInt32, name String, sku Array(UInt32))
116+
ENGINE MergeTree()
117+
ORDER BY (id)
118+
`,
119+
})
120+
121+
await defaultClient.command({ query: `CREATE ROLE OR REPLACE ${role}` })
122+
await defaultClient.command({
123+
query: `GRANT SELECT ON ${tableName} TO ${role}`,
124+
})
125+
await defaultClient.command({ query: `GRANT ${role} TO ${username}` })
126+
127+
return role
128+
}
129+
})()

packages/client-common/__tests__/integration/exec_and_command.test.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { type ClickHouseClient } from '@clickhouse/client-common'
33
import {
44
createTestClient,
55
getClickHouseTestEnvironment,
6-
getTestDatabaseName,
76
guid,
87
TestEnv,
98
validateUUID,
@@ -51,8 +50,8 @@ describe('exec and command', () => {
5150
})
5251
})
5352

54-
it('does not swallow ClickHouse error', async () => {
55-
const { ddl, tableName } = getDDL()
53+
it('should not swallow ClickHouse error', async () => {
54+
const { ddl } = getDDL()
5655
const commands = async () => {
5756
const command = () =>
5857
runExec({
@@ -65,9 +64,6 @@ describe('exec and command', () => {
6564
jasmine.objectContaining({
6665
code: '57',
6766
type: 'TABLE_ALREADY_EXISTS',
68-
message: jasmine.stringContaining(
69-
`Table ${getTestDatabaseName()}.${tableName} already exists. `,
70-
),
7167
}),
7268
)
7369
})

0 commit comments

Comments
 (0)