Skip to content

Commit f398782

Browse files
pulpdrewslvrtrn
andauthored
Add support for role query parameters (#328)
Co-authored-by: Serge Klochkov <3175289+slvrtrn@users.noreply.github.com>
1 parent 00af5c2 commit f398782

File tree

10 files changed

+533
-0
lines changed

10 files changed

+533
-0
lines changed

examples/README.md

Lines changed: 1 addition & 0 deletions
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

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
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 = 'JSON'
10+
const username = 'role_user'
11+
const password = 'role_user_password'
12+
const table1 = 'table_1'
13+
const table2 = '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+
role: table1Role,
27+
})
28+
29+
// Selecting from table1 is allowed using table1Role
30+
let rs = await client.query({
31+
query: `select count(*) from ${table1}`,
32+
format,
33+
})
34+
console.log(
35+
`Successfully queried from ${table1} using ${table1Role}. Result: `,
36+
(await rs.json()).data,
37+
)
38+
39+
// Selecting from table2 is not allowed using table1Role
40+
await client
41+
.query({ query: `select count(*) from ${table2}`, format })
42+
.catch((e: ClickHouseError) => {
43+
console.error(
44+
`Failed to qeury from ${table2} due to error with type: ${e.type}. Message: ${e.message}`,
45+
)
46+
})
47+
48+
// Override the client's role to table2Role, allowing a query to table2
49+
rs = await client.query({
50+
query: `select count(*) from ${table2}`,
51+
format,
52+
role: table2Role,
53+
})
54+
console.log(
55+
`Successfully queried from ${table2} using ${table2Role}. Result: `,
56+
(await rs.json()).data,
57+
)
58+
59+
// Selecting from table1 is no longer allowed, since table2Role is being used
60+
await client
61+
.query({
62+
query: `select count(*) from ${table1}`,
63+
format,
64+
role: table2Role,
65+
})
66+
.catch((e: ClickHouseError) => {
67+
console.error(
68+
`Failed to qeury from ${table1} due to error with type: ${e.type}. Message: ${e.message}`,
69+
)
70+
})
71+
72+
// Multiple roles can be specified to allowed querying from either table
73+
rs = await client.query({
74+
query: `select count(*) from ${table1}`,
75+
format,
76+
role: [table1Role, table2Role],
77+
})
78+
console.log(
79+
`Successfully queried from ${table1} using roles: [${table1Role}, ${table2Role}]. Result: `,
80+
(await rs.json()).data,
81+
)
82+
83+
rs = await client.query({
84+
query: `select count(*) from ${table2}`,
85+
format,
86+
role: [table1Role, table2Role],
87+
})
88+
console.log(
89+
`Successfully queried from ${table2} using roles: [${table1Role}, ${table2Role}]. Result: `,
90+
(await rs.json()).data,
91+
)
92+
93+
await client.close()
94+
95+
async function createOrReplaceUser(username: string, password: string) {
96+
await defaultClient.command({
97+
query: `CREATE USER OR REPLACE ${username} IDENTIFIED WITH plaintext_password BY '${password}'`,
98+
})
99+
}
100+
101+
async function createTableAndGrantAccess(
102+
tableName: string,
103+
username: string,
104+
) {
105+
const role = `${tableName}_role`
106+
107+
await defaultClient.command({
108+
query: `
109+
CREATE OR REPLACE TABLE ${tableName}
110+
(id UInt32, name String, sku Array(UInt32))
111+
ENGINE MergeTree()
112+
ORDER BY (id)
113+
`,
114+
})
115+
116+
await defaultClient.command({ query: `CREATE ROLE OR REPLACE ${role}` })
117+
await defaultClient.command({
118+
query: `GRANT SELECT ON ${tableName} TO ${role}`,
119+
})
120+
await defaultClient.command({ query: `GRANT ${role} TO ${username}` })
121+
122+
return role
123+
}
124+
})()

0 commit comments

Comments
 (0)