import {useOidc} from "./useOidc";
import {logger} from "../../services";
/**
* @typedef {object} IdentityCheckReturn
*/
type IdentityCheckReturn = {
/**
* All of the provided values match / are present in the attribute
*/
all: boolean,
/**
* At least one of the provided values match / is present in the attribute
*/
any: boolean,
/**
* The supplied attribute is present in the user object (e.g. optional attributes like groups)
*/
exists: boolean
}
/**
* Custom hook for checking the current OIDC user's identity attributes.
*
* This hook tells you about identity attributes in the current user object.
*
* You can supply either strings or regex objects to the lookup values of the hook. Strings will be matched completely
* (i.e. `x == y`) and regexes will use the built-in evaluation (i.e. `regex.test(value)`).
*
* The hook returns three booleans: `exists`, `any`, and `all`
* - `exists`: The supplied attribute is present in the user object (e.g. optional attributes like groups)
* - `any`: At least one of the provided values match / is present in the attribute
* - `all`: All of the provided values match / are present in the attribute
*
* ### Hook Usage Examples
* #### Checking if the user has a specific `groups` value (e.g. orgu-1440)
*
* When a single value argument is supplied, the results for `any` and `all` will be the same.
*
* ```
* const {any} = useIdentityCheck('groups', /orgu-1440/)
* ```
*
* <br/>
*
* #### Checking if the user is in one or more of a set of `groups` <span id="use-identity-check-example-anchor"/>
*
* Multiple values can be provided to the hook to be evaluated collectively. In this case, `any` will be true if any of
* the supplied values match / are present in the supplied attribute and `all` will only be true if **all** the supplied
* values match / are present in the supplied attribute.
*
* See [below](#use-identity-check-multiple-note) for gotchas about regexes and searching for multiple values.
*
* ```
* const {all, any} = useIdentityCheck('groups', /144040/, /144060/)
* ```
* <br/>
*
* #### Checking if the users first name is 'john' or contains a 'y'
*
* This example is not likely to be a common use case but demonstrates the ability to check any piece of identity
* information.
*
* ```
* const {any} = useIdentityCheck('given_name', 'john', /y/)
* ```
*
* <br/>
*
* #### Regex matching
*
* Because the matching portion of the hook evaluates supplied regexes using the built regex engine, you can provide
* "complex" regexes to match more specific patterns.
*
* For the sake of demonstration, lets assume scrum masters have a specific orgu role assigned to their identity (e.g. orgu-144040-ScrumMaster)
* in addition to their standard staff orgu role (e.g. orgu-144040-Staff)
*
* We can use a regex pattern to only allow access for someone who is a scrum master in orgu 144040 OR 144060.
*
* ```
* const {all, any} = useIdentityCheck('groups', /1440(4|6).*scrum/i)
* ```
*
* <span id="use-identity-check-multiple-note">Note:</span> The logic above can be confusing due to the regex having multiple "clauses". The `any` and `all` values of the
* hook relate to how many of the provided values match, NOT whether the supplied value matches multiple attribute values.
*
* In the above example, the `all` value will be true if a user is in 144040 OR 144060 because the supplied value matches
* an attribute value. If you want to check multiple conditions, they should be supplied as two separate argument values.
* See [this example](#use-identity-check-example-anchor)
*
* @category Hooks
*
* @param attribute {string} Which identity attribute to check
* @param values {string | RegExp} Variable number of values to look for in `attribute`
*
* @return {IdentityCheckReturn}
*/
export function useIdentityCheck(attribute: string, ...values: Array<string | RegExp>): IdentityCheckReturn {
// Extract the user profile from the oidc user object
const {user} = useOidc()
//TODO: How defensive should we be?
// Missing attribute or no present user
if (!attribute || !user) {
return {all: false, any: false, exists: false}
}
let {profile} = user
// Check if attribute is present in the identity
let exists = profile.hasOwnProperty(attribute)
// If only attribute is provided or the attribute doesn't exist, finish here
if (values.length < 1 || !exists) {
return {all: exists, any: exists, exists}
}
let all = true, any = false
for (let value of values) {
// Check if the value is an invalid type, if so log dev error and return false for everything
if (!(typeof value === 'string' || value instanceof String || value instanceof RegExp)) {
logger.devError("Values for useIdentityCheck must be strings or RegExp")
return {all: false, any: false, exists: false}
}
let attrValue = profile[attribute]
let valuePresent = false
if (Array.isArray(attrValue)) {
// If attribute is an array, check if the requested value is present in one of the entries
if (value instanceof RegExp) {
// If the requested value is a regex, match it against
//@ts-ignore
valuePresent = attrValue.some((v) => value.test(v))
} else {
// Otherwise use equality check
valuePresent = attrValue.some((v) => value == v)
}
} else {
// If attribute is not an array, check the value directly
if (value instanceof RegExp) {
// If the requested value is a regex, match it against
valuePresent = value.test(attrValue)
} else {
// Otherwise use equality check
valuePresent = value == attrValue
}
}
// Add the current values logic to the existing values
all = all && valuePresent
any = any || valuePresent
}
return {all, any, exists}
}
Source