Skip to content

Commit

Permalink
Add support for role query parameters (#328)
Browse files Browse the repository at this point in the history
Co-authored-by: Serge Klochkov <[email protected]>
  • Loading branch information
pulpdrew and slvrtrn authored Nov 5, 2024
1 parent 00af5c2 commit f398782
Show file tree
Hide file tree
Showing 10 changed files with 533 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ If something is missing, or you found a mistake in one of these examples, please
- [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.
- [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.
- [session_level_commands.ts](session_level_commands.ts) - using SET commands, memorized for the specific session_id.
- [role.ts](role.ts) - using one or more roles without explicit `USE` commands or session IDs

## How to run

Expand Down
124 changes: 124 additions & 0 deletions examples/role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { ClickHouseError } from '@clickhouse/client'
import { createClient } from '@clickhouse/client' // or '@clickhouse/client-web'

/**
* An example of specifying a role using query parameters
* See https://clickhouse.com/docs/en/interfaces/http#setting-role-with-query-parameters
*/
void (async () => {
const format = 'JSON'
const username = 'role_user'
const password = 'role_user_password'
const table1 = 'table_1'
const table2 = 'table_2'

// Create 2 tables, a role for each table allowing SELECT, and a user with access to those roles
const defaultClient = createClient()
await createOrReplaceUser(username, password)
const table1Role = await createTableAndGrantAccess(table1, username)
const table2Role = await createTableAndGrantAccess(table2, username)
await defaultClient.close()

// Create a client using a role that only has permission to query table1
const client = createClient({
username,
password,
role: table1Role,
})

// Selecting from table1 is allowed using table1Role
let rs = await client.query({
query: `select count(*) from ${table1}`,
format,
})
console.log(
`Successfully queried from ${table1} using ${table1Role}. Result: `,
(await rs.json()).data,
)

// Selecting from table2 is not allowed using table1Role
await client
.query({ query: `select count(*) from ${table2}`, format })
.catch((e: ClickHouseError) => {
console.error(
`Failed to qeury from ${table2} due to error with type: ${e.type}. Message: ${e.message}`,
)
})

// Override the client's role to table2Role, allowing a query to table2
rs = await client.query({
query: `select count(*) from ${table2}`,
format,
role: table2Role,
})
console.log(
`Successfully queried from ${table2} using ${table2Role}. Result: `,
(await rs.json()).data,
)

// Selecting from table1 is no longer allowed, since table2Role is being used
await client
.query({
query: `select count(*) from ${table1}`,
format,
role: table2Role,
})
.catch((e: ClickHouseError) => {
console.error(
`Failed to qeury from ${table1} due to error with type: ${e.type}. Message: ${e.message}`,
)
})

// Multiple roles can be specified to allowed querying from either table
rs = await client.query({
query: `select count(*) from ${table1}`,
format,
role: [table1Role, table2Role],
})
console.log(
`Successfully queried from ${table1} using roles: [${table1Role}, ${table2Role}]. Result: `,
(await rs.json()).data,
)

rs = await client.query({
query: `select count(*) from ${table2}`,
format,
role: [table1Role, table2Role],
})
console.log(
`Successfully queried from ${table2} using roles: [${table1Role}, ${table2Role}]. Result: `,
(await rs.json()).data,
)

await client.close()

async function createOrReplaceUser(username: string, password: string) {
await defaultClient.command({
query: `CREATE USER OR REPLACE ${username} IDENTIFIED WITH plaintext_password BY '${password}'`,
})
}

async function createTableAndGrantAccess(
tableName: string,
username: string,
) {
const role = `${tableName}_role`

await defaultClient.command({
query: `
CREATE OR REPLACE TABLE ${tableName}
(id UInt32, name String, sku Array(UInt32))
ENGINE MergeTree()
ORDER BY (id)
`,
})

await defaultClient.command({ query: `CREATE ROLE OR REPLACE ${role}` })
await defaultClient.command({
query: `GRANT SELECT ON ${tableName} TO ${role}`,
})
await defaultClient.command({ query: `GRANT ${role} TO ${username}` })

return role
}
})()
Loading

0 comments on commit f398782

Please sign in to comment.