Skip to content

Commit

Permalink
Topic/k1ch/ Introduce include_permissions query param for `GET /roles…
Browse files Browse the repository at this point in the history
…` APIs (#126)

* chore: topic/k1ch/ update knex configuration to avoid race condition

* feat: topic/k1ch/ introduce includePermissions query for get/roles API

* chore: bump version to 2.3.0-rc.1

* refactor: k1ch/ refactored view-select-relationships to use knex instead of PG pool

* refactor: k1ch / modify admin-role db layer to use knex instead of PG pool

* chore: k1ch / bump version to 2.3.0-rc.2

* chore: k1ch / ensure seedKeysIfDbIsEmpty does not cause Unhandled Promise Rejection

* feat: k1ch/ introduce clients/client_id/roles?includePermissions=true

* chore: k1ch/ try catch for seedKeysIfDbIsEmpty function

* chore: k1ch/ add test for getPermissionsByRoleKey

* chore: update OAS and the-usher.js

* chore: k1ch/ add more tests for roles?include_permission=true

* chore: minor updates
  • Loading branch information
k1ch authored Oct 17, 2024
1 parent 890c37d commit 63dd5c4
Show file tree
Hide file tree
Showing 23 changed files with 536 additions and 231 deletions.
22 changes: 15 additions & 7 deletions database/knexfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,22 @@ module.exports = {
schemaName: env.PGSCHEMA,
},
pool: {
min: +process.env.KNEX_POOL_MIN || 0,
max: +process.env.KNEX_POOL_MAX || 100,
min: +process.env.KNEX_POOL_MIN || 0, // Minimum number of connections in the pool
max: +process.env.KNEX_POOL_MAX || 100, // Maximum number of connections in the pool

// tarn config (https://www.npmjs.com/package/tarn)
/*
Tarn.js configuration (see https://www.npmjs.com/package/tarn for more details)
Default tarn.js settings:
propagateCreateError: true // Whether to propagate errors encountered during creation
createRetryIntervalMillis: 200 // Delay between retries when trying to create a new connection
createTimeoutMillis: 30000 // Time to wait before timing out when creating a new connection
acquireTimeoutMillis: 30000 // Time to wait before timing out when acquiring a connection from the pool
reapIntervalMillis: 1000 // Frequency of checking for idle resources to be destroyed
*/
propagateCreateError: process.env.KNEX_POOL_PROPAGATE_CREATE_ERROR === 'true' || false,
createRetryIntervalMillis: +process.env.KNEX_POOL_CREATE_RETRY_INTERVAL_MILLIS || 500,
createTimeoutMillis: +process.env.KNEX_POOL_CREATE_TIMEOUT_MILLIS || 5000,
acquireTimeoutMillis: +process.env.KNEX_POOL_ACQUIRE_TIMEOUT_MILLIS || 5000,
reapIntervalMillis: +process.env.KNEX_POOL_REAP_INTERVAL_MILLIS || 1000,
createRetryIntervalMillis: +process.env.KNEX_POOL_CREATE_RETRY_INTERVAL_MILLIS || 200,
createTimeoutMillis: +process.env.KNEX_POOL_CREATE_TIMEOUT_MILLIS || 10000,
acquireTimeoutMillis: +process.env.KNEX_POOL_ACQUIRE_TIMEOUT_MILLIS || 10000,
reapIntervalMillis: +process.env.KNEX_POOL_REAP_INTERVAL_MILLIS || 500,
},
}
18 changes: 18 additions & 0 deletions database/layer/admin-permission.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,27 @@ const getPermission = async (permissionKey) => {
}
}

/**
* Retrieve a list of permissions by role key
*
* @param {number} roleKey - The role key
* @returns {Promise<Array<Object>>} - A promise that resolves to the list of permission objects associated with the role
*/
const getPermissionsByRoleKey = async (roleKey) => {
try {
return await usherDb('permissions')
.join('rolepermissions', 'permissions.key', '=', 'rolepermissions.permissionkey')
.where({ 'rolepermissions.rolekey': roleKey })
.select('permissions.*');
} catch (err) {
throw pgErrorHandler(err);
}
}

module.exports = {
insertPermissionByClientId,
updatePermissionByPermissionname,
deletePermissionByPermissionname,
getPermission,
getPermissionsByRoleKey,
}
74 changes: 41 additions & 33 deletions database/layer/admin-role.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
const { PGPool } = require('./pg_pool')
const pool = new PGPool()
const { usherDb } = require('./knex')
const { pgErrorHandler } = require('../utils/pgErrorHandler')

module.exports = {
insertRoleByClientId,
updateRoleByClientRolename,
deleteRoleByClientRolename,
getRole,
listRoles
}

