rbac-truiam
v3.1.1
Published
RBAC module for TruIAM users
Maintainers
Readme
RBAC MODULE
Installation
Using npm
npm install rbac-truiamGetting Started
rbac-truiam is a Node.js client for TruIAM's RBAC backend (the TruIAM Backend Service) — it wraps that backend's HTTP API so your own app can authenticate users, check permissions, and read/manage roles, policies, services, accesses, resources, organizations, tenants, and users without hand-rolling the HTTP calls or the Redis caching yourself.
1. Import it
Works with either module style — the package is written in TypeScript and compiles to CommonJS with type declarations included, so both of these work with no extra setup:
// CommonJS
const { initializeUser, checkPermission, userProfile } = require('rbac-truiam');
// TypeScript / ESM
import { initializeUser, checkPermission, userProfile } from 'rbac-truiam';2. Configure the backend URL and Redis
Two things this package needs, both read automatically from your own app's environment (.env/process.env) — nothing to call in code for the common case:
- Backend URL:
TRUIAM_URL. If unset, falls back to the default TruIAM SaaS URL — set this explicitly if you're pointing at a different environment (staging, on-prem, etc.). - Redis:
REDIS_URL(takes precedence if set), orREDIS_HOST/REDIS_PORT/REDIS_USERNAME/REDIS_PASSWORD. Used for caching lookups this package makes on your behalf (role/policy/service/access/resource detail, session data) so repeat calls are cheap. If your app already sets these for its own Redis usage, this package just reuses them — no separate Redis instance is required.setUpRedisConfig()(see below) exists only if you'd rather set these programmatically instead of via environment variables; it's optional.
3. Know the two function families
Every function in this package belongs to one of two groups, and which one matters for what you pass in:
- RBAC functions (
initializeUser,checkPermission,userProfile,getRoleById,getAttachedPolicy,getPolicyDetailsByRole,getPolicyDetailsByOrg, and everything else listed under "RBAC Functions" below) — these take a bare access token (the string itself, e.g.{ token }), and represent "what can this logged-in user do / see." Most are Redis-cached. - TruIAM API functions (everything under "TRUIAM Functions" below — auth, user admin, org/tenant/permission admin) — these take a
headerDataobject ({ idp, domain, token, ... }) instead of a bare token, and represent auth flows and admin operations (creating users, managing orgs, listing policies for an org, etc.).
If you're not sure which one you need: checking or displaying something about the person currently logged into your app → RBAC function. Signing someone in/up, or managing users/orgs/policies as an admin → TruIAM API function.
4. A typical flow
Most integrations follow the same shape: get a token (from your own auth, or via this package's executeSignIn), initialize it once, then use RBAC functions for the rest of the request/session.
const { initializeUser, checkPermission, userProfile } = require('rbac-truiam');
// Once per session/token — this fetches and caches the user's org, tenant,
// and role/policy data so the calls below don't each hit the backend fresh.
const initResult = await initializeUser({ token: userAccessToken });
if (!initResult.status) {
// token invalid/expired — handle accordingly
}
// Now cheap, cache-backed calls for the rest of the request:
const canEditInvoices = await checkPermission({
resourceId: 'invoices',
accessName: 'edit',
token: userAccessToken
});
const profile = await userProfile({ id: initResult.data.userId, token: userAccessToken });5. Response shape
Nearly every function resolves (it doesn't throw) to an object of the same shape, so you can handle success and failure the same way everywhere:
{ status: true, code: 200, message: 'Success', data: { /* whatever this call returns */ } }
// or, on failure:
{ status: false, code: 404, message: 'Not Found' } // no `data` key on failureAlways check .status before reading .data. A few functions differ from this (noted individually below) — verifyToken is local-only (no network call), getPermissions is an unimplemented stub, and getAttachedPolicy (RoleController's raw call) has an inconsistent shape covered in its own section.
6. Full function list
Everything below is grouped by domain, with parameters and an example for each. If you want every function explained in one place with backend-reliability notes (which reads are safe to trust vs. which have known join issues, which have clearer aliases, etc.), see the function reference doc alongside this README.
RBAC Functions
TRUIAM Functions
Authentication Functions
Admin Functions
Organization / Tenant / Permission Admin Functions
Initialize User
// Parameters:
// - token: STRING (Token for user initialization)
const initializeUserParams = {
token: 'STRING'
};
const initializeUserResult = await initializeUser(initializeUserParams);roleDetails (new): initializeUserResult.data now includes a roleDetails
array alongside the existing role field (an array of role name strings,
unchanged). roleDetails carries the full nested tree the backend already
returns on every initializeUser call — each entry has roleId, roleName,
and that role's attached policies (each with its own nested services /
accesses / resources, when the backend populates them) — instead of that
data being fetched and then discarded. Nothing about role/roles changes;
roleDetails is purely additive, so existing callers are unaffected.
// initializeUserResult.data.role -> ['UI ADMIN'] (unchanged)
// initializeUserResult.data.roleDetails -> [{ roleId: 9, roleName: 'UI ADMIN', policies: [...] }]Fixed this session — no longer requires Redis Stack / the RedisJSON module.
initializeUser's own Redis cache write previously ran a JSON.SET command,
which only exists if Redis has the RedisJSON module loaded. On plain
redis-server, that failed silently — JSON.SET errored out, the error was
swallowed internally, and initializeUser still reported success even though
nothing was actually cached, so every later call that depends on this cache
(checkPermission, getUserRoles, userProfile's cache-hit path) then
behaved as if the user had never been initialized. The cache write now stores
a plain JSON.stringify'd string instead (an ordinary Redis SET), which
works on any Redis — Redis Stack no longer required for this.
Native role-policy caching (new): alongside the session cache above,
initializeUser now also writes each role's own policies to its own key,
role:<roleId>:policies, with the same expiration as the session cache
(derived from the token's own exp claim). Roles with no policies attached
are skipped — nothing is written for them. initializeUserResult.data's
roleDetails already carries each role's policies inline, so this is for
callers who want to look up one specific role's policies by ID directly
(e.g. getEntity('role:9:policies') if you're reading Redis yourself)
without walking the whole session object. roleDetails also now carries a
roleIds sibling internally in the Redis-cached session value (an array of
just { roleId, roleName }), for the same reason.
// Redis key: role:<roleId>:policies (skipped if the role has no policies)
// value: the same `policies` array already present in roleDetails[i].policiesCheck Permission
Requires initializeUser(token) to have been called first for this token — checkPermission reads the cached data that initializeUser populates, and returns an error telling you to call initializeUser first if it hasn't been.
Fixed this session: calling this with a resourceId that has no attached policy for the role being checked (a role with no policies attached at all, or simply a resourceId nobody's been granted access to) used to throw Cannot read properties of undefined (reading '0') instead of returning a normal {data: {result: false}}. That's fixed now — an unmatched resourceId/role combination correctly resolves to "no," not a crash.
/* Parameters:
- resourceId: STRING | NUMBER (ID of the resource, not its name)
- accessName: STRING (Access/action name defined in TRUIAM ACCESS TYPE, e.g. 'view', 'edit')
- token: STRING (Access token — must already be initialized via initializeUser)
- roleName : STRING (optional — name of the role to check. If omitted, all of the
current user's roles are checked and the result is true if ANY of them has the access)
*/
const checkPermissionParams = {
resourceId: 'RESOURCE_ID',
accessName: 'view',
token: 'STRING'
};
const checkPermissionResult = await checkPermission(checkPermissionParams);User Profile
// Parameters:
// - token: STRING (User token)
const userProfileParams = {
token: 'STRING'
};
const userProfileResult = await userProfile(userProfileParams);roleId/roles (new): userProfileResult.data now also includes roleId
(the user's first attached role's ID, or null if none) and roles (the
full list of { roleId, roleName } the user has). This is resolved without
any new backend endpoint: if initializeUser(token) was already called for
this token, userProfile reads the role data straight from the same Redis
cache initializeUser populates — no extra backend call. If it wasn't
(nothing cached yet for this token), userProfile falls back to calling the
same /client/initialize logic initializeUser uses internally, purely to
resolve and cache the role data, then reads it from there. Worth knowing:
on that cache-miss path, userProfile does more backend work than it did
before this change — the same work initializeUser would have done. Calling
initializeUser(token) before userProfile(token) (the common flow already)
avoids that extra work entirely.
// userProfileResult.data.roleId -> 9
// userProfileResult.data.roles -> [{ roleId: 9, roleName: 'UI ADMIN' }]Get User Roles
// Parameters:
// - token: STRING (User token)
const getUserRolesParams = {
token: 'STRING'
};
const getUserRolesResult = await getUserRoles(getUserRolesParams);Fixed this session: if the cached session data for a token wasn't in the
expected shape (most commonly, a token cached before this session's Redis fix
shipped, back when the underlying JSON.SET cache write silently failed on
plain Redis), getUserRoles used to still return a 200 OK with
{ roles: undefined } — a broken payload disguised as success — instead of
either the real role list or a clear error. It now checks that the cached
roles value is actually an array before returning success, and returns a
400 with a clear message (asking you to call initializeUser(token) again)
if it isn't. If nothing is cached for the token at all, you still get the
existing 404 User not found or Token has expired.
Get Organization Detail
// Parameters:
// - orgId: STRING (ID of the organization)
// - token: STRING (User token)
const getOrgDetailParams = {
orgId: '19',
token: 'STRING'
};
const getOrgDetailResult = await getOrgDetail(getOrgDetailParams);Get Organization by Token
// Parameters:
// - token: STRING (User token)
const getOrgByTokenParams = {
token: 'STRING'
};
const getOrgByTokenResult = await getOrgByToken(getOrgByTokenParams);Get Role by ID
token is required, despite reading like a plain by-ID lookup — the backend route behind this (GET /client/roleById) actually requires an access token and returns 401 Token is missing without one. Found by running this against a real backend; it had zero test coverage before that, so the earlier version of this doc (and the request shape below) omitted token for a while without anything catching it.
// Parameters:
// - roleId: STRING (ID of the role)
// - orgId: STRING (ID of the organization)
// - token: STRING (caller's access token)
const getRoleByIdParams = {
roleId: '41',
orgId: '38',
token: 'the-caller-access-token'
};
const getRoleByIdResult = await getRoleById(getRoleByIdParams);Get Attached Policy
Known limitation: the backend endpoint behind this (GET
/client/getPolicyAttachedToRole) joins through consent-based tables the
admin CRUD flow doesn't populate, so its result may not reflect real
attachments for normally-configured orgs. getPolicyDetailsByRole below
uses this call only for the role→policy step and re-derives everything else
through reliably-joined calls — prefer it over trusting this call's shape
directly for anything beyond a bare policy-id list.
// Parameters:
// -
roleId: STRING (ID of the role)
// - orgId: STRING (ID of the organization)
const getAttachedPolicyParams = {
roleId: '41',
orgId: '38'
};
const getAttachedPolicyResult = await getAttachedPolicy(getAttachedPolicyParams);Get Policy by ID
// Parameters:
// - policyId: STRING (ID of the policy)
// - orgId: STRING (ID of the organization)
const getPolicyByIdParams = {
policyId: '11',
orgId: '38'
};
const getPolicyByIdResult = await getPolicyById(getPolicyByIdParams);Get Attached Service
// Parameters:
// - policyId: STRING (ID of the policy)
// - orgId: STRING (ID of the organization)
const getAttachedServiceParams = {
policyId: '11',
orgId: '38'
};
const getAttachedServiceResult = await getAttachedService(getAttachedServiceParams);Get Service by ID
// Parameters:
// - serviceId: STRING (ID of the service)
const getServiceByIdParams = {
serviceId: 'SERVICE_ID'
};
const getServiceByIdResult = await getServiceById(getServiceByIdParams);Get Attached Access
// Parameters:
// - serviceId: STRING (ID of the service)
// - orgId: STRING (ID of the organization)
const getAttachedAccessParams = {
serviceId: 'SERVICE_ID',
orgId: 'ORG_ID'
};
const getAttachedAccessResult = await getAttachedAccess(getAttachedAccessParams);Get Access by ID
// Parameters:
// - accessId: STRING (ID of the access)
const getAccessByIdParams = {
accessId: 'ACCESS_ID'
};
const getAccessByIdResult = await getAccessById(getAccessByIdParams);Get Attached Resource
// Parameters:
// - accessId: STRING (ID of the access)
// - orgId: STRING (ID of the organization)
const getAttachedResourceParams = {
accessId: 'ACCESS_ID',
orgId: 'ORG_ID'
};
const getAttachedResourceResult = await getAttachedResource(getAttachedResourceParams);Get Policy Details by Role
New. Composes the four calls above into one "everything attached to this role" result — role → policy → service → access → resource — so callers don't have to walk the chain by hand. Every leg was already exported and already Redis-cached; this just assembles them. Deliberately does not use whatever service/access/resource data the backend's role→policy lookup might itself embed — only its policy list is used, and the service/access/resource detail underneath each policy is re-fetched via the same reliable, cached calls documented above.
// Parameters:
// - roleIds: STRING (ID(s) of the role, same format as Get Attached Policy's roleIds)
// - orgId: STRING (ID of the organization)
// - orgTenantId: STRING (optional — scopes the lookup to a specific org-tenant)
// - token: STRING (Access token)
const getPolicyDetailsByRoleParams = {
roleIds: 'ROLE_ID',
orgId: 'ORG_ID',
token: 'STRING'
};
const getPolicyDetailsByRoleResult = await getPolicyDetailsByRole(getPolicyDetailsByRoleParams);
// getPolicyDetailsByRoleResult.data -> [
// {
// policyId, policyName, policyDescription,
// services: [
// { serviceId, serviceName, accesses: [
// { accessId, accessName, resources: [{ resourceId, resourceName }] }
// ]}
// ]
// }
// ]Get RBAC Model
Fetches an organization's (optionally org-tenant-scoped) RBAC structure and caches it in Redis.
Known limitation: the backend query behind this (GET /client/rbac)
joins through the same tables the admin CRUD flow doesn't populate (a
"consent"-based join, not policy_service/service_access/access_resources)
— the same reliability problem documented for getAttachedPolicy below. Its
per-role structure may not reflect real policy/service/access/resource
attachments for normally-configured orgs. If you need reliable full policy
detail scoped to an org or org-tenant, use getPolicyDetailsByOrg below
instead, which is built on the correctly-joined calls.
// Parameters:
// - orgId: STRING
// - orgTenantId: STRING (optional — scopes the model to a specific org-tenant)
// - token: STRING (caller's access token -- required; a live run showed this
// was never being forwarded to the backend at all, causing every call to
// fail with a misleading "No structure found" instead of an auth error)
const getRbacModelParams = {
orgId: 'YOUR_ORG_ID',
token: 'the-caller-access-token'
};
const getRbacModelResult = await getRbacModel(getRbacModelParams);Get Policy Details by Org
New. The org/org-tenant-basis counterpart to getPolicyDetailsByRole
above — same assembled policy → service → access → resource shape, but
starting from every policy in your own org (optionally narrowed to one
org-tenant) instead of a role's attached policies. Built on listPolicy
(correctly-joined tables), not getRbacModel/getAttachedPolicy (see the
caution notes on those).
// Parameters:
// - headerData: OBJECT ({domain, idp, token}) -- used for the policy listing call
// - orgId: STRING (must match the org headerData.token belongs to -- used
// for the service/access/resource lookups underneath, same as
// getPolicyDetailsByRole)
// - orgTenantId: STRING (optional — scope to one org-tenant's policies)
// - includeOrgLevel: BOOLEAN (optional — only meaningful with orgTenantId;
// when true, also includes the org-level (tenant-less) policies)
// - type: STRING (optional policy type filter, same as listPolicy)
//
// Org scope itself is always the caller's own org, derived server-side from
// headerData.token -- there's no way to fetch another org's policies
// through this call.
const getPolicyDetailsByOrgParams = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const getPolicyDetailsByOrgResult = await getPolicyDetailsByOrg(getPolicyDetailsByOrgParams);
// getPolicyDetailsByOrgResult.data -> [
// {
// policyId, policyName, policyDescription,
// services: [
// { serviceId, serviceName, accesses: [
// { accessId, accessName, resources: [{ resourceId, resourceName }] }
// ]}
// ]
// }
// ]Set Up Redis Config
Optional. As of Slice 0, Redis connection details are read automatically
from the host application's own environment (REDIS_URL, or
REDIS_HOST/REDIS_PORT/REDIS_USERNAME/REDIS_PASSWORD) — calling this
is no longer required. It still works as an explicit override if you'd
rather configure Redis in code than via environment variables.
// Parameters:
// - host: STRING
// - port: NUMBER
// - password: STRING
const setUpRedisConfigParams = {
host: 'YOUR_REDIS_HOST',
port: 6379,
password: 'YOUR_REDIS_PASSWORD'
};
await setUpRedisConfig(setUpRedisConfigParams);Get Permissions
Not yet implemented. getPermissions is exported from the package
today, but its body is a stub (a couple of commented-out lines and a
console.log) — it does not call the backend, does not read Redis, and
always resolves to undefined. Documented here so it isn't mistaken for a
working function; don't rely on it until it has a real implementation.
Confirm Email Sign-Up Code
// Parameters
// `email`: STRING (Email address)
// `clientId`: STRING (Client ID)
// `domain`: STRING (Domain)
// `code`: NUMBER (Sign-up code)
const confirmEmailSignUpCodeParams = {
email: 'YOUR_EMAIL',
clientId: 'YOUR_CLIENT_ID',
domain: 'YOUR_DOMAIN',
code: YOUR_CODE
};
const confirmEmailSignUpCodeResult = await confirmEmailSignUpCode(confirmEmailSignUpCodeParams);Resend Email Sign-Up Code
// ##### Parameters
// - `email`: STRING (Email address)
// - `clientId`: STRING (Client ID)
// - `domain`: STRING (Domain)
// - `code`: NUMBER (Sign-up code)
const resendEmailSignUpCodeParams = {
email: 'YOUR_EMAIL',
clientId: 'YOUR_CLIENT_ID',
domain: 'YOUR_DOMAIN',
code: YOUR_CODE
};
const resendEmailSignUpCodeResult = await resendEmailSignUpCode(resendEmailSignUpCodeParams);Execute Sign-In
authorize is exported separately but calls the exact same backend
function under the hood — the two are interchangeable.
// ##### Parameters
// - `email`: STRING (Email address)
// - `password`: STRING (Password)
// - `clientId`: STRING (Client ID)
// - `callbackurl`: STRING (Callback URL)
// - `domain`: STRING (Domain)
const executeSignInParams = {
email: 'YOUR_EMAIL',
password: 'YOUR_PASSWORD',
clientId: 'YOUR_CLIENT_ID',
callbackurl: 'YOUR_CALLBACK_URL',
domain: 'YOUR_DOMAIN'
};
const executeSignInResult = await executeSignIn(executeSignInParams);Execute Sign-Up
// ##### Parameters
// - `clientId`: STRING (Client ID)
// - `email`: STRING (Email address)
// - `password`: STRING (Password)
// - `domain`: STRING (Domain)
const executeSignUpParams = {
clientId: 'YOUR_CLIENT_ID',
email: 'YOUR_EMAIL',
password: 'YOUR_PASSWORD',
}
const result = await rbac.executeSignUp(executeSignUpParams);Execute Logout
// ##### Parameters
// - `clientId`: STRING (Client ID)
// - `refreshToken`: STRING (USER REFRESH TOKEN)
const params = {
clientId: 'YOUR_CLIENT_ID',
refreshToken: 'YOUR_REFRESH_TOKEN'
}
const result = await rbac.executeLogoutUser(params);Get Refreshed Access Token
// ##### Parameters
// - `clientId`: STRING (Client ID)
// - `domain`: STRING (Domain)
// - `refreshToken`: STRING (USER REFRESH TOKEN)
const params = {
clientId: 'YOUR_CLIENT_ID',
domain: 'YOUR_DOMAIN',
refreshToken: 'YOUR_REFRESH_TOKEN'
}
const result = await rbac.refreshAccessToken(params);Get DNS Detail
Parameters
clientId: STRING (Client ID)domain: STRING (Domain)callbackurl: STRING (Callback URL)
const getDnsDetailParams = {
clientId: 'YOUR_CLIENT_ID',
callbackurl: 'YOUR_CALLBACK_URL',
domain: 'YOUR_DOMAIN'
};
const getDnsDetailResult = await getDnsDetail(getDnsDetailParams);Update Password
Updates the user's password.
Parameters
domain: STRING (Domain)email: STRING (Email address)newPassword: STRING (New password)callbackurl: STRING (Callback URL)clientId: STRING (Client ID)
Example
const updatePasswordParams = {
domain: 'YOUR_DOMAIN',
email: 'YOUR_EMAIL',
newPassword: 'YOUR_NEW_PASSWORD',
callbackurl: 'YOUR_CALLBACK_URL',
clientId: 'YOUR_CLIENT_ID',
};
const updatePasswordResult = await updatePassword(updatePasswordParams);Forgot Password
Initiates the forgot password process.
Parameters
domain: STRING (Domain)email: STRING (Email address)clientId: STRING (Client ID)
Example
const forgotPasswordParams = {
domain: 'YOUR_DOMAIN',
email: 'YOUR_EMAIL',
clientId: 'YOUR_CLIENT_ID',
};
const forgotPasswordResult = await forgotPassword(forgotPasswordParams);Reset Password
Resets the user's password using a code.
Parameters
domain: STRING (Domain)email: STRING (Email address)newPassword: STRING (New password)code: STRING | NUMBER (Reset code)clientId: STRING (Client ID)
Example
const resetPasswordParams = {
domain: 'YOUR_DOMAIN',
email: 'YOUR_EMAIL',
newPassword: 'YOUR_NEW_PASSWORD',
code: 'YOUR_RESET_CODE',
clientId: 'YOUR_CLIENT_ID',
};
const resetPasswordResult = await resetPassword(resetPasswordParams);Verify Access Token
Verifies the provided access token locally — it decodes the JWT and
checks its expiry. This does not make a network call and does not validate
the token's cryptographic signature (the package has no access to the
issuer's signing key or a JWKS endpoint), so treat valid: true as "well-formed
and not expired," not as proof the token wasn't forged.
Parameters
token: STRING (Access Token to be verified)
Returns
data.valid: BOOLEANdata.decoded: the decoded JWT payload, whenvalidistruedata.reason:'expired'or'malformed', whenvalidisfalse
Example
const verifyTokenParams = {
token: 'YOUR_ACCESS_TOKEN',
};
const verifyTokenResult = await verifyToken(verifyTokenParams);Exchange Code for tokens
Retrieves tokens using a code.
Parameters
code: STRING (Code to exchange for tokens)callbackurl: STRING (Callback URL)clientId: STRING (Client ID)
Example
const getTokenFromCodeParams = {
code: 'YOUR_CODE',
callbackurl: 'YOUR_CALLBACK_URL',
clientId: 'YOUR_CLIENT_ID',
};
const getTokenFromCodeResult = await getTokenFromCode(getTokenFromCodeParams);Unlock User
All Admin Functions below take a headerData object and, where relevant, a
payload/userId/enable field — not the flat {domain, idp,
accessToken, ...} shape shown in earlier revisions of this README, which
never matched the actual code (headerData wasn't read at all, so calls
with the old shape would throw). headerData is passed straight through as
the outgoing HTTP headers, so its keys must match what the backend expects:
domain, idp, token (not accessToken), and — only for
external/third-party callers — external: 'true' and extapp (your client
ID). This matches the commonHeaderData fixture used throughout this
package's own test suite.
Parameters:
headerData.domain: STRING (Domain)headerData.idp: STRING (IDP)headerData.token: STRING (Access token)userId: NUMBER (User ID)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
userId: 42
};
const result = await rbac.unlockUser(params);Delete User
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - userId: NUMBER (User ID)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
userId: 42
};
const result = await rbac.deleteUser(params);Create User
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (new user's data, e.g. email and password)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
email: '[email protected]',
password: 'password'
}
};
const result = await rbac.createUser(params);Profile Fields
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
}
};
const result = await rbac.profileFields(params);User Account Action
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT ({userId, action: 'disable' | 'enable'})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
userId: 42,
action: 'disable'
}
};
const result = await rbac.userAccountAction(params);Toggle MFA
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - userId: NUMBER (User ID)
// - enable: BOOLEAN (Flag to enable/disable MFA)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
userId: 42,
enable: true
};
const result = await rbac.toggleMfa(params);Get User
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - userId: NUMBER (User ID)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
userId: 42
};
const result = await rbac.getUser(params);Get All Users
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
}
};
const result = await rbac.getAllUser(params);Update User
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (profile fields to update)
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
firstName: 'Jane',
lastName: 'Doe'
}
};
const result = await rbac.updateUser(params);Migrate User
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (migration details, e.g. {userId, targetOrgId})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
userId: 42,
targetOrgId: 7
}
};
const result = await rbac.migrateUser(params);Pending Approvals List
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (filters for the pending-approvals query, e.g. {orgId})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
orgId: 1
}
};
const result = await rbac.pendingApprovalsList(params);Request Action
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT ({userId, action})
const params = {
headerData: {
domain: 'YOUR_DOMAIN',
idp: 'YOUR_IDP',
token: 'YOUR_ACCESS_TOKEN'
},
payload: {
userId: 42,
action: 'approve'
}
};
const result = await rbac.requestAction(params);Organization / Tenant / Permission Admin Functions
This whole group of functions (below) was fully implemented and exported
from the package but had zero README documentation before this section
was added. All of them take the same headerData: {domain, idp, token}
shape as the Admin Functions above.
A few names here were too generic for a public package's export list —
list, details, otList, and addMember/removeMember in particular
are easy to confuse with unrelated functionality, and addMember/
removeMember are actually org-tenant-scoped despite the generic name
(see addOrgMember/removeOrgMember for the org-level equivalents).
Clearer aliases are documented alongside each — the original names still
work unchanged, so switching is entirely optional.
List Organizations
// Aliases: list (original), listOrganizations (clearer)
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' }
};
const result = await rbac.listOrganizations(params);Get Organization Details
// Aliases: details (original), getOrganizationDetails (clearer)
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.getOrganizationDetails(params);Member List
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.memberList(params);App List
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.appList(params);Role List
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.roleList(params);Admin List
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.adminList(params);App List Without Org
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' }
};
const result = await rbac.appListWithoutOrg(params);Add Org Member
Adds a member at the organization level.
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (e.g. {orgId, userId})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
payload: { orgId: 'YOUR_ORG_ID', userId: 42 }
};
const result = await rbac.addOrgMember(params);Remove Org Member
Removes a member at the organization level.
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (e.g. {orgId, userId})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
payload: { orgId: 'YOUR_ORG_ID', userId: 42 }
};
const result = await rbac.removeOrgMember(params);List Org Tenants
// Aliases: otList (original), listOrgTenants (clearer)
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.listOrgTenants(params);Add Org-Tenant Member
Adds a member at the org-tenant level (not the organization level — see Add Org Member above for that).
// Aliases: addMember (original), addOrgTenantMember (clearer)
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (e.g. {orgTenantId, userId})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
payload: { orgTenantId: 'YOUR_ORG_TENANT_ID', userId: 42 }
};
const result = await rbac.addOrgTenantMember(params);Remove Org-Tenant Member
Removes a member at the org-tenant level (not the organization level — see Remove Org Member above for that).
// Aliases: removeMember (original), removeOrgTenantMember (clearer)
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT (e.g. {orgTenantId, userId})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
payload: { orgTenantId: 'YOUR_ORG_TENANT_ID', userId: 42 }
};
const result = await rbac.removeOrgTenantMember(params);Tenant Settings
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' }
};
const result = await rbac.tenantSettings(params);Tenant Details
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' }
};
const result = await rbac.tenantDetails(params);List Attached Policy Role
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - orgId: STRING
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
orgId: 'YOUR_ORG_ID'
};
const result = await rbac.list_attached_policy_role(params);List Policy
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - type: STRING (policy type filter — e.g. 'org' or 'tenant')
// - orgTenantId: STRING (optional — scope to one org-tenant's policies)
// - includeOrgLevel: BOOLEAN (optional — only meaningful with orgTenantId;
// when true, also includes the org-level (tenant-less) policies alongside
// that tenant's own)
//
// Earlier versions of this function read the `type` filter from a field
// named `orgId`, which was a naming bug (the backend calls it `type`, and it
// isn't actually an org ID). `type` is now the documented name; `orgId` is
// still accepted as a deprecated fallback. Org scope itself is always the
// caller's own org, derived server-side from the token in `headerData` —
// there's no way to list another org's policies through this call.
const params = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
type: 'org'
};
const result = await rbac.listPolicy(params);
// Scoped to one org-tenant, including that org's tenant-less policies too:
const tenantScopedParams = {
headerData: { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' },
type: 'org',
orgTenantId: 'YOUR_ORG_TENANT_ID',
includeOrgLevel: true
};
const tenantScopedResult = await rbac.listPolicy(tenantScopedParams);Create Resource
// Parameters:
// - headerData: OBJECT ({domain, idp, token})
// - payload: OBJECT ({name, description, orgTenantId})
const headerData = { domain: 'YOUR_DOMAIN', idp: 'YOUR_IDP', token: 'YOUR_ACCESS_TOKEN' };
const payload = {
name: 'YOUR_RESOURCE_NAME',
description: 'YOUR_RESOURCE_DESCRIPTION',
orgTenantId: 'YOUR_ORG_TENANT_ID'
};
const result = await rbac.createResource(headerData, payload);Get Org-Tenant Detail
// Parameters:
// - orgId: STRING
// - orgTenantId: STRING
// - token: STRING (Access token)
const params = {
orgId: 'YOUR_ORG_ID',
orgTenantId: 'YOUR_ORG_TENANT_ID',
token: 'YOUR_ACCESS_TOKEN'
};
const result = await rbac.getOrgTenantDetail(params);New-feature candidates (not yet built)
These exist on the TruIAM Backend Service under /client/* but
aren't wrapped by this package yet. Flagged here rather than built, since
the intended use case needs a quick confirmation first:
getRoleAttachedToUser— not yet wrapped.resourceById— only used internally byResourceController, never exposed at the top level.generateSession/validateSession/sessionStatus— a QR-code partner-login session flow (confirmed while investigating Slice 2'sverifyTokenfix), separate from the JWT-based auth this package wraps everywhere else. May not be something this package needs at all.
Webhook Events (TruIAM class)
TruIAM is a small Express router factory for receiving TruIAM webhook
events in your own app. It has no README coverage before this section
despite being a real exported part of the public API.
Current status: as of this writing, the TruIAM Backend Service does not
actually emit any webhook calls to this endpoint yet (confirmed against its
source — there is no code there that POSTs to /truiam/event). Wiring this
up now means your handlers are ready for when the backend starts sending
these events.
Usage
const { TruIAM } = require('rbac-truiam');
const express = require('express');
const app = express();
app.use(express.json());
const truiam = new TruIAM();
// Override whichever handlers you care about — each receives the webhook's
// request body and should return a boolean. Unoverridden handlers just log
// and return true.
truiam.onUserCreation = async (body) => {
console.log('user created', body);
return true;
};
truiam.onTenantDeletion = async (body) => {
console.log('tenant deleted', body);
return true;
};
// `middleware()` is a factory — call it once to get a Router, then mount
// that Router. Don't pass `truiam.middleware` (uncalled) to `app.use()`;
// it takes no arguments and returns the Router you actually want to mount.
app.use(truiam.middleware());Available handlers
onUserCreation, onUserDeletion, onTenantCreation, onTenantDeletion,
onOrganizationCreation, onOrganizationDeletion, onGroupCreation.
Event routing
The mounted router listens on POST /truiam/event and dispatches on
req.body.eventType: tenant_creation, tenant_deletion, user_creation,
organization_creation, organization_deletion, and group_creation each
call their matching handler above and respond 200. Any other event type
(or any other path) responds without calling a handler — 404 for an
unmatched path, 200 for an unrecognized eventType on the webhook path
itself.
Notes
- Call
.middleware()once per app (e.g. at startup), not per-request — each call builds a fresh Router.
Testing This Package Before Publishing
If you're working on this package itself (not just consuming it), run these
in order before npm publish — each catches a different class of problem
that the others don't:
npm install
npm run build # compiles src/ -> lib/ (tsc)
npm test # the mocked Jest suite -- fast, no network required
npm run verify-package # packaging-level checks -- see belownpm run verify-package (scripts/verify-package.js) exists because
tsc --noEmit and npm test both being green doesn't guarantee the
published tarball is correct — it builds the package, runs npm pack,
installs the real tarball into a scratch temp project, and checks: the
compiled .js and .d.ts files are actually present, src/test/
src/__mocks__ are excluded from what gets published, a fresh npm install
of the tarball produces no deprecation warnings, require() resolves every
expected export as a function/class, an offline function call
(verifyToken) actually runs, and a real TypeScript consumer gets real type
information rather than silently falling back to any. This is a pure
local check — no network access needed, safe to run anytime. (On Windows,
this script resolves npm to npm.cmd internally — a plain
execFileSync('npm', ...) fails there with ENOENT since Windows doesn't
treat npm as directly executable without a shell.)
Manual smoke test via npm link — useful for exercising real behavior
interactively before committing to a version bump:
npm link # from this package's root
cd /path/to/some/consumer/project
npm link rbac-truiam # points the consumer at your local build via symlinknpm link does not go through package.json's "files" filtering the
way a real install does, so it can mask a packaging bug that only
npm pack/verify-package would catch — use it for behavior checks, not as
a substitute for verify-package.
Testing against a real backend — the mocked Jest suite and
verify-package both stop short of proving the package actually talks to a
live TruIAM backend correctly. The live-test/ folder (gitignored, not
published) is a small standalone harness for that: it exercises every
read-only exported function against a real backend with a real access
token and prints a per-function pass/fail report, without ever calling
anything that mutates a live account (no user creation/deletion, password
resets, etc. — those are intentionally excluded and would need to be added
on purpose if you ever want to test them). See live-test/README.md for
usage; in short:
cd live-test
TRUIAM_TOKEN="<a live Cognito access token>" IDP="cognito" DOMAIN="your-domain" node run.jsNever commit a real access token into any file in this repo — pass it as an
environment variable at the command line as shown above. live-test's
output (live-test/results/*.json) is automatically scrubbed of the token
value before being written to disk.