async function insertRoleByClientId (clientId, rolename, roledescription) {
let sql = 'INSERT INTO usher.roles (clientkey, name, description) SELECT key, $1, $2 FROM usher.clients WHERE client_id = $3'
const insertRoleByClientId = async (clientId, rolename, roledescription) => {
let sql = 'INSERT INTO usher.roles (clientkey, name, description) SELECT key, ?, ? FROM usher.clients WHERE client_id = ?'
try {
const results = await pool.query(sql, [rolename, roledescription, clientId])
const results = await usherDb.raw(sql, [rolename, roledescription, clientId])
if (results.rowCount === 1) {
sql = 'SElECT r.key, r.clientkey, r.name, r.description FROM usher.roles r inner join usher.clients c on (r.clientkey=c.key AND c.client_id=$1) WHERE r.name=$2'
const role = await pool.query(sql, [clientId, rolename])
sql = 'SElECT r.key, r.clientkey, r.name, r.description FROM usher.roles r inner join usher.clients c on (r.clientkey=c.key AND c.client_id=?) WHERE r.name=?'
const role = await usherDb.raw(sql, [clientId, rolename])
return role.rows[0]
} else {
throw new Error(`Client does not exist matching client_id ${clientId}`)
Expand All @@ -29,11 +21,11 @@ async function insertRoleByClientId (clientId, rolename, roledescription) {
}
}

async function updateRoleByClientRolename (clientId, rolename, roledescription) {
const sql = 'UPDATE usher.roles r SET description = $1 WHERE EXISTS (SELECT 1 FROM usher.clients c WHERE c.client_id = $2) AND r.name = $3'
const updateRoleByClientRolename = async (clientId, rolename, roledescription) => {
const sql = 'UPDATE usher.roles r SET description = ? WHERE EXISTS (SELECT 1 FROM usher.clients c WHERE c.client_id = ?) AND r.name = ?'
const sqlParams = [roledescription, clientId, rolename]
try {
const updateResult = await pool.query(sql, sqlParams)
const updateResult = await usherDb.raw(sql, sqlParams)
if (updateResult.rowCount === 1) {
return 'Update successful'
} else {
Expand All @@ -50,17 +42,21 @@ async function updateRoleByClientRolename (clientId, rolename, roledescription)
* @param {number} key The Role primary key
* @returns Role object
*/
async function getRole (key) {
const sql = 'SELECT r.* FROM usher.roles r WHERE r.key = $1'
const results = await pool.query(sql, [key])
return results.rows[0]
const getRole = async (key) => {
try {
const sql = 'SELECT r.* FROM usher.roles r WHERE r.key = ?'
const results = await usherDb.raw(sql, [key])
return results.rows[0]
} catch (err) {
throw pgErrorHandler(err)
}
}

async function deleteRoleByClientRolename (clientId, rolename) {
const sql = 'DELETE FROM usher.roles r WHERE EXISTS (SELECT 1 FROM usher.clients c WHERE c.client_id = $1) AND r.name = $2'
const deleteRoleByClientRolename = async (clientId, rolename) => {
const sql = 'DELETE FROM usher.roles r WHERE EXISTS (SELECT 1 FROM usher.clients c WHERE c.client_id = ?) AND r.name = ?'
const sqlParams = [clientId, rolename]
try {
const results = await pool.query(sql, sqlParams)
const results = await usherDb.raw(sql, sqlParams)
if (results.rowCount === 1) {
return 'Delete successful'
} else {
Expand All @@ -78,20 +74,32 @@ async function deleteRoleByClientRolename (clientId, rolename) {
* @param {string} [clientId] Optional client_id to filter list of Roles belonging to given Client
* @returns Array of Role objects with associated extra fields from tenants, clients
*/
async function listRoles (clientId) {
const params = []
let sql = `SELECT t.name AS tenantname, t.iss_claim AS iss_claim,
const listRoles = async (clientId) => {
try {
const params = []
let sql = `SELECT t.name AS tenantname, t.iss_claim AS iss_claim,
c.client_id AS client_id, r.key, r.clientkey, r.name, r.description
FROM usher.roles r
JOIN usher.clients c ON (c.key = r.clientkey)
JOIN usher.tenantclients tc ON (c.key = tc.clientkey)
JOIN usher.tenants t ON (t.key = tc.tenantkey)
WHERE 1=1 `
if (clientId) {
sql += ' and c.client_id = $1'
params.push(clientId)
if (clientId) {
sql += ' and c.client_id = ?'
params.push(clientId)
}

const roles = await usherDb.raw(sql, params)
return roles.rows
} catch (err) {
throw pgErrorHandler(err)
}
}

const roles = await pool.query(sql, params)
return roles.rows
module.exports = {
insertRoleByClientId,
updateRoleByClientRolename,
deleteRoleByClientRolename,
getRole,
listRoles,
}
13 changes: 5 additions & 8 deletions database/layer/knex.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ try {
usherDb = knex(knexDbConfig)
if (usherDb?.client?.pool) {
const pool = usherDb.client.pool
// Set idle timeout to 0 to release connections immediately. This can't be configured through Knex.
/*
Set idleTimeoutMillis to 0 to ensure connections are released during the next pool.check() interval.
The check interval can be configured using reapIntervalMillis.
Default idleTimeoutMillis is 30000 ms.
*/
pool.idleTimeoutMillis = 0

// Check the pool for idle connections on 'release' event
pool.on('release', () => {
process.nextTick(() => {
pool.check() // Ensures the pool checks for idle connections immediately
})
})
}
} catch (err) {
console.error('Failed to create knex instance: ', JSON.stringify(err))
Expand Down
Loading

0 comments on commit 63dd5c4

Please sign in to comment.